all repos — flounder @ 56777472e76955038796df889db7e26fed227dcb

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		defer file.Close()
120		if err != nil {
121			log.Println(err)
122			renderError(w, err.Error(), 400)
123			return
124		}
125		var dest []byte
126		file.Read(dest)
127		log.Println("asdfadf")
128		err = checkIfValidFile(fileHeader.Filename, dest)
129		if err != nil {
130			log.Println(err)
131			renderError(w, err.Error(), 400)
132			return
133		}
134		destPath := path.Join(c.FilesDirectory, authUser, fileHeader.Filename)
135
136		f, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE, 0644)
137		if err != nil {
138			log.Println(err)
139			renderError(w, InternalServerErrorMsg, 500)
140			return
141		}
142		defer f.Close()
143		io.Copy(f, file)
144	}
145	http.Redirect(w, r, "/my_site", 302)
146}
147
148func deleteFileHandler(w http.ResponseWriter, r *http.Request) {
149	authUser := "alex"
150	fileName := filepath.Clean(r.URL.Path[len("/delete/"):])
151	filePath := path.Join(c.FilesDirectory, authUser, fileName)
152	if r.Method == "POST" {
153		os.Remove(filePath) // suppress error
154	}
155	http.Redirect(w, r, "/my_site", 302)
156}
157
158func mySiteHandler(w http.ResponseWriter, r *http.Request) {
159	authUser := "alex"
160	// check auth
161	files, _ := getUserFiles(authUser)
162	data := struct {
163		Domain    string
164		PageTitle string
165		AuthUser  string
166		Files     []*File
167	}{c.RootDomain, c.SiteTitle, authUser, files}
168	_ = t.ExecuteTemplate(w, "my_site.html", data)
169}
170
171func loginHandler(w http.ResponseWriter, r *http.Request) {
172	if r.Method == "GET" {
173		// show page
174		data := struct {
175			Error     string
176			PageTitle string
177		}{"", "Login"}
178		err := t.ExecuteTemplate(w, "login.html", data)
179		if err != nil {
180			log.Println(err)
181			renderError(w, InternalServerErrorMsg, 500)
182			return
183		}
184	} else if r.Method == "POST" {
185		r.ParseForm()
186		name := r.Form.Get("username")
187		password := r.Form.Get("password")
188		err := checkAuth(name, password)
189		if err == nil {
190			log.Println("logged in")
191			// redirect home
192		} else {
193			data := struct {
194				Error     string
195				PageTitle string
196			}{"Invalid login or password", c.SiteTitle}
197			err := t.ExecuteTemplate(w, "login.html", data)
198			if err != nil {
199				log.Println(err)
200				renderError(w, InternalServerErrorMsg, 500)
201				return
202			}
203		}
204		// create session
205		// redirect home
206		// verify login
207		// check for errors
208	}
209}
210
211func registerHandler(w http.ResponseWriter, r *http.Request) {
212	if r.Method == "GET" {
213		data := struct {
214			Domain    string
215			Errors    []string
216			PageTitle string
217		}{c.RootDomain, nil, "Register"}
218		err := t.ExecuteTemplate(w, "register.html", data)
219		if err != nil {
220			log.Println(err)
221			renderError(w, InternalServerErrorMsg, 500)
222			return
223		}
224	} else if r.Method == "POST" {
225	}
226}
227
228// Server a user's file
229func userFile(w http.ResponseWriter, r *http.Request) {
230	userName := strings.Split(r.Host, ".")[0]
231	fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path))
232	extension := path.Ext(fileName)
233	if r.URL.Path == "/static/style.css" {
234		http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))
235	}
236	if extension == ".gmi" || extension == ".gemini" {
237		// covert to html
238		stat, _ := os.Stat(fileName)
239		file, _ := os.Open(fileName)
240		htmlString := gmi.Parse(file).HTML()
241		reader := strings.NewReader(htmlString)
242		w.Header().Set("Content-Type", "text/html")
243		http.ServeContent(w, r, fileName, stat.ModTime(), reader)
244	} else {
245		http.ServeFile(w, r, fileName)
246	}
247}
248
249func runHTTPServer() {
250	log.Println("Running http server")
251	var err error
252	t, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.html"))
253	if err != nil {
254		log.Fatal(err)
255	}
256	serveMux := http.NewServeMux()
257
258	serveMux.HandleFunc(c.RootDomain+"/", rootHandler)
259	serveMux.HandleFunc(c.RootDomain+"/my_site", mySiteHandler)
260	serveMux.HandleFunc(c.RootDomain+"/edit/", editFileHandler)
261	serveMux.HandleFunc(c.RootDomain+"/upload", uploadFilesHandler)
262	serveMux.HandleFunc(c.RootDomain+"/login", loginHandler)
263	serveMux.HandleFunc(c.RootDomain+"/register", registerHandler)
264	serveMux.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler)
265
266	// TODO rate limit login https://github.com/ulule/limiter
267
268	wrapped := handlers.LoggingHandler(os.Stdout, serveMux)
269
270	// handle user files based on subdomain
271	serveMux.HandleFunc("/", userFile)
272	// login+register functions
273	srv := &http.Server{
274		ReadTimeout:  5 * time.Second,
275		WriteTimeout: 10 * time.Second,
276		IdleTimeout:  120 * time.Second,
277		Addr:         ":8080",
278		// TLSConfig:    tlsConfig,
279		Handler: wrapped,
280	}
281	log.Fatal(srv.ListenAndServe())
282}