💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › 18238f9c65f6a1111aeb514504… captured on 2022-07-16 at 17:09:37. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 package main
1
2 import (
3 "fmt"
4 "io"
5 "log"
6 "os"
7 "strconv"
8 "strings"
9
10 "gemigit/auth"
11 "gemigit/config"
12 "gemigit/db"
13 "gemigit/httpgit"
14 "gemigit/repo"
15
16 "github.com/gabriel-vasile/mimetype"
17 "github.com/pitr/gig"
18 )
19
20 func main() {
21
22 if err := config.LoadConfig(); err != nil {
23 log.Fatalln(err.Error())
24 }
25
26 if len(os.Args) > 1 {
27 if os.Args[1] == "chpasswd" {
28 if len(os.Args) < 4 {
29 fmt.Println(os.Args[0] + " chpasswd <username> <new password>")
30 return
31 }
32 err := db.Init(config.Cfg.Gemigit.Database)
33 if err != nil {
34 log.Fatalln(err.Error())
35 }
36 defer db.Close()
37 if err := db.ChangePassword(os.Args[2], os.Args[3]); err != nil {
38 fmt.Println(err.Error())
39 return
40 }
41 fmt.Println(os.Args[2] + "'s password changed")
42 return
43 }
44 }
45
46 log.SetFlags(log.LstdFlags | log.Lshortfile)
47
48 err := db.Init(config.Cfg.Gemigit.Database)
49 if err != nil {
50 log.Fatalln(err.Error())
51 }
52 defer db.Close()
53 if err := repo.Init("repos"); err != nil {
54 log.Fatalln(err.Error())
55 }
56
57 go httpgit.Listen("repos/", config.Cfg.Gemigit.Port)
58 go auth.Decrease()
59
60 gig.DefaultLoggerConfig.Format = "${time_rfc3339} - ${remote_ip} | Path=${path}, Status=${status}, Latency=${latency}\n"
61 g := gig.Default()
62 g.Use(gig.Recover())
63
64 secure := g.Group("/account", gig.PassAuth(func(sig string, c gig.Context) (string, error) {
65 _, b := db.GetUser(sig)
66 if !b {
67 return "/login", nil
68 }
69 return "", nil
70 }))
71 {
72 secure.Handle("", func(c gig.Context) error {
73 user, exist := db.GetUser(c.CertHash())
74 if !exist {
75 return c.NoContent(gig.StatusBadRequest, "Invalid username")
76 }
77 ret := "=>/ Main page\n\n"
78 ret += "# Account : " + user.Name + "\n"
79 err := user.UpdateDescription()
80 if err != nil {
81 return c.NoContent(gig.StatusBadRequest, err.Error())
82 }
83 if user.Description != "" {
84 ret += user.Description + "\n\n"
85 } else {
86 ret += "\n"
87 }
88 ret += "=>/account/addrepo Create a new repository\n"
89 ret += "=>/account/chdesc Change your account description\n"
90 ret += "=>/account/chpasswd Change your password\n"
91 ret += "=>/account/disconnect Disconnect\n"
92 ret += "\n## Repositories list\n\n"
93
94 repos, err := user.GetRepos(false)
95 if err != nil {
96 ret += "Failed to load user's repositories\n"
97 log.Println(err)
98 } else {
99 for _, repo := range repos {
100 ret += "=>/account/repo/" + repo.Name + " " + repo.Name + "\n"
101 }
102 }
103
104 return c.Gemini(ret)
105 })
106
107 secure.Handle("/repo/:repo/files", func(c gig.Context) error {
108 user, exist := db.GetUser(c.CertHash())
109 if !exist {
110 return c.NoContent(gig.StatusBadRequest, "Invalid username")
111 }
112 query, err := c.QueryString()
113 if err != nil {
114 return c.NoContent(gig.StatusBadRequest, err.Error())
115 }
116 if query != "" {
117 repofile, err := repo.GetFile(c.Param("repo"), user.Name, query)
118 if err != nil {
119 return c.NoContent(gig.StatusBadRequest, err.Error())
120 }
121 contents, err := repofile.Contents()
122 if err != nil {
123 return c.NoContent(gig.StatusBadRequest, err.Error())
124 }
125 return c.Gemini(contents)
126 }
127 return repoRequest(c, "files", true)
128 })
129
130 secure.Handle("/repo/:repo/files/:blob", func(c gig.Context) error {
131 user, exist := db.GetUser(c.CertHash())
132 if !exist {
133 return c.NoContent(gig.StatusBadRequest, "Invalid username")
134 }
135 content, err := repo.GetPrivateFile(c.Param("repo"), user.Name, c.Param("blob"), c.CertHash())
136 if err != nil {
137 return c.NoContent(gig.StatusBadRequest, err.Error())
138 }
139 lines := strings.Split(content, "\n")
140 file := ""
141 for i, line := range lines {
142 file += strconv.Itoa(i) + " \t" + line + "\n"
143 }
144 return c.Gemini(file)
145 })
146
147 secure.Handle("/repo/:repo/license", func(c gig.Context) error {
148 return repoRequest(c, "license", true)
149 })
150
151 secure.Handle("/repo/:repo/readme", func(c gig.Context) error {
152 return repoRequest(c, "readme", true)
153 })
154
155 secure.Handle("/repo/:repo/*", func(c gig.Context) error {
156 user, exist := db.GetUser(c.CertHash())
157 if !exist {
158 return c.NoContent(gig.StatusBadRequest, "Invalid username")
159 }
160 repofile, err := repo.GetFile(c.Param("repo"), user.Name, c.Param("*"))
161 if err != nil {
162 return c.NoContent(gig.StatusBadRequest, err.Error())
163 }
164 reader, err := repofile.Reader()
165 if err != nil {
166 return c.NoContent(gig.StatusBadRequest, err.Error())
167 }
168 buf, err := io.ReadAll(reader)
169 if err != nil {
170 return c.NoContent(gig.StatusBadRequest, err.Error())
171 }
172 mtype := mimetype.Detect(buf)
173 return c.Blob(mtype.String(), buf)
174 })
175
176 secure.Handle("/repo/:repo", func(c gig.Context) error {
177 return repoRequest(c, "", true)
178 })
179
180 secure.Handle("/repo/:repo/togglepublic", func(c gig.Context) error {
181 user, exist := db.GetUser(c.CertHash())
182 if !exist {
183 return c.NoContent(gig.StatusBadRequest, "Invalid username")
184 }
185 if err := user.TogglePublic(c.Param("repo"), c.CertHash()); err != nil {
186 return c.NoContent(gig.StatusBadRequest, err.Error())
187 }
188 return c.NoContent(gig.StatusRedirectTemporary, "/account/repo/"+c.Param("repo"))
189 })
190
191 secure.Handle("/repo/:repo/chname", func(c gig.Context) error {
192 newname, err := c.QueryString()
193 if err != nil {
194 return c.NoContent(gig.StatusBadRequest, "Invalid input received")
195 }
196 if newname == "" {
197 return c.NoContent(gig.StatusInput, "New repository name")
198 }
199 user, exist := db.GetUser(c.CertHash())
200 if !exist {
201 return c.NoContent(gig.StatusBadRequest, "Invalid username")
202 }
203 if err := user.ChangeRepoName(c.Param("repo"), newname, c.CertHash()); err != nil {
204 return c.NoContent(gig.StatusBadRequest, err.Error())
205 }
206 if err := repo.ChangeRepoDir(c.Param("repo"), user.Name, newname); err != nil {
207 return c.NoContent(gig.StatusBadRequest, err.Error())
208 }
209 return c.NoContent(gig.StatusRedirectTemporary, "/account/repo/"+newname)
210 })
211
212 secure.Handle("/repo/:repo/chdesc", func(c gig.Context) error {
213 newdesc, err := c.QueryString()
214 if err != nil {
215 return c.NoContent(gig.StatusBadRequest, "Invalid input received")
216 }
217 if newdesc == "" {
218 return c.NoContent(gig.StatusInput, "New repository description")
219 }
220 user, exist := db.GetUser(c.CertHash())
221 if !exist {
222 return c.NoContent(gig.StatusBadRequest, "Invalid username")
223 }
224 if err := user.ChangeRepoDesc(c.Param("repo"), newdesc); err != nil {
225 return c.NoContent(gig.StatusBadRequest, err.Error())
226 }
227 return c.NoContent(gig.StatusRedirectTemporary, "/account/repo/"+c.Param("repo"))
228 })
229
230 secure.Handle("/chdesc", func(c gig.Context) error {
231 newdesc, err := c.QueryString()
232 if err != nil {
233 return c.NoContent(gig.StatusBadRequest, "Invalid input received")
234 }
235 if newdesc == "" {
236 return c.NoContent(gig.StatusInput, "New account description")
237 }
238 user, exist := db.GetUser(c.CertHash())
239 if !exist {
240 return c.NoContent(gig.StatusBadRequest, "Invalid username")
241 }
242 if err := user.ChangeDescription(newdesc, c.CertHash()); err != nil {
243 return c.NoContent(gig.StatusBadRequest, err.Error())
244 }
245 return c.NoContent(gig.StatusRedirectTemporary, "/account")
246 })
247
248 secure.Handle("/addrepo", func(c gig.Context) error {
249
250 name, err := c.QueryString()
251 if err != nil {
252 return c.NoContent(gig.StatusBadRequest, err.Error())
253 }
254 if name != "" {
255 user, b := db.GetUser(c.CertHash())
256 if b {
257 if err := user.CreateRepo(name, c.CertHash()); err != nil {
258 return c.NoContent(gig.StatusBadRequest, err.Error())
259 }
260 if err := repo.InitRepo(name, user.Name); err != nil {
261 return c.NoContent(gig.StatusBadRequest, err.Error())
262 }
263 return c.NoContent(gig.StatusRedirectTemporary, "/account/repo/"+name)
264 }
265 return c.NoContent(gig.StatusBadRequest, "Cannot find username")
266 }
267
268 return c.NoContent(gig.StatusInput, "Repository name")
269 })
270
271 secure.Handle("/repo/:repo/delrepo", func(c gig.Context) error {
272
273 name, err := c.QueryString()
274 if err != nil {
275 return c.NoContent(gig.StatusBadRequest, "Invalid input received")
276 }
277 if name != "" {
278 if name != c.Param("repo") {
279 return c.NoContent(gig.StatusRedirectTemporary, "/account/repo/"+c.Param("repo"))
280 }
281 user, b := db.GetUser(c.CertHash())
282 if b {
283 if err := user.DeleteRepo(name, c.CertHash()); err != nil {
284 return c.NoContent(gig.StatusBadRequest, err.Error())
285 }
286 if err := repo.RemoveRepo(name, user.Name); err != nil {
287 return c.NoContent(gig.StatusBadRequest, err.Error())
288 }
289 return c.NoContent(gig.StatusRedirectTemporary, "/account")
290 }
291 return c.NoContent(gig.StatusBadRequest, "Cannot find username")
292 }
293
294 return c.NoContent(gig.StatusInput, "Type the repository name")
295 })
296
297 secure.Handle("/chpasswd", func(c gig.Context) error {
298 passwd, err := c.QueryString()
299 if err != nil {
300 return c.NoContent(gig.StatusBadRequest, "Invalid input received")
301 }
302 if passwd != "" {
303
304 user, b := db.GetUser(c.CertHash())
305 if b {
306 err := user.ChangePassword(passwd, c.CertHash())
307 if err != nil {
308 return c.NoContent(gig.StatusBadRequest, err.Error())
309 }
310 return c.NoContent(gig.StatusRedirectTemporary, "/account")
311 }
312 return c.NoContent(gig.StatusBadRequest, "Cannot find username")
313 }
314 return c.NoContent(gig.StatusSensitiveInput, "New password")
315 })
316
317 secure.Handle("/disconnect", func(c gig.Context) error {
318 user, exist := db.GetUser(c.CertHash())
319 if !exist {
320 return c.NoContent(gig.StatusBadRequest, "Invalid username")
321 }
322 if err := user.Disconnect(c.CertHash()); err != nil {
323 return c.NoContent(gig.StatusBadRequest, err.Error())
324 }
325
326 return c.NoContent(gig.StatusRedirectTemporary, "/")
327 })
328 }
329
330 public := g.Group("/repo")
331 {
332 public.Handle("", func(c gig.Context) error {
333 ret := "=>/ Go back\n\n"
334 ret += "# Public repositories\n\n"
335 repos, err := db.GetPublicRepo()
336 if err != nil {
337 log.Println(err.Error())
338 return c.NoContent(gig.StatusTemporaryFailure, "Internal error, "+err.Error())
339 }
340 for _, repo := range repos {
341 ret += "=> /repo/" + repo.Username + "/" + repo.Name + " " + repo.Name + " by " + repo.Username + "\n"
342 if repo.Description != "" {
343 ret += "> " + repo.Description + "\n"
344 }
345 }
346 return c.Gemini(ret)
347 })
348
349 public.Handle("/:user", func(c gig.Context) error {
350 ret := "=>/repo Go back\n\n# " + c.Param("user") + "\n\n"
351 user, err := db.GetPublicUser(c.Param("user"))
352 if err != nil {
353 return c.NoContent(gig.StatusBadRequest, err.Error())
354 }
355 if user.Description != "" {
356 ret += user.Description + "\n\n"
357 }
358 ret += "## Repositories\n"
359 repos, err := user.GetRepos(true)
360 if err != nil {
361 return c.NoContent(gig.StatusTemporaryFailure, "Invalid account, "+err.Error())
362 }
363 for _, repo := range repos {
364 ret += "=> /repo/" + repo.Username + "/" + repo.Name + " " + repo.Name + "\n"
365 }
366 return c.Gemini(ret)
367 })
368
369 public.Handle("/:user/:repo/files", func(c gig.Context) error {
370 return repoRequest(c, "files", false)
371 })
372
373 public.Handle("/:user/:repo/files/:blob", func(c gig.Context) error {
374 content, err := repo.GetPublicFile(c.Param("repo"), c.Param("user"), c.Param("blob"))
375 if err != nil {
376 return c.NoContent(gig.StatusBadRequest, err.Error())
377 }
378 lines := strings.Split(content, "\n")
379 file := ""
380 for i, line := range lines {
381 file += strconv.Itoa(i) + " \t" + line + "\n"
382 }
383 return c.Gemini(file)
384 })
385
386 public.Handle("/:user/:repo/license", func(c gig.Context) error {
387 return repoRequest(c, "license", false)
388 })
389
390 public.Handle("/:user/:repo/readme", func(c gig.Context) error {
391 return repoRequest(c, "readme", false)
392 })
393
394 public.Handle("/:user/:repo", func(c gig.Context) error {
395 return repoRequest(c, "", false)
396 })
397
398 public.Handle("/:user/:repo/*", func(c gig.Context) error {
399 repofile, err := repo.GetFile(c.Param("repo"), c.Param("user"), c.Param("*"))
400 if err != nil {
401 return c.NoContent(gig.StatusBadRequest, err.Error())
402 }
403 reader, err := repofile.Reader()
404 if err != nil {
405 return c.NoContent(gig.StatusBadRequest, err.Error())
406 }
407 buf, err := io.ReadAll(reader)
408 if err != nil {
409 return c.NoContent(gig.StatusBadRequest, err.Error())
410 }
411 mtype := mimetype.Detect(buf)
412 return c.Blob(mtype.String(), buf)
413 })
414 }
415
416 g.PassAuthLoginHandle("/login", func(user, pass, sig string, c gig.Context) (string, error) {
417 err := auth.Connect(user, pass, sig, c.IP())
418 if err != nil {
419 return "", err
420 }
421 return "/account", nil
422 })
423
424 if config.Cfg.Gemigit.AllowRegistration {
425 g.Handle("/register", func(c gig.Context) error {
426 cert := c.Certificate()
427 if cert == nil {
428 return c.NoContent(gig.StatusClientCertificateRequired, "Certificate required")
429 }
430
431 name, err := c.QueryString()
432 if err != nil {
433 return c.NoContent(gig.StatusBadRequest, "Invalid name received")
434 }
435 if name != "" {
436 return c.NoContent(gig.StatusRedirectPermanent, "/register/"+name)
437 }
438
439 return c.NoContent(gig.StatusInput, "Username")
440 })
441
442 g.Handle("/register/:name", func(c gig.Context) error {
443 cert := c.Certificate()
444 if cert == nil {
445 return c.NoContent(gig.StatusClientCertificateRequired, "Certificate required")
446 }
447
448 password, err := c.QueryString()
449 if err != nil {
450 return c.NoContent(gig.StatusBadRequest, "Invalid password received")
451 }
452 if password != "" {
453 if err = db.Register(c.Param("name"), password); err != nil {
454 return c.NoContent(gig.StatusBadRequest, err.Error())
455 }
456 return c.Gemini("# Your registration was completed successfully\n=> /login Login now")
457 }
458
459 return c.NoContent(gig.StatusSensitiveInput, "Password")
460 })
461 }
462
463 g.Handle("/", func(c gig.Context) error {
464 _, connected := db.GetUser(c.CertHash())
465 ret := ""
466 if !connected {
467 ret = "# " + config.Cfg.Gemigit.Name + "\n\n"
468 ret += "=> /login Login\n"
469 if config.Cfg.Gemigit.AllowRegistration {
470 ret += "=> /register Register\n"
471 }
472 } else {
473 ret = "# " + config.Cfg.Gemigit.Name + "\n=> /account Account page\n"
474 }
475 ret += "=> /repo Public repositories"
476 return c.Gemini(ret)
477 })
478
479 err = g.Run("cert.pem", "key.pem")
480 if err != nil {
481 log.Fatal(err.Error())
482 }
483 }
484