all repos — flounder @ 803bb266d991ee8ddb1b7d977c7caa5bb361d399

A small site builder for the Gemini protocol

http.go (view raw)

  1package main
  2
  3import (
  4	"git.sr.ht/~adnano/gmi"
  5	"html/template"
  6	"log"
  7	"net/http"
  8	"os"
  9	"path"
 10	"strings"
 11)
 12
 13var t *template.Template
 14
 15// TODO somewhat better error handling
 16const InternalServerErrorMsg = "500: Internal Server Error"
 17
 18func renderError(w http.ResponseWriter, errorMsg string, statusCode int) { // TODO think about pointers
 19	data := struct{ ErrorMsg string }{errorMsg}
 20	err := t.ExecuteTemplate(w, "error.html", data)
 21	if err != nil { // shouldn't happen probably
 22		http.Error(w, errorMsg, statusCode)
 23	}
 24}
 25
 26func indexHandler(w http.ResponseWriter, r *http.Request) {
 27	// serve everything inside static directory
 28	if r.URL.Path != "/" {
 29		fileName := path.Join(c.TemplatesDirectory, "static", r.URL.Path)
 30		http.ServeFile(w, r, fileName)
 31		return
 32	}
 33	indexFiles, err := getIndexFiles()
 34	if err != nil {
 35		log.Println(err)
 36		renderError(w, InternalServerErrorMsg, 500)
 37		return
 38	}
 39	allUsers, err := getUsers()
 40	if err != nil {
 41		log.Println(err)
 42		renderError(w, InternalServerErrorMsg, 500)
 43		return
 44	}
 45	data := struct {
 46		Domain    string
 47		PageTitle string
 48		Files     []*File
 49		Users     []string
 50	}{c.RootDomain, c.SiteTitle, indexFiles, allUsers}
 51	err = t.ExecuteTemplate(w, "index.html", data)
 52	if err != nil {
 53		log.Println(err)
 54		renderError(w, InternalServerErrorMsg, 500)
 55		return
 56	}
 57
 58}
 59
 60func editFileHandler(w http.ResponseWriter, r *http.Request) {
 61	// read file content. create if dne
 62	// authUser := "alex"
 63	if r.Method == "GET" {
 64		data := struct {
 65			FileName  string
 66			FileText  string
 67			PageTitle string
 68		}{"filename", "filetext", c.SiteTitle}
 69		err := t.ExecuteTemplate(w, "edit_file.html", data)
 70		if err != nil {
 71			log.Println(err)
 72			renderError(w, InternalServerErrorMsg, 500)
 73			return
 74		}
 75	} else if r.Method == "POST" {
 76	}
 77}
 78
 79func deleteFileHandler(w http.ResponseWriter, r *http.Request) {
 80	if r.Method == "POST" {
 81	}
 82}
 83
 84func mySiteHandler(w http.ResponseWriter, r *http.Request) {
 85	authUser := "alex"
 86	// check auth
 87	files, _ := getUserFiles(authUser)
 88	data := struct {
 89		Domain    string
 90		PageTitle string
 91		AuthUser  string
 92		Files     []*File
 93	}{c.RootDomain, c.SiteTitle, authUser, files}
 94	_ = t.ExecuteTemplate(w, "my_site.html", data)
 95}
 96
 97func loginHandler(w http.ResponseWriter, r *http.Request) {
 98	if r.Method == "GET" {
 99		// show page
100		data := struct {
101			Error     string
102			PageTitle string
103		}{"", "Login"}
104		err := t.ExecuteTemplate(w, "login.html", data)
105		if err != nil {
106			log.Println(err)
107			renderError(w, InternalServerErrorMsg, 500)
108			return
109		}
110	} else if r.Method == "POST" {
111		r.ParseForm()
112		name := r.Form.Get("username")
113		password := r.Form.Get("password")
114		err := checkAuth(name, password)
115		if err == nil {
116			log.Println("logged in")
117			// redirect home
118		} else {
119			data := struct {
120				Error     string
121				PageTitle string
122			}{"Invalid login or password", c.SiteTitle}
123			err := t.ExecuteTemplate(w, "login.html", data)
124			if err != nil {
125				log.Println(err)
126				renderError(w, InternalServerErrorMsg, 500)
127				return
128			}
129		}
130		// create session
131		// redirect home
132		// verify login
133		// check for errors
134	}
135}
136
137func registerHandler(w http.ResponseWriter, r *http.Request) {
138	if r.Method == "GET" {
139		data := struct {
140			Domain    string
141			Errors    []string
142			PageTitle string
143		}{c.RootDomain, nil, "Register"}
144		err := t.ExecuteTemplate(w, "register.html", data)
145		if err != nil {
146			log.Println(err)
147			renderError(w, InternalServerErrorMsg, 500)
148			return
149		}
150	} else if r.Method == "POST" {
151	}
152}
153
154// Server a user's file
155func userFile(w http.ResponseWriter, r *http.Request) {
156	userName := strings.Split(r.Host, ".")[0]
157	fileName := path.Join(c.FilesDirectory, userName, r.URL.Path)
158	extension := path.Ext(fileName)
159	if r.URL.Path == "/static/style.css" {
160		http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))
161	}
162	if extension == ".gmi" || extension == ".gemini" {
163		if strings.Contains(fileName, "..") {
164			// prevent directory traversal TODO verify
165			http.Error(w, "invalid URL path", http.StatusBadRequest)
166		} else {
167			// covert to html
168			stat, _ := os.Stat(fileName)
169			file, _ := os.Open(fileName)
170			htmlString := gmi.Parse(file).HTML()
171			reader := strings.NewReader(htmlString)
172			w.Header().Set("Content-Type", "text/html")
173			http.ServeContent(w, r, fileName, stat.ModTime(), reader)
174		}
175		// TODO clean
176	} else {
177		http.ServeFile(w, r, fileName)
178	}
179}
180
181func runHTTPServer() {
182	log.Println("Running http server")
183	var err error
184	t, err = template.ParseGlob("./templates/*.html") // TODO make template dir configruable
185	if err != nil {
186		log.Fatal(err)
187	}
188	http.HandleFunc(c.RootDomain+"/", indexHandler)
189	http.HandleFunc(c.RootDomain+"/my_site", mySiteHandler)
190	http.HandleFunc(c.RootDomain+"/edit/", editFileHandler)
191	http.HandleFunc(c.RootDomain+"/login", loginHandler)
192	http.HandleFunc(c.RootDomain+"/register", registerHandler)
193	http.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler)
194	// login+register functions
195
196	// handle user files based on subdomain
197	http.HandleFunc("/", userFile)
198	log.Fatal(http.ListenAndServe(":8080", logRequest(http.DefaultServeMux)))
199}
200
201func logRequest(handler http.Handler) http.Handler {
202	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
203		log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
204		handler.ServeHTTP(w, r)
205	})
206}