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}