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