mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-10-06 01:06:59 +08:00
158 lines
3.2 KiB
Go
158 lines
3.2 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/knadh/koanf/providers/file"
|
|
"github.com/stv0g/cunicu/pkg/util/buildinfo"
|
|
)
|
|
|
|
type remoteFileProvider struct {
|
|
url *url.URL
|
|
etag string
|
|
lastModified time.Time
|
|
order []string
|
|
}
|
|
|
|
func RemoteFileProvider(u *url.URL) *remoteFileProvider {
|
|
return &remoteFileProvider{
|
|
url: u,
|
|
}
|
|
}
|
|
|
|
func (p *remoteFileProvider) Read() (map[string]interface{}, error) {
|
|
return nil, errors.New("this provider does not support parsers")
|
|
}
|
|
|
|
func (p *remoteFileProvider) ReadBytes() ([]byte, error) {
|
|
if p.url.Scheme != "https" {
|
|
host, _, err := net.SplitHostPort(p.url.Host)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to split host:port: %w", err)
|
|
} else if host != "localhost" && host != "127.0.0.1" && host != "::1" && host != "[::1]" {
|
|
return nil, errors.New("remote configuration must be provided via HTTPS")
|
|
}
|
|
}
|
|
|
|
client := &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
req := &http.Request{
|
|
Method: "GET",
|
|
URL: p.url,
|
|
Header: http.Header{},
|
|
}
|
|
|
|
req.Header.Set("User-Agent", buildinfo.UserAgent())
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch %s: %w", p.url, err)
|
|
} else if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("failed to fetch: %s: %s", p.url, resp.Status)
|
|
}
|
|
|
|
buf, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
p.order, err = ExtractInterfaceOrder(buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get interface order: %w", err)
|
|
}
|
|
|
|
p.etag = resp.Header.Get("Etag")
|
|
|
|
if lm := resp.Header.Get("Last-modified"); lm != "" {
|
|
p.lastModified, err = time.Parse(http.TimeFormat, lm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse Last-Modified header: %w", err)
|
|
}
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
func (p *remoteFileProvider) Order() []string {
|
|
return p.order
|
|
}
|
|
|
|
func (p *remoteFileProvider) Version() any {
|
|
p.hasChanged()
|
|
|
|
if p.etag != "" {
|
|
return p.etag
|
|
}
|
|
|
|
if !p.lastModified.IsZero() {
|
|
return p.lastModified.Unix()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *remoteFileProvider) hasChanged() (bool, error) {
|
|
client := &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
req := &http.Request{
|
|
Method: "HEAD",
|
|
URL: p.url,
|
|
Header: http.Header{},
|
|
}
|
|
|
|
req.Header.Set("User-Agent", buildinfo.UserAgent())
|
|
|
|
if p.etag != "" {
|
|
req.Header.Set("If-None-Match", fmt.Sprintf("\"%s\"", p.etag))
|
|
}
|
|
|
|
if !p.lastModified.IsZero() {
|
|
req.Header.Set("If-Modified-Since", p.lastModified.Format(http.TimeFormat))
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to fetch %s: %w", p.url, err)
|
|
} else if resp.StatusCode != http.StatusOK {
|
|
return false, fmt.Errorf("failed to fetch: %s: %s", p.url, resp.Status)
|
|
}
|
|
|
|
return resp.StatusCode == 200, nil
|
|
}
|
|
|
|
type localFileProvider struct {
|
|
*file.File
|
|
|
|
order []string
|
|
}
|
|
|
|
func LocalFileProvider(u *url.URL) *localFileProvider {
|
|
return &localFileProvider{
|
|
File: file.Provider(u.Path),
|
|
}
|
|
}
|
|
|
|
func (p *localFileProvider) ReadBytes() ([]byte, error) {
|
|
buf, err := p.File.ReadBytes()
|
|
|
|
if err == nil {
|
|
p.order, err = ExtractInterfaceOrder(buf)
|
|
}
|
|
|
|
return buf, err
|
|
}
|
|
|
|
func (p *localFileProvider) Order() []string {
|
|
return p.order
|
|
}
|