0 package gmi

1

2 import (

3 "bytes"

4 "errors"

5 "fmt"

6 "gemigit/access"

7 "gemigit/config"

8 "gemigit/db"

9 "gemigit/repo"

10 io "gemigit/util"

11 "log"

12 "strconv"

13 "strings"

14 "text/template"

15

16 "github.com/go-git/go-git/v5/plumbing"

17 "github.com/go-git/go-git/v5/plumbing/object"

18 "github.com/go-git/go-git/v5/plumbing/transport"

19 "github.com/pitr/gig"

20 )

21

22 func execT(c gig.Context, template string, data interface{}) error {

23 t := templates.Lookup(template)

24 var b bytes.Buffer

25 err := t.Execute(&b, data)

26 if err != nil {

27 log.Println(err.Error())

28 return c.NoContent(gig.StatusTemporaryFailure, err.Error())

29 }

30 return c.Gemini(b.String())

31 }

32

33 func execTemplate(template string, data interface{}) (string, error) {

34 t := templates.Lookup(template)

35 var b bytes.Buffer

36 err := t.Execute(&b, data)

37 if err != nil {

38 log.Println(err.Error())

39 return "", err

40 }

41 return strings.TrimRight(b.String(), "\n"), nil

42 }

43

44 const (

45 pageLog = iota

46 pageFiles

47 pageRefs

48 pageLicense

49 pageReadme

50 )

51

52 var templates *template.Template

53

54 func LoadTemplate(dir string) error {

55 var err error

56

57 dirlen := len(dir)

58 if dirlen > 1 && dir[dirlen - 1] == '/' {

59 dir = dir[:dirlen - 1]

60 }

61

62 templates = template.New("gmi")

63 template.Must(templates.Funcs(template.FuncMap {

64 "AccessFirst": accessFirstOption,

65 "AccessSecond": accessSecondOption,

66 "AccessPrivilege": privilegeToString,

67 }).ParseFiles(

68 dir + "/index.gmi",

69 dir + "/account.gmi",

70 dir + "/repo.gmi",

71 dir + "/repo_log.gmi",

72 dir + "/repo_files.gmi",

73 dir + "/repo_refs.gmi",

74 dir + "/repo_license.gmi",

75 dir + "/repo_readme.gmi",

76 dir + "/repo_access.gmi",

77 dir + "/register_success.gmi",

78 dir + "/public_repo.gmi",

79 dir + "/group_list.gmi",

80 dir + "/group.gmi",

81 dir + "/public_list.gmi",

82 dir + "/public_user.gmi",

83 dir + "/otp.gmi",

84 dir + "/token.gmi",

85 dir + "/token_new.gmi",

86 ))

87 if err != nil {

88 return err

89 }

90 log.Println("Templates loaded")

91 return nil

92 }

93

94 func showRepoFile(user string, reponame string, file string) (string, error) {

95 out, err := repo.GetFile(reponame, user, file)

96 if err != nil {

97 return "", err

98 }

99 reader, err := out.Reader()

100 if err != nil {

101 return "", err

102 }

103 buf, err := io.ReadAll(reader)

104 if err != nil {

105 return "", err

106 }

107 return string(buf), nil

108 }

109

110 func ShowIndex(c gig.Context) (error) {

111 _, connected := db.GetUser(c.CertHash())

112 data := struct {

113 Title string

114 Registration bool

115 Connected bool

116 Public bool

117 }{

118 Title: config.Cfg.Title,

119 Registration: config.Cfg.Users.Registration,

120 Connected: connected,

121 Public: connected || config.Cfg.Git.Public,

122 }

123 return execT(c, "index.gmi", data)

124 }

125

