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