go-jamming/app/webmention/send/discoverer.go

102 lines
2.8 KiB
Go

package send
import (
"brainbaking.com/go-jamming/rest"
"fmt"
"github.com/rs/zerolog/log"
"net/url"
"regexp"
"strings"
"willnorris.com/go/microformats"
)
const (
typeWebmention string = "webmention"
typeUnknown string = "unknown"
typePingback string = "pingback"
)
var (
relWebmention = regexp.MustCompile(`rel="??'??webmention`)
possibleFeedEndpoints = []string{
"all/index.xml",
"index.xml",
"feed",
"feed/index.xml",
}
)
func (sndr *Sender) discoverRssFeed(domain string) (string, error) {
for _, endpt := range possibleFeedEndpoints {
feedUrl := fmt.Sprintf("https://%s/%s", domain, endpt)
resp, err := sndr.RestClient.Head(feedUrl)
if err != nil || !rest.IsStatusOk(resp) || resp.Header.Get("Content-Type") != "text/xml" {
continue
}
return feedUrl, nil
}
return "", fmt.Errorf("Unable to discover RSS feed for domain %s", domain)
}
func (sndr *Sender) discoverMentionEndpoint(target string) (link string, mentionType string) {
mentionType = typeUnknown
header, body, err := sndr.RestClient.GetBody(target)
if err != nil {
log.Warn().Str("target", target).Msg("Failed to discover possible endpoint, aborting send")
return
}
link = header.Get(rest.RequestUrl) // default to a possible redirect of the target
baseUrl, _ := url.Parse(link)
// prefer links in the header over the html itself.
for _, possibleLink := range header.Values("link") {
if relWebmention.MatchString(possibleLink) {
return buildWebmentionHeaderLink(possibleLink, baseUrl), typeWebmention
}
}
if header.Get("X-Pingback") != "" {
return header.Get("X-Pingback"), typePingback
}
// this also complies with w3.org regulations: relative endpoint could be possible
format := microformats.Parse(strings.NewReader(body), baseUrl)
if len(format.Rels[typeWebmention]) > 0 {
mentionType = typeWebmention
for _, possibleWm := range format.Rels[typeWebmention] {
if possibleWm != link {
link = possibleWm
return
}
}
} else if len(format.Rels[typePingback]) > 0 {
mentionType = typePingback
link = format.Rels[typePingback][0]
}
return
}
// buildWebmentionHeaderLink tries to extract the link from the link header.
// e.g. Link: <http://aaronpk.example/webmention-endpoint>; rel="webmention"
// could also be comma-separated, e.g. <https://webmention.rocks/test/19/webmention/error>; rel="other", <https://webmention.rocks/test/19/webmention?head=true>; rel="webmention"
func buildWebmentionHeaderLink(link string, baseUrl *url.URL) (wm string) {
if strings.Contains(link, ",") {
for _, possibleLink := range strings.Split(link, ",") {
if relWebmention.MatchString(possibleLink) {
link = strings.TrimSpace(possibleLink)
}
}
}
raw := strings.Split(link, ";")[0][1:]
wm = raw[:len(raw)-1]
if !strings.HasPrefix(wm, "http") {
abs, _ := baseUrl.Parse(wm)
wm = abs.String()
}
return
}