all repos — flounder @ 7068552cb491131c4b58a79149ac457333881ac5

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
 19// TODO somewhat better error handling
 20const InternalServerErrorMsg = "500: Internal Server Error"
 21
 22func renderError(w http.ResponseWriter, errorMsg string, statusCode int) { // TODO think about pointers
 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		f, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
 70		defer f.Close()
 71		fileBytes, err := ioutil.ReadAll(f)
 72		if err != nil {
 73			log.Println(err)
 74			renderError(w, InternalServerErrorMsg, 500)
 75			return
 76		}
 77		data := struct {
 78			FileName  string
 79			FileText  string
 80			PageTitle string
 81		}{fileName, string(fileBytes), c.SiteTitle}
 82		err = t.ExecuteTemplate(w, "edit_file.html", data)
 83		if err != nil {
 84			log.Println(err)
 85			renderError(w, InternalServerErrorMsg, 500)
 86			return
 87		}
 88	} else if r.Method == "POST" {
 89		// get post body
 90		r.ParseForm()
 91		fileText := r.Form.Get("file_text")
 92		err := ioutil.WriteFile(filePath, []byte(fileText), 0644)
 93		if err != nil {
 94			log.Println(err)
 95			renderError(w, InternalServerErrorMsg, 500)
 96			return
 97		}
 98		http.Redirect(w, r, "/my_site", 302)
 99	}
100}
101
102func deleteFileHandler(w http.ResponseWriter, r *http.Request) {
103	authUser := "alex"
104	fileName := filepath.Clean(r.URL.Path[len("/delete/"):])
105	filePath := path.Join(c.FilesDirectory, authUser, fileName)
106	if r.Method == "POST" {
107		os.Remove(filePath)
108		http.Redirect(w, r, "/my_site", 302)
109	}
110}
111
112func mySiteHandler(w http.ResponseWriter, r *http.Request) {
113	authUser := "alex"
114	// check auth
115	files, _ := getUserFiles(authUser)
116	data := struct {
117		Domain    string
118		PageTitle string
119		AuthUser  string
120		Files     []*File
121	}{c.RootDomain, c.SiteTitle, authUser, files}
122	_ = t.ExecuteTemplate(w, "my_site.html", data)
123}
124
125func loginHandler(w http.ResponseWriter, r *http.Request) {
126	if r.Method == "GET" {
127		// show page
128		data := struct {
129			Error     string
130			PageTitle string
131		}{"", "Login"}
132		err := t.ExecuteTemplate(w, "login.html", data)
133		if err != nil {
134			log.Println(err)
135			renderError(w, InternalServerErrorMsg, 500)
136			return
137		}
138	} else if r.Method == "POST" {
139		r.ParseForm()
140		name := r.Form.Get("username")
141		password := r.Form.Get("password")
142		err := checkAuth(name, password)
143		if err == nil {
144			log.Println("logged in")
145			// redirect home
146		} else {
147			data := struct {
148				Error     string
149				PageTitle string
150			}{"Invalid login or password", c.SiteTitle}
151			err := t.ExecuteTemplate(w, "login.html", data)
152			if err != nil {
153				log.Println(err)
154				renderError(w, InternalServerErrorMsg, 500)
155				return
156			}
157		}
158		// create session
159		// redirect home
160		// verify login
161		// check for errors
162	}
163}
164
165func registerHandler(w http.ResponseWriter, r *http.Request) {
166	if r.Method == "GET" {
167		data := struct {
168			Domain    string
169			Errors    []string
170			PageTitle string
171		}{c.RootDomain, nil, "Register"}
172		err := t.ExecuteTemplate(w, "register.html", data)
173		if err != nil {
174			log.Println(err)
175			renderError(w, InternalServerErrorMsg, 500)
176			return
177		}
178	} else if r.Method == "POST" {
179	}
180}
181
182// Server a user's file
183func userFile(w http.ResponseWriter, r *http.Request) {
184	userName := strings.Split(r.Host, ".")[0]
185	fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path))
186	extension := path.Ext(fileName)
187	if r.URL.Path == "/static/style.css" {
188		http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))
189	}
190	if extension == ".gmi" || extension == ".gemini" {
191		// covert to html
192		stat, _ := os.Stat(fileName)
193		file, _ := os.Open(fileName)
194		htmlString := gmi.Parse(file).HTML()
195		reader := strings.NewReader(htmlString)
196		w.Header().Set("Content-Type", "text/html")
197		http.ServeContent(w, r, fileName, stat.ModTime(), reader)
198	} else {
199		http.ServeFile(w, r, fileName)
200	}
201}
202
203func runHTTPServer() {
204	log.Println("Running http server")
205	var err error
206	t, err = template.ParseGlob("./templates/*.html") // TODO make template dir configruable
207	if err != nil {
208		log.Fatal(err)
209	}
210	serveMux := http.NewServeMux()
211
212	serveMux.HandleFunc(c.RootDomain+"/", rootHandler)
213	serveMux.HandleFunc(c.RootDomain+"/my_site", mySiteHandler)
214	serveMux.HandleFunc(c.RootDomain+"/edit/", editFileHandler)
215	// serveMux.HandleFunc(c.RootDomain+"/upload/", uploadFilesHandler)
216	serveMux.HandleFunc(c.RootDomain+"/login", loginHandler)
217	serveMux.HandleFunc(c.RootDomain+"/register", registerHandler)
218	serveMux.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler)
219
220	// TODO rate limit login https://github.com/ulule/limiter
221
222	wrapped := handlers.LoggingHandler(os.Stdout, serveMux)
223
224	// handle user files based on subdomain
225	serveMux.HandleFunc("/", userFile)
226	// login+register functions
227	srv := &http.Server{
228		ReadTimeout:  5 * time.Second,
229		WriteTimeout: 10 * time.Second,
230		IdleTimeout:  120 * time.Second,
231		Addr:         ":8080",
232		// TLSConfig:    tlsConfig,
233		Handler: wrapped,
234	}
235	log.Fatal(srv.ListenAndServe())
236}