package gitea import ( "bytes" "fmt" "io" "io/fs" "log" "net/http" "net/url" "strings" "sync" "code.gitea.io/sdk/gitea" ) const ( defaultPagesRef = "gitea-pages" defaultPagesTopic = "gitea-pages" ) // Client ... type Client struct { cli *gitea.Client url string domain string token string } // NewClient ... func NewClient(url, token, domain string) (*Client, error) { cli, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetGiteaVersion("")) if err != nil { return nil, err } return &Client{ cli: cli, url: url, domain: domain, token: token, }, nil } // Open ... func (c *Client) Open(user, path string) (fs.File, error) { log.Printf("user: %s", user) log.Printf("path: %s", path) var ( repo string filepath string ) parts := strings.Split(strings.TrimLeft(path, "/"), "/") log.Printf("parts: %d #%v", len(parts), parts) if len(parts) > 1 { repo = parts[0] filepath = strings.Join(parts[1:], "/") } else { repo = fmt.Sprintf("%s.%s", user, c.domain) filepath = path } log.Printf("repo: %s", repo) // if path is empty they want to have the index.html if filepath == "" || filepath == "/" { filepath = "index.html" } allowed := c.allowsPages(user, repo) log.Printf("allowed? %t", allowed) if !allowed && repo != fmt.Sprintf("%s.%s", user, c.domain) { repo = fmt.Sprintf("%s.%s", user, c.domain) if c.allowsPages(user, repo) { filepath = path } else { return nil, fs.ErrNotExist } } res, err := c.getRawFileOrLFS(user, repo, filepath, defaultPagesRef) if err != nil { return nil, err } if strings.HasSuffix(filepath, ".md") { res, err = handleMD(res) if err != nil { return nil, err } } return &openFile{ content: res, name: filepath, }, nil } func (c *Client) getRawFileOrLFS(owner, repo, filepath, ref string) ([]byte, error) { var ( giteaURL string err error ) // TODO: make pr for go-sdk // gitea sdk doesn't support "media" type for lfs/non-lfs giteaURL, err = url.JoinPath(c.url+"/api/v1/repos/", owner, repo, "media", filepath) if err != nil { return nil, err } giteaURL += "?ref=" + url.QueryEscape(ref) req, err := http.NewRequest(http.MethodGet, giteaURL, nil) if err != nil { return nil, err } req.Header.Add("Authorization", "token "+c.token) res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } switch res.StatusCode { case http.StatusNotFound: return nil, fs.ErrNotExist case http.StatusOK: default: return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode) } data, err := io.ReadAll(res.Body) if err != nil { return nil, err } defer res.Body.Close() return data, nil } var bufPool = sync.Pool{ New: func() any { return new(bytes.Buffer) }, } func handleMD(res []byte) ([]byte, error) { meta, body, err := extractFrontMatter(string(res)) if err != nil { return nil, err } html, err := markdown([]byte(body)) if err != nil { return nil, err } title, _ := meta["title"].(string) res = append([]byte("\n\n\n

"), []byte(title)...) res = append(res, []byte("

")...) res = append(res, html...) res = append(res, []byte("")...) return res, nil } func (c *Client) repoTopics(owner, repo string) ([]string, error) { repos, _, err := c.cli.ListRepoTopics(owner, repo, gitea.ListRepoTopicsOptions{}) return repos, err } func (c *Client) hasRepoBranch(owner, repo, branch string) bool { b, _, err := c.cli.GetRepoBranch(owner, repo, branch) if err != nil { return false } return b.Name == branch } func (c *Client) allowsPages(owner, repo string) bool { topics, err := c.repoTopics(owner, repo) if err != nil { log.Printf("error finding topics for %s/%s: %s", owner, repo, err) return false } for _, topic := range topics { if topic == defaultPagesTopic { return true } } return false } func splitName(name string) (string, string, string) { parts := strings.Split(name, "/") // parts contains: ["owner", "repo", "filepath"] switch len(parts) { case 1: return parts[0], "", "" case 2: return parts[0], parts[1], "" default: return parts[0], parts[1], strings.Join(parts[2:], "/") } }