package routes import ( "compress/gzip" "fmt" "log" "net/http" "path" "path/filepath" "strconv" "strings" securejoin "github.com/cyphar/filepath-securejoin" "github.com/dimfeld/httptreemux/v5" "github.com/microcosm-cc/bluemonday" "github.com/russross/blackfriday/v2" "go.alanpearce.eu/elgit/config" "go.alanpearce.eu/elgit/git" "go.alanpearce.eu/elgit/templates" ) type deps struct { c *config.Config projects []string } func (d *deps) Index(w http.ResponseWriter, r *http.Request, params map[string]string) { repos := d.getAllRepos() pageData := templates.PageData{ Meta: d.c.Meta, } if err := templates.Index(pageData, repos).Render(w); err != nil { log.Println(err) return } } func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, "") if err != nil { d.Write404(w) return } commits, err := gr.Commits() if err != nil { d.Write500(w) log.Println(err) return } var readmeContent string for _, readme := range d.c.Repo.Readme { ext := filepath.Ext(readme) content, _ := gr.FileContent(readme) if len(content) > 0 { switch ext { case ".md", ".mkd", ".markdown": unsafe := blackfriday.Run( []byte(content), blackfriday.WithExtensions(blackfriday.CommonExtensions), ) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) readmeContent = string(html) default: safe := bluemonday.UGCPolicy().SanitizeBytes([]byte(content)) readmeContent = fmt.Sprintf(`
%s
`, safe) } break } } if readmeContent == "" { log.Printf("no readme found for %s", name) } mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch) if err != nil { d.Write500(w) log.Println(err) return } if len(commits) >= 3 { commits = commits[:3] } pageData := templates.PageData{ Meta: d.c.Meta, Name: name, DisplayName: getDisplayName(name), Ref: mainBranch, Description: getDescription(path), Servername: d.c.Server.Name, Gomod: isGoModule(gr), } if err := templates.Repo(pageData, commits, readmeContent).Render(w); err != nil { log.Println(err) } } func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } treePath := strings.TrimSuffix(params["rest"], "/") ref := params["ref"] path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, ref) if err != nil { d.Write404(w) return } files, err := gr.FileTree(treePath) if err != nil { d.Write500(w) log.Println(err) return } data := make(map[string]any) data["name"] = name data["displayname"] = getDisplayName(name) data["ref"] = ref data["parent"] = treePath data["desc"] = getDescription(path) data["dotdot"] = filepath.Dir(treePath) d.listFiles(files, data, w) } func (d *deps) FileContent(w http.ResponseWriter, r *http.Request, params map[string]string) { var raw bool if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil { raw = rawParam } name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } treePath := params["rest"] ref := params["ref"] name = httptreemux.Clean(name) path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, ref) if err != nil { d.Write404(w) return } contents, err := gr.FileContent(treePath) if err != nil { d.Write500(w) return } data := make(map[string]any) data["name"] = name data["displayname"] = getDisplayName(name) data["ref"] = ref data["desc"] = getDescription(path) data["path"] = treePath if raw { d.showRaw(contents, w) } else { if d.c.Meta.SyntaxHighlight == "" { d.showFile(contents, data, w) } else { d.showFileWithHighlight(treePath, contents, data, w) } } } func (d *deps) Archive(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } file := params["file"] // TODO: extend this to add more files compression (e.g.: xz) if !strings.HasSuffix(file, ".tar.gz") { d.Write404(w) return } ref := strings.TrimSuffix(file, ".tar.gz") // This allows the browser to use a proper name for the file when // downloading filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) setContentDisposition(w, filename) setGZipMIME(w) path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, ref) if err != nil { d.Write404(w) return } gw := gzip.NewWriter(w) defer gw.Close() prefix := fmt.Sprintf("%s-%s", name, ref) err = gr.WriteTar(gw, prefix) if err != nil { // once we start writing to the body we can't report error anymore // so we are only left with printing the error. log.Println(err) return } err = gw.Flush() if err != nil { // once we start writing to the body we can't report error anymore // so we are only left with printing the error. log.Println(err) return } } func (d *deps) Log(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } ref := params["ref"] path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, ref) if err != nil { d.Write404(w) return } commits, err := gr.Commits() if err != nil { d.Write500(w) log.Println(err) return } pageData := templates.PageData{ Meta: d.c.Meta, Name: name, DisplayName: getDisplayName(name), Ref: ref, Description: getDescription(path), Log: true, } if err := templates.Log(pageData, commits).Render(w); err != nil { log.Println(err) return } } func (d *deps) Diff(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } ref := params["ref"] path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, ref) if err != nil { d.Write404(w) return } diff, err := gr.Diff() if err != nil { d.Write500(w) log.Println(err) return } pageData := templates.PageData{ Meta: d.c.Meta, Name: name, Stat: diff.Stat, Diff: diff.Diff, DisplayName: getDisplayName(name), Ref: ref, Description: getDescription(path), } if err := templates.Commit(pageData, diff).Render(w); err != nil { log.Println(err) return } } func (d *deps) Refs(w http.ResponseWriter, r *http.Request, params map[string]string) { name := path.Join(params["category"], params["name"]) if d.isNotAllowed(name) { d.Write404(w) return } path, err := d.GetCleanPath(name) if err != nil { log.Printf("getcleanpath error: %v", err) d.Write404(w) return } gr, err := git.Open(path, "") if err != nil { d.Write404(w) return } tags, err := gr.Tags() if err != nil { // Non-fatal, we *should* have at least one branch to show. log.Println(err) } branches, err := gr.Branches() if err != nil { log.Println(err) d.Write500(w) return } pageData := templates.PageData{ Meta: d.c.Meta, Name: name, DisplayName: getDisplayName(name), Description: getDescription(path), } if err := templates.Refs(pageData, branches, tags).Render(w); err != nil { log.Println(err) return } } func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request, params map[string]string) { f := params["file"] f = httptreemux.Clean(f) f, err := securejoin.SecureJoin(d.c.Dirs.Static, f) if err != nil { d.Write404(w) return } http.ServeFile(w, r, f) }