173 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			173 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package gitea
 | 
						||
 | 
						||
import (
 | 
						||
	"fmt"
 | 
						||
	"io"
 | 
						||
	"net/http"
 | 
						||
	"strings"
 | 
						||
 | 
						||
	"github.com/caddyserver/caddy/v2"
 | 
						||
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
						||
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
 | 
						||
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | 
						||
	"git.supported.systems/rsp2k/caddy-gitea"
 | 
						||
)
 | 
						||
 | 
						||
func init() {
 | 
						||
	// Register the module and the Caddyfile handler directive.
 | 
						||
	caddy.RegisterModule(Middleware{})
 | 
						||
	httpcaddyfile.RegisterHandlerDirective("gitea", parseCaddyfile)
 | 
						||
}
 | 
						||
 | 
						||
// parseCaddyfile creates and configures the module from Caddyfile input.
 | 
						||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
 | 
						||
	var m Middleware
 | 
						||
	if err := m.UnmarshalCaddyfile(h.Dispenser); err != nil {
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	return &m, nil
 | 
						||
}
 | 
						||
 | 
						||
// Middleware implements the Gitea plugin.
 | 
						||
type Middleware struct {
 | 
						||
	// The Gitea client is created during Provision.
 | 
						||
	Client *gitea.Client `json:"-"`
 | 
						||
 | 
						||
	// Required/optional configuration fields:
 | 
						||
	Server             string `json:"server,omitempty"`              // e.g. "https://gitea.example.com"
 | 
						||
	Token              string `json:"token,omitempty"`               // API token if needed
 | 
						||
	GiteaPages         string `json:"gitea_pages,omitempty"`         // e.g. URL for Gitea pages
 | 
						||
	GiteaPagesAllowAll string `json:"gitea_pages_allowall,omitempty"`  // configuration for allow-all pages mode
 | 
						||
	Domain             string `json:"domain,omitempty"`              // if set, used to split subdomain parts
 | 
						||
}
 | 
						||
 | 
						||
// CaddyModule returns the Caddy module information.
 | 
						||
func (Middleware) CaddyModule() caddy.ModuleInfo {
 | 
						||
	return caddy.ModuleInfo{
 | 
						||
		ID: "http.handlers.gitea",
 | 
						||
		New: func() caddy.Module {
 | 
						||
			return new(Middleware)
 | 
						||
		},
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
// Provision creates the Gitea client.
 | 
						||
func (m *Middleware) Provision(ctx caddy.Context) error {
 | 
						||
	// Validate required configuration.
 | 
						||
	if m.Server == "" {
 | 
						||
		return fmt.Errorf("gitea: server must be specified")
 | 
						||
	}
 | 
						||
 | 
						||
	var err error
 | 
						||
	m.Client, err = gitea.NewClient(m.Server, m.Token, m.GiteaPages, m.GiteaPagesAllowAll)
 | 
						||
	return err
 | 
						||
}
 | 
						||
 | 
						||
// Validate performs additional configuration validation.
 | 
						||
func (m *Middleware) Validate() error {
 | 
						||
	if m.Server == "" {
 | 
						||
		return fmt.Errorf("gitea: server is required")
 | 
						||
	}
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// UnmarshalCaddyfile sets up the configuration from Caddyfile tokens.
 | 
						||
func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
						||
	// Each block of tokens (if more than one block is given) will be processed.
 | 
						||
	for d.Next() {
 | 
						||
		// Process all subdirectives in the block.
 | 
						||
		for n := d.Nesting(); d.NextBlock(n); {
 | 
						||
			switch d.Val() {
 | 
						||
			case "server":
 | 
						||
				if !d.NextArg() {
 | 
						||
					return d.ArgErr()
 | 
						||
				}
 | 
						||
				m.Server = d.Val()
 | 
						||
			case "token":
 | 
						||
				if !d.NextArg() {
 | 
						||
					return d.ArgErr()
 | 
						||
				}
 | 
						||
				m.Token = d.Val()
 | 
						||
			case "gitea_pages":
 | 
						||
				if !d.NextArg() {
 | 
						||
					return d.ArgErr()
 | 
						||
				}
 | 
						||
				m.GiteaPages = d.Val()
 | 
						||
			case "gitea_pages_allowall":
 | 
						||
				if !d.NextArg() {
 | 
						||
					return d.ArgErr()
 | 
						||
				}
 | 
						||
				m.GiteaPagesAllowAll = d.Val()
 | 
						||
			case "domain":
 | 
						||
				if !d.NextArg() {
 | 
						||
					return d.ArgErr()
 | 
						||
				}
 | 
						||
				m.Domain = d.Val()
 | 
						||
			default:
 | 
						||
				return d.Errf("gitea: unrecognized subdirective '%s'", d.Val())
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// ServeHTTP implements the HTTP handler. It uses the Gitea client to
 | 
						||
// retrieve content based on the request’s host and path.
 | 
						||
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
 | 
						||
	// Derive the file path from the request's host and URL.
 | 
						||
	//
 | 
						||
	// If m.Domain is set then we expect requests in one of two forms:
 | 
						||
	//   - repo.username.<domain>  (len=2 after splitting by '.')
 | 
						||
	//   - branch.repo.username.<domain>  (len=3)
 | 
						||
	//
 | 
						||
	// Otherwise, we just use the first segment of the host.
 | 
						||
	host := r.Host
 | 
						||
	if m.Domain != "" {
 | 
						||
		// Remove the configured domain (and a trailing dot, if any)
 | 
						||
		host = strings.TrimSuffix(host, m.Domain)
 | 
						||
		host = strings.TrimRight(host, ".")
 | 
						||
	}
 | 
						||
	parts := strings.Split(host, ".")
 | 
						||
	var fp string
 | 
						||
	// Default: use first part plus the URL path.
 | 
						||
	if len(parts) > 0 {
 | 
						||
		fp = parts[0] + r.URL.Path
 | 
						||
	}
 | 
						||
	// Adjust based on subdomain count when a domain is configured.
 | 
						||
	ref := r.URL.Query().Get("ref")
 | 
						||
	if m.Domain != "" {
 | 
						||
		switch len(parts) {
 | 
						||
		case 2:
 | 
						||
			// Format: repo.username.<domain>
 | 
						||
			fp = parts[1] + "/" + parts[0] + r.URL.Path
 | 
						||
		case 3:
 | 
						||
			// Format: branch.repo.username.<domain>
 | 
						||
			fp = parts[2] + "/" + parts[1] + r.URL.Path
 | 
						||
			ref = parts[0]
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// Open the file from Gitea.
 | 
						||
	f, err := m.Client.Open(fp, ref)
 | 
						||
	if err != nil {
 | 
						||
		// Return a 404 error if the file isn’t found.
 | 
						||
		return caddyhttp.Error(http.StatusNotFound, err)
 | 
						||
	}
 | 
						||
	// If the returned file supports closing, ensure it gets closed.
 | 
						||
	if closer, ok := f.(io.Closer); ok {
 | 
						||
		defer closer.Close()
 | 
						||
	}
 | 
						||
 | 
						||
	// Copy the file's contents to the response.
 | 
						||
	_, err = io.Copy(w, f)
 | 
						||
	return err
 | 
						||
}
 | 
						||
 | 
						||
// Interface guards
 | 
						||
var (
 | 
						||
	_ caddy.Provisioner           = (*Middleware)(nil)
 | 
						||
	_ caddy.Validator             = (*Middleware)(nil)
 | 
						||
	_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)
 | 
						||
	_ caddyfile.Unmarshaler       = (*Middleware)(nil)
 | 
						||
)
 |