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