126 func ShowAccount(c gig.Context) (error) {

127 user, exist := db.GetUser(c.CertHash())

128 if !exist {

129 return c.NoContent(gig.StatusBadRequest, "Invalid username")

130 }

131 repoNames := []string{}

132 repos, err := user.GetRepos(false)

133 if err != nil {

134 repoNames = []string{"Failed to load repositories"}

135 log.Println(err)

136 } else {

137 for _, repo := range repos {

138 repoNames = append(repoNames, repo.Name)

139 }

140 }

141 accessRepos, err := user.HasReadAccessTo()

142 sessions, err := user.GetSessionsCount()

143 if err != nil {

144 log.Println(err)

145 return c.NoContent(gig.StatusBadRequest, "Unexpected error")

146 }

147 if sessions == 1 {

148 sessions = 0

149 }

150 data := struct {

151 Username string

152 Description string

153 Repositories []string

154 RepositoriesAccess []db.Repo

155 Sessions int

156 }{

157 Username: user.Name,

158 Description: user.Description,

159 Repositories: repoNames,

160 RepositoriesAccess: accessRepos,

161 Sessions: sessions,

162 }

163 return execT(c, "account.gmi", data)

164 }

165

166 func ShowGroups(c gig.Context) (error) {

167 user, exist := db.GetUser(c.CertHash())

168 if !exist {

169 return c.NoContent(gig.StatusBadRequest, "Invalid username")

170 }

171 groups, err := user.GetGroups()

172 if err != nil {

173 log.Println(err.Error())

174 return c.NoContent(gig.StatusTemporaryFailure,

175 "Failed to fetch groups")

176 }

177 data := struct {

178 Groups []db.Group

179 }{

180 Groups: groups,

181 }

182 return execT(c, "group_list.gmi", data)

183 }

184

185 func ShowMembers(c gig.Context) (error) {

186 user, exist := db.GetUser(c.CertHash())

187 if !exist {

188 return c.NoContent(gig.StatusBadRequest, "Invalid username")

189 }

190 group := c.Param("group")

191 isOwner, err := user.IsInGroup(group)

192 if err != nil {

193 return c.NoContent(gig.StatusTemporaryFailure,

194 "Group not found")

195 }

196

197 members, err := user.GetMembers(group)

198 if err != nil {

199 log.Println(err.Error())

200 return c.NoContent(gig.StatusTemporaryFailure,

201 "Failed to fetch group members")

202 }

203 desc, err := db.GetGroupDesc(group)

204 if err != nil {

205 log.Println(err.Error())

206 return c.NoContent(gig.StatusTemporaryFailure,

207 "Failed to fetch group description")

208 }

209

210 owner := ""

211 if isOwner {

212 owner = user.Name

213 } else {

214 m, err := db.GetGroupOwner(group)

215 if err != nil {

216 log.Println(err.Error())

217 return c.NoContent(gig.StatusTemporaryFailure,

218 "Failed to fetch group owner")

219 }

220 owner = m.Name

221 }

222

223 data := struct {

224 Members []db.Member

225 MembersCount int

226 IsOwner bool

227 Owner string

228 Group string

229 Description string

230 }{

231 Members: members,

232 MembersCount: len(members),

233 IsOwner: isOwner,

234 Owner: owner,

235 Group: group,

236 Description: desc,

237 }

238 return execT(c, "group.gmi", data)

239 }

240

241 func getRepo(c gig.Context, owner bool) (string, string, error) {

242 username := ""

243 if owner {

244 user, exist := db.GetUser(c.CertHash())

245 if !exist {

246 return "", "", c.NoContent(gig.StatusBadRequest,

247 "Invalid username")

248 }

249 username = user.Name

250 } else {

251 username = c.Param("user")

252 ret, err := db.IsRepoPublic(c.Param("repo"), c.Param("user"))

253 if !ret {

254 user, exist := db.GetUser(c.CertHash())

255 if exist {

256 err := access.HasReadAccess(c.Param("repo"),

257 c.Param("user"),

258 user.Name)

259 ret = err == nil

260 }

261 }

262 if !ret || err != nil {

263 return "", "", c.NoContent(gig.StatusBadRequest,

264 "No repository called " + c.Param("repo") +

265 " by user " + c.Param("user"))

266 }

267 }

268 return username, c.Param("repo"), nil

269 }

270

271 func hasFile(name string, author string, file string) bool {

272 ret, err := repo.GetFile(name, author, file)

273 if ret != nil && err == nil {

274 return true

275 }

276 return false

277 }

278

279 type commit struct {

280 Message string

281 Info string

282 }

283

284 type file struct {

285 Hash string

286 Info string

287 }

288

289 type branch struct {

290 Name string

291 Info string

292 }

293

294 func showRepoLogs(name string, author string) (string, error) {

295 ret, err := repo.GetCommits(name, author)

296 if ret == nil || err == transport.ErrEmptyRemoteRepository {

297 return "", nil

298 }

299 if err != nil {

300 log.Println(err.Error())

301 return "", errors.New("Corrupted repository")

302 }

303 commits := []commit{}

304 err = ret.ForEach(func(c *object.Commit) error {

305 info := c.Hash.String() + ", by " + c.Author.Name + " on " +

306 c.Author.When.Format("2006-01-02 15:04:05")

307 commits = append(commits, commit{Info: info,

308 Message: c.Message})

309 return nil

310 })

311 return execTemplate("repo_log.gmi", commits)

312 }

313

314 func showRepoFiles(name string, author string) (string, error) {

315 ret, err := repo.GetFiles(name, author)

316 if ret == nil || err == transport.ErrEmptyRemoteRepository {

317 return "", nil

318 }

319 if err != nil {

320 log.Println(err.Error())

321 return "", errors.New("Corrupted repository")

322 }

323 files := []file{}

324 err = ret.ForEach(func(f *object.File) error {

325 info := f.Mode.String() + " " + f.Name +

326 " " + strconv.Itoa(int(f.Size))

327 files = append(files, file{Info: info,

328 Hash: f.Blob.Hash.String()})

329 return nil

330 })

331 return execTemplate("repo_files.gmi", files)

332 }

333

334 func showRepoRefs(name string, author string) (string, error) {

335 refs, err := repo.GetRefs(name, author)

336 if refs == nil || err == transport.ErrEmptyRemoteRepository {

337 return "", nil

338 }

339 if err != nil {

340 log.Println(err)

341 return "", errors.New("Corrupted repository")

342 }

343 branches := []branch{}

344 tags := []branch{}

345 err = refs.ForEach(func(c *plumbing.Reference) error {

346 if c.Type().String() != "hash-reference" ||

347 c.Name().IsRemote() {

348 return nil

349 }

350 var b branch

351 b.Name = c.Name().String()

352 b.Name = b.Name[strings.LastIndex(b.Name, "/") + 1:]

353 b.Info = "last commit on "

354

355 commit, err := repo.GetCommit(name, author, c.Hash())

356 if err != nil {

357 b.Info = "failed to fetch commit"

358 } else {

359 when := commit.Author.When

360 str := fmt.Sprintf(

361 "%d-%02d-%02d %02d:%02d:%02d",

362 when.Year(), int(when.Month()),

363 when.Day(), when.Hour(),

364 when.Minute(), when.Second())

365 b.Info += str + " by " + commit.Author.Name

366 }

367 if c.Name().IsBranch() {

368 branches = append(branches, b)

369 } else {

370 tags = append(tags, b)

371 }

372 return nil

373 })

374 refs.Close()

375 data := struct {

376 Branches []branch

377 Tags []branch

378 }{

379 branches,

380 tags,

381 }

382 return execTemplate("repo_refs.gmi", data)

383 }

384

385 func showRepoLicense(name string, author string) (string, error) {

386 content, err := showRepoFile(author, name, "LICENSE")

387 if err != nil {

388 return "", errors.New("No license found")

389 }

390 return execTemplate("repo_license.gmi", content)

391 }

392

393 func showRepoReadme(name string, author string) (string, error) {

394 content, err := showRepoFile(author, name, "README.gmi")

395 if err != nil {

396 content, err = showRepoFile(author, name, "README")

397 }

398 if err != nil {

399 return "", errors.New("No readme found")

400 }

401 return execTemplate("repo_readme.gmi", content)

402 }

403

404 func showRepo(c gig.Context, page int, owner bool) (error) {

405 author, name, err := getRepo(c, owner)

406 if err != nil {

407 log.Println(err.Error())

408 return c.NoContent(gig.StatusTemporaryFailure, err.Error())

409 }

410 desc, err := db.GetRepoDesc(name, author)

411 if err != nil {

412 log.Println(err.Error())

413 return c.NoContent(gig.StatusTemporaryFailure,

414 "Repository not found")

415 }

416 protocol := "http"

417 if config.Cfg.Git.Https {

418 protocol = "https"

419 }

420 public, err := db.IsRepoPublic(name, author)

421 if err != nil {

422 log.Println(err.Error())

423 return c.NoContent(gig.StatusTemporaryFailure,

424 "Repository not found")

425 }

426

427 content := ""

428 switch page {

429 case pageLog:

430 content, err = showRepoLogs(name, author)

431 case pageFiles:

432 content, err = showRepoFiles(name, author)

433 case pageRefs:

434 content, err = showRepoRefs(name, author)

435 case pageLicense:

436 content, err = showRepoLicense(name, author)

437 case pageReadme:

438 content, err = showRepoReadme(name, author)

439 }

440 if err != nil {

441 return c.NoContent(gig.StatusBadRequest,

442 "Invalid repository")

443 }

444

445 data := struct {

446 Protocol string

447 Domain string

448 User string

449 Description string

450 Repo string

451 Public bool

452 HasReadme bool

453 HasLicense bool

454 Content string

455 }{

456 Protocol: protocol,

457 Domain: config.Cfg.Git.Domain,

458 User: author,

459 Description: desc,

460 Repo: name,

461 Public: public,

462 HasReadme: hasFile(name, author, "README.gmi") ||

463 hasFile(name, author, "README"),

464 HasLicense: hasFile(name, author, "LICENSE"),

465 Content: content,

466 }

467 if owner {

468 return execT(c, "repo.gmi", data)

469 }

470 return execT(c, "public_repo.gmi", data)

471 }

472

473 func PublicList(c gig.Context) (error) {

474 repos, err := db.GetPublicRepo()

475 if err != nil {

476 log.Println(err.Error())

477 return c.NoContent(gig.StatusTemporaryFailure,

478 "Internal error, "+err.Error())

479 }

480 return execT(c, "public_list.gmi", repos)

481 }

482

483 func PublicAccount(c gig.Context) error {

484 user, err := db.GetPublicUser(c.Param("user"))

485 if err != nil {

486 return c.NoContent(gig.StatusBadRequest, err.Error())

487 }

488 repos, err := user.GetRepos(true)

489 if err != nil {

490 return c.NoContent(gig.StatusTemporaryFailure,

491 "Invalid account, " + err.Error())

492 }

493 data := struct {

494 Name string

495 Description string

496 Repositories []db.Repo

497 }{

498 user.Name,

499 user.Description,

500 repos,

501 }

502 return execT(c, "public_user.gmi", data)

503 }

504

505 func ShowAccess(c gig.Context) error {

506 user, exist := db.GetUser(c.CertHash())

507 if !exist {

508 return c.NoContent(gig.StatusBadRequest, "Invalid username")

509 }

510 repo, err := user.GetRepo(c.Param("repo"))

511 if err != nil {

512 return c.NoContent(gig.StatusBadRequest, err.Error())

513 }

514 access, err := db.GetRepoUserAccess(repo.ID)

515 if err != nil {

516 return c.NoContent(gig.StatusBadRequest, err.Error())

517 }

518 groups, err := db.GetRepoGroupAccess(repo.ID)

519 if err != nil {

520 return c.NoContent(gig.StatusBadRequest, err.Error())

521 }

522 data := struct {

523 Repo string

524 Collaborators []db.Access

525 Groups []db.Access

526 Owner bool

527 }{

528 Repo: repo.Name,

529 Collaborators: access,

530 Groups: groups,

531 Owner: true,

532 }

533 return execT(c, "repo_access.gmi", data)

534 }

535