2023-08-19 17:44:34 +03:00
|
|
|
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)
|
|
|
|
|
2023-08-19 17:59:01 +03:00
|
|
|
var (
|
|
|
|
repo string
|
|
|
|
filepath string
|
|
|
|
)
|
2023-08-19 17:44:34 +03:00
|
|
|
|
|
|
|
parts := strings.Split(strings.TrimLeft(path, "/"), "/")
|
|
|
|
log.Printf("parts: %d #%v", len(parts), parts)
|
2023-11-12 23:44:57 +03:00
|
|
|
if len(parts) > 1 {
|
2023-08-19 17:44:34 +03:00
|
|
|
repo = parts[0]
|
2023-08-19 17:59:01 +03:00
|
|
|
filepath = strings.Join(parts[1:], "/")
|
2023-11-12 23:44:57 +03:00
|
|
|
if strings.Contains(parts[len(parts)-1], ".") {
|
|
|
|
repo = fmt.Sprintf("%s.%s", user, c.domain)
|
|
|
|
filepath = path
|
|
|
|
}
|
2023-08-19 17:44:34 +03:00
|
|
|
} else {
|
|
|
|
repo = fmt.Sprintf("%s.%s", user, c.domain)
|
2023-08-19 17:59:01 +03:00
|
|
|
filepath = path
|
2023-08-19 17:44:34 +03:00
|
|
|
}
|
|
|
|
log.Printf("repo: %s", repo)
|
|
|
|
|
|
|
|
// if path is empty they want to have the index.html
|
2023-08-19 17:59:01 +03:00
|
|
|
if filepath == "" || filepath == "/" {
|
|
|
|
filepath = "index.html"
|
2023-08-19 17:44:34 +03:00
|
|
|
}
|
|
|
|
|
2023-08-19 18:20:26 +03:00
|
|
|
// If it's a dir, give them index.html
|
|
|
|
if strings.HasSuffix(filepath, "/") {
|
2023-08-19 18:21:38 +03:00
|
|
|
filepath += "index.html"
|
2023-08-19 18:20:26 +03:00
|
|
|
}
|
|
|
|
|
2023-08-19 17:44:34 +03:00
|
|
|
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)
|
2023-08-19 17:59:01 +03:00
|
|
|
if c.allowsPages(user, repo) {
|
|
|
|
filepath = path
|
|
|
|
} else {
|
2023-08-19 17:44:34 +03:00
|
|
|
return nil, fs.ErrNotExist
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-19 18:02:33 +03:00
|
|
|
res, err := c.getRawFileOrLFS(user, repo, filepath, defaultPagesRef)
|
2023-08-19 17:44:34 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-19 18:02:33 +03:00
|
|
|
if strings.HasSuffix(filepath, ".md") {
|
2023-08-19 17:44:34 +03:00
|
|
|
res, err = handleMD(res)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &openFile{
|
|
|
|
content: res,
|
2023-08-19 18:02:33 +03:00
|
|
|
name: filepath,
|
2023-08-19 17:44:34 +03:00
|
|
|
}, 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("<!DOCTYPE html>\n<html>\n<body>\n<h1>"), []byte(title)...)
|
|
|
|
res = append(res, []byte("</h1>")...)
|
|
|
|
res = append(res, html...)
|
|
|
|
res = append(res, []byte("</body></html>")...)
|
|
|
|
|
|
|
|
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:], "/")
|
|
|
|
}
|
|
|
|
}
|