140 lines
3.1 KiB
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
|
|
}
|