all repos — flounder @ 15ec4e13737ab222e225c5f93818bc51ce2cb9f2

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