all repos — flounder @ 4b85797053e9433b25d6961b53092975a66c6a45

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