all repos — flounder @ d6be68418a350fc835a86dd82bba9935d7a609ba

A small site builder for the Gemini protocol

http.go (view raw)

  1package main
  2
  3import (
  4	"git.sr.ht/~adnano/gmi"
  5	"github.com/gorilla/handlers"
  6	"html/template"
  7	"io"
  8	"io/ioutil"
  9	"log"
 10	"net/http"
 11	"os"
 12	"path"
 13	"path/filepath"
 14	"strings"
 15	"time"
 16)
 17
 18var t *template.Template
 19
 20const InternalServerErrorMsg = "500: Internal Server Error"
 21
 22func renderError(w http.ResponseWriter, errorMsg string, statusCode int) {
 23	data := struct {
 24		PageTitle string
 25		ErrorMsg  string
 26	}{"Error!", errorMsg}
 27	err := t.ExecuteTemplate(w, "error.html", data)
 28	if err != nil { // shouldn't happen probably
 29		http.Error(w, errorMsg, statusCode)
 30	}
 31}
 32
 33func rootHandler(w http.ResponseWriter, r *http.Request) {
 34	// serve everything inside static directory
 35	if r.URL.Path != "/" {
 36		fileName := path.Join(c.TemplatesDirectory, "static", filepath.Clean(r.URL.Path))
 37		http.ServeFile(w, r, fileName)
 38		return
 39	}
 40	indexFiles, err := getIndexFiles()
 41	if err != nil {
 42		log.Println(err)
 43		renderError(w, InternalServerErrorMsg, 500)
 44		return
 45	}
 46	allUsers, err := getUsers()
 47	if err != nil {
 48		log.Println(err)
 49		renderError(w, InternalServerErrorMsg, 500)
 50		return
 51	}
 52	data := struct {
 53		Domain    string
 54		PageTitle string
 55		Files     []*File
 56		Users     []string
 57	}{c.RootDomain, c.SiteTitle, indexFiles, allUsers}
 58	err = t.ExecuteTemplate(w, "index.html", data)
 59	if err != nil {
 60		log.Println(err)
 61		renderError(w, InternalServerErrorMsg, 500)
 62		return
 63	}
 64}
 65
 66func editFileHandler(w http.ResponseWriter, r *http.Request) {
 67	authUser := "alex"
 68	fileName := filepath.Clean(r.URL.Path[len("/edit/"):])
 69	filePath := path.Join(c.FilesDirectory, authUser, fileName)
 70	if r.Method == "GET" {
 71		err := checkIfValidFile(filePath, nil)
 72		if err != nil {
 73			log.Println(err)
 74			renderError(w, err.Error(), 400)
 75			return
 76		}
 77		f, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
 78		defer f.Close()
 79		fileBytes, err := ioutil.ReadAll(f)
 80		if err != nil {
 81			log.Println(err)
 82			renderError(w, InternalServerErrorMsg, 500)
 83			return
 84		}
 85		data := struct {
 86			FileName  string
 87			FileText  string
 88			PageTitle string
 89		}{fileName, string(fileBytes), c.SiteTitle}
 90		err = t.ExecuteTemplate(w, "edit_file.html", data)
 91		if err != nil {
 92			log.Println(err)
 93			renderError(w, InternalServerErrorMsg, 500)
 94			return
 95		}
 96	} else if r.Method == "POST" {
 97		// get post body
 98		r.ParseForm()
 99		fileBytes := []byte(r.Form.Get("file_text"))
100		err := checkIfValidFile(filePath, fileBytes)
101		if err != nil {
102			log.Println(err)
103			renderError(w, err.Error(), 400)
104			return
105		}
106		err = ioutil.WriteFile(filePath, fileBytes, 0644)
107		if err != nil {
108			log.Println(err)
109			renderError(w, InternalServerErrorMsg, 500)
110			return
111		}
112		http.Redirect(w, r, "/my_site", 302)
113	}
114}
115
116func uploadFilesHandler(w http.ResponseWriter, r *http.Request) {
117	if r.Method == "POST" {
118		authUser := "alex"
119		r.ParseMultipartForm(10 << 20)
120		file, fileHeader, err := r.FormFile("file")
121		fileName := filepath.Clean(fileHeader.Filename)
122		defer file.Close()
123		if err != nil {
124			log.Println(err)
125			renderError(w, err.Error(), 400)
126			return
127		}
128		var dest []byte
129		file.Read(dest)
130		log.Println("asdfadf")
131		err = checkIfValidFile(fileName, dest)
132		if err != nil {
133			log.Println(err)
134			renderError(w, err.Error(), 400)
135			return
136		}
137		destPath := path.Join(c.FilesDirectory, authUser, fileName)
138
139		f, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE, 0644)
140		if err != nil {
141			log.Println(err)
142			renderError(w, InternalServerErrorMsg, 500)
143			return
144		}
145		defer f.Close()
146		io.Copy(f, file)
147	}
148	http.Redirect(w, r, "/my_site", 302)
149}
150
151func deleteFileHandler(w http.ResponseWriter, r *http.Request) {
152	authUser := "alex"
153	fileName := filepath.Clean(r.URL.Path[len("/delete/"):])
154	filePath := path.Join(c.FilesDirectory, authUser, fileName)
155	if r.Method == "POST" {
156		os.Remove(filePath) // suppress error
157	}
158	http.Redirect(w, r, "/my_site", 302)
159}
160
161func mySiteHandler(w http.ResponseWriter, r *http.Request) {
162	authUser := "alex"
163	// check auth
164	files, _ := getUserFiles(authUser)
165	data := struct {
166		Domain    string
167		PageTitle string
168		AuthUser  string
169		Files     []*File
170	}{c.RootDomain, c.SiteTitle, authUser, files}
171	_ = t.ExecuteTemplate(w, "my_site.html", data)
172}
173
174func loginHandler(w http.ResponseWriter, r *http.Request) {
175	if r.Method == "GET" {
176		// show page
177		data := struct {
178			Error     string
179			PageTitle string
180		}{"", "Login"}
181		err := t.ExecuteTemplate(w, "login.html", data)
182		if err != nil {
183			log.Println(err)
184			renderError(w, InternalServerErrorMsg, 500)
185			return
186		}
187	} else if r.Method == "POST" {
188		r.ParseForm()
189		name := r.Form.Get("username")
190		password := r.Form.Get("password")
191		err := checkAuth(name, password)
192		if err == nil {
193			log.Println("logged in")
194			// redirect home
195		} else {
196			data := struct {
197				Error     string
198				PageTitle string
199			}{"Invalid login or password", c.SiteTitle}
200			err := t.ExecuteTemplate(w, "login.html", data)
201			if err != nil {
202				log.Println(err)
203				renderError(w, InternalServerErrorMsg, 500)
204				return
205			}
206		}
207		// create session
208		// redirect home
209		// verify login
210		// check for errors
211	}
212}
213
214func registerHandler(w http.ResponseWriter, r *http.Request) {
215	if r.Method == "GET" {
216		data := struct {
217			Domain    string
218			Errors    []string
219			PageTitle string
220		}{c.RootDomain, nil, "Register"}
221		err := t.ExecuteTemplate(w, "register.html", data)
222		if err != nil {
223			log.Println(err)
224			renderError(w, InternalServerErrorMsg, 500)
225			return
226		}
227	} else if r.Method == "POST" {
228	}
229}
230
231// Server a user's file
232func userFile(w http.ResponseWriter, r *http.Request) {
233	userName := strings.Split(r.Host, ".")[0]
234	fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path))
235	extension := path.Ext(fileName)
236	if r.URL.Path == "/static/style.css" {
237		http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))
238	}
239	if extension == ".gmi" || extension == ".gemini" {
240		// covert to html
241		stat, _ := os.Stat(fileName)
242		file, _ := os.Open(fileName)
243		htmlString := gmi.Parse(file).HTML()
244		reader := strings.NewReader(htmlString)
245		w.Header().Set("Content-Type", "text/html")
246		http.ServeContent(w, r, fileName, stat.ModTime(), reader)
247	} else {
248		http.ServeFile(w, r, fileName)
249	}
250}
251
252func runHTTPServer() {
253	log.Println("Running http server")
254	var err error
255	t, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.html"))
256	if err != nil {
257		log.Fatal(err)
258	}
259	serveMux := http.NewServeMux()
260
261	serveMux.HandleFunc(c.RootDomain+"/", rootHandler)
262	serveMux.HandleFunc(c.RootDomain+"/my_site", mySiteHandler)
263	serveMux.HandleFunc(c.RootDomain+"/edit/", editFileHandler)
264	serveMux.HandleFunc(c.RootDomain+"/upload", uploadFilesHandler)
265	serveMux.HandleFunc(c.RootDomain+"/login", loginHandler)
266	serveMux.HandleFunc(c.RootDomain+"/register", registerHandler)
267	serveMux.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler)
268
269	// TODO rate limit login https://github.com/ulule/limiter
270
271	wrapped := handlers.LoggingHandler(os.Stdout, serveMux)
272
273	// handle user files based on subdomain
274	serveMux.HandleFunc("/", userFile)
275	// login+register functions
276	srv := &http.Server{
277		ReadTimeout:  5 * time.Second,
278		WriteTimeout: 10 * time.Second,
279		IdleTimeout:  120 * time.Second,
280		Addr:         ":8080",
281		// TLSConfig:    tlsConfig,
282		Handler: wrapped,
283	}
284	log.Fatal(srv.ListenAndServe())
285}