auth/internal/httputil/httputil.go

140 lines
3.1 KiB
Go

package httputil
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/goccy/go-json"
"github.com/tomnomnom/linkheader"
"golang.org/x/exp/slices"
"willnorris.com/go/microformats"
"source.toby3d.me/toby3d/auth/internal/common"
"source.toby3d.me/toby3d/auth/internal/domain"
)
const RelIndieauthMetadata = "indieauth-metadata"
var ErrEndpointNotExist = domain.NewError(
domain.ErrorCodeServerError,
"cannot found any endpoints",
"https://indieauth.net/source/#discovery-0",
)
func ExtractFromMetadata(client *http.Client, u string) (*domain.Metadata, error) {
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(body)
endpoints := ExtractEndpoints(buf, resp.Request.URL, resp.Header.Get(common.HeaderLink), RelIndieauthMetadata)
if len(endpoints) == 0 {
return nil, ErrEndpointNotExist
}
if resp, err = client.Get(endpoints[len(endpoints)-1].String()); err != nil {
return nil, fmt.Errorf("failed to fetch metadata endpoint configuration: %w", err)
}
result := new(domain.Metadata)
if err = json.NewDecoder(resp.Body).Decode(result); err != nil {
return nil, fmt.Errorf("cannot unmarshal emtadata configuration: %w", err)
}
return result, nil
}
func ExtractEndpoints(body io.Reader, u *url.URL, linkHeader, rel string) []*url.URL {
results := make([]*url.URL, 0)
urls, err := ExtractEndpointsFromHeader(linkHeader, rel)
if err == nil {
results = append(results, urls...)
}
urls, err = ExtractEndpointsFromBody(body, u, rel)
if err == nil {
results = append(results, urls...)
}
return results
}
func ExtractEndpointsFromHeader(linkHeader, rel string) ([]*url.URL, error) {
results := make([]*url.URL, 0)
for _, link := range linkheader.Parse(linkHeader) {
if !strings.EqualFold(link.Rel, rel) {
continue
}
u, err := url.Parse(link.URL)
if err != nil {
return nil, fmt.Errorf("cannot parse header endpoint: %w", err)
}
results = append(results, u)
}
return results, nil
}
func ExtractEndpointsFromBody(body io.Reader, u *url.URL, rel string) ([]*url.URL, error) {
endpoints, ok := microformats.Parse(body, u).Rels[rel]
if !ok || len(endpoints) == 0 {
return nil, ErrEndpointNotExist
}
results := make([]*url.URL, 0)
for i := range endpoints {
u, err := url.Parse(endpoints[i])
if err != nil {
return nil, fmt.Errorf("cannot parse body endpoint: %w", err)
}
results = append(results, u)
}
return results, nil
}
func ExtractProperty(body io.Reader, u *url.URL, itemType, key string) []any {
if data := microformats.Parse(body, u); data != nil {
return FindProperty(data.Items, itemType, key)
}
return nil
}
func FindProperty(src []*microformats.Microformat, itemType, key string) []any {
for _, item := range src {
if slices.Contains(item.Type, itemType) {
return item.Properties[key]
}
if result := FindProperty(item.Children, itemType, key); result != nil {
return result
}
}
return nil
}