diff --git a/app/pingback/handler.go b/app/pingback/handler.go index 7ef3272..794a80f 100644 --- a/app/pingback/handler.go +++ b/app/pingback/handler.go @@ -2,13 +2,102 @@ package pingback import ( + "encoding/xml" + "github.com/rs/zerolog/log" + "github.com/wgroeneveld/go-jamming/app/webmention" + "github.com/wgroeneveld/go-jamming/rest" + "io/ioutil" "net/http" + "text/template" "github.com/wgroeneveld/go-jamming/common" ) -func Handle(conf *common.Config) http.HandlerFunc { +func HandlePost(conf *common.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + pingbackError(w, "Unable to read request body") + } + rpc := &XmlRPCMethodCall{} + err = xml.Unmarshal(body, rpc) + if err != nil { + pingbackError(w, "Unable to unmarshal XMLRPC request body") + } + + if !validate(rpc, conf) { + pingbackError(w, "malformed pingback request") + return + } + + wm := webmention.Mention{ + Source: rpc.Source(), + Target: rpc.Target(), + } + receiver := webmention.Receiver{ + RestClient: &rest.HttpClient{}, + Conf: conf, + } + go receiver.Receive(wm) + pingbackSuccess(w, "Thanks, bro. Will process this soon, pinky swear!") + } } +var successXml = ` + + + + + + {{ . }} + + + + + +` +// compile once, execute as many times as needed. +var successTpl, _ = template.New("success").Parse(successXml) + +func pingbackSuccess(w http.ResponseWriter, msg string) { + w.WriteHeader(200) + successTpl.Execute(w, msg) +} + +// according to the XML-RPC spec, always return a 200, but encode it into the XML. +func pingbackError(w http.ResponseWriter, msg string) { + xml := ` + + + + + + + faultCode + + + + 0 + + + + + + faultString + + + + Sorry pal. Malformed request? Or something else, who knows... + + + + + + +` + log.Error().Str("msg", msg).Msg("Pingback receive went wrong") + w.WriteHeader(200) + w.Write([]byte(xml)) +} + + diff --git a/app/pingback/validate.go b/app/pingback/validate.go new file mode 100644 index 0000000..a1f3aa1 --- /dev/null +++ b/app/pingback/validate.go @@ -0,0 +1,34 @@ +package pingback + +import ( + "github.com/wgroeneveld/go-jamming/common" + "strings" +) + +func validate(rpc *XmlRPCMethodCall, conf *common.Config) bool { + if rpc.MethodName != "pingback.ping" { + return false + } + if len(rpc.Params.Parameters) != 2 { + return false + } + + target := rpc.Target() + if !strings.HasPrefix(target, "http") { + return false + } + _, err := conf.FetchDomain(target) + if err != nil { + return false + } + + source := rpc.Source() + if !strings.HasPrefix(source, "http") { + return false + } + + if source == target { + return false + } + return true +} diff --git a/app/pingback/validate_test.go b/app/pingback/validate_test.go new file mode 100644 index 0000000..945b0e4 --- /dev/null +++ b/app/pingback/validate_test.go @@ -0,0 +1,178 @@ +package pingback + +import ( + "encoding/xml" + "github.com/stretchr/testify/assert" + "github.com/wgroeneveld/go-jamming/common" + "testing" +) + +var conf *common.Config = &common.Config{ + AllowedWebmentionSources: []string{ + "brainbaking.com", + "jefklakscodex.com", + }, +} + +func TestValidate(t *testing.T) { + cases := []struct { + label string + xml string + expected bool + }{ + { + "not valid if methodName is not pingback.ping", + ` + + + ka.tsjing + + + https://cool.site + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "not valid if less than two parameters", + ` + + + pingback.ping + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "not valid if more than two parameters", + ` + + pingback.ping + + + https://cool.site + + + https://brainbaking.com/post/2021/03/cool-ness + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "not valid if target is not in trusted domains from config", + ` + + + pingback.ping + + + https://cool.site + + + https://flashballz.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "not valid if target is not http(s)", + ` + + + pingback.ping + + + https://cool.site + + + gemini://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "not valid if source is not http(s)", + ` + + + pingback.ping + + + gemini://cool.site + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + { + "is valid if pingback.ping and two http(s) parameters of which target is trusted", + ` + + + pingback.ping + + + https://cool.site + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + true, + }, + { + "is not valid if source and target are the same urls", + ` + + + pingback.ping + + + https://brainbaking.com/post/2021/03/cool-ness + + + https://brainbaking.com/post/2021/03/cool-ness + + + + `, + false, + }, + } + + for _, tc := range cases { + t.Run(tc.label, func(t *testing.T) { + var xmlObj XmlRPCMethodCall + err := xml.Unmarshal([]byte(tc.xml), &xmlObj) + assert.NoError(t, err, "XML invalid in test case") + + result := validate(&xmlObj, conf) + assert.Equal(t, tc.expected, result) + }) + } +} \ No newline at end of file diff --git a/app/pingback/xmlrpc.go b/app/pingback/xmlrpc.go new file mode 100644 index 0000000..86be6e0 --- /dev/null +++ b/app/pingback/xmlrpc.go @@ -0,0 +1,45 @@ +package pingback + +import "encoding/xml" + +/* e.g. (see tests) + + + pingback.ping + + + https://brainbaking.com/kristien.html + + + https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/ + + + + */ +type XmlRPCMethodCall struct { + XMLName xml.Name `xml:"methodCall"` + MethodName string `xml:"methodName"` + Params XmlRPCParams `xml:"params"` +} + +func (rpc *XmlRPCMethodCall) Source() string { + return rpc.Params.Parameters[0].Value.String +} + +func (rpc *XmlRPCMethodCall) Target() string { + return rpc.Params.Parameters[1].Value.String +} + +type XmlRPCParams struct { + XMLName xml.Name `xml:"params"` + Parameters []XmlRPCParam `xml:"param"` +} + +type XmlRPCParam struct { + XMLName xml.Name `xml:"param"` + Value XmlRPCValue `xml:"value"` +} + +type XmlRPCValue struct { + String string `xml:"string"` +} \ No newline at end of file diff --git a/app/pingback/xmlrpc_test.go b/app/pingback/xmlrpc_test.go new file mode 100644 index 0000000..f46b987 --- /dev/null +++ b/app/pingback/xmlrpc_test.go @@ -0,0 +1,30 @@ +package pingback + +import ( + "encoding/xml" + "github.com/stretchr/testify/assert" + "testing" +) + +// See https://www.hixie.ch/specs/pingback/pingback#refsXMLRPC +func TestMarshallValidXMLRPC(t *testing.T) { + xmlString := ` + + pingback.ping + + + https://brainbaking.com/kristien.html + + + https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/ + + +` + var rpc XmlRPCMethodCall + err := xml.Unmarshal([]byte(xmlString), &rpc) + + assert.NoError(t, err) + assert.Equal(t, "pingback.ping", rpc.MethodName) + assert.Equal(t, "https://brainbaking.com/kristien.html", rpc.Params.Parameters[0].Value.String) + assert.Equal(t, "https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/", rpc.Params.Parameters[1].Value.String) +} \ No newline at end of file diff --git a/app/routes.go b/app/routes.go index 3084d5f..1e4118f 100644 --- a/app/routes.go +++ b/app/routes.go @@ -12,7 +12,7 @@ import ( // https://blog.questionable.services/article/http-handler-error-handling-revisited/ is the better idea, but more work func (s *server) routes() { s.router.HandleFunc("/", index.Handle(s.conf)).Methods("GET") - s.router.HandleFunc("/pingback", pingback.Handle(s.conf)).Methods("POST") + s.router.HandleFunc("/pingback", pingback.HandlePost(s.conf)).Methods("POST") s.router.HandleFunc("/webmention", webmention.HandlePost(s.conf)).Methods("POST") s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandleGet(s.conf))).Methods("GET") s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandlePut(s.conf))).Methods("PUT") diff --git a/app/webmention/handler.go b/app/webmention/handler.go index a078ee3..532ad70 100644 --- a/app/webmention/handler.go +++ b/app/webmention/handler.go @@ -37,16 +37,16 @@ func HandlePost(conf *common.Config) http.HandlerFunc { return } - wm := webmention{ - source: r.FormValue("source"), - target: target, + wm := Mention{ + Source: r.FormValue("source"), + Target: target, } - recv := &receiver{ - restClient: httpClient, - conf: conf, + recv := &Receiver{ + RestClient: httpClient, + Conf: conf, } - go recv.receive(wm) + go recv.Receive(wm) rest.Accept(w) } } diff --git a/app/webmention/microformats.go b/app/webmention/microformats.go index 176ae9b..bc2d7fe 100644 --- a/app/webmention/microformats.go +++ b/app/webmention/microformats.go @@ -1,6 +1,7 @@ package webmention import ( + "strings" "time" "willnorris.com/go/microformats" ) @@ -80,3 +81,57 @@ func mfProp(mf *microformats.Microformat, key string) *microformats.Microformat } return val[0].(*microformats.Microformat) } + +func determinePublishedDate(hEntry *microformats.Microformat, utcOffset int) string { + publishedDate := mfStr(hEntry, "published") + if publishedDate == "" { + return publishedNow(utcOffset) + } + return publishedDate +} + +func determineAuthorName(hEntry *microformats.Microformat) string { + authorName := mfStr(mfProp(hEntry, "author"), "name") + if authorName == "" { + return mfProp(hEntry, "author").Value + } + return authorName +} + +func determineMfType(hEntry *microformats.Microformat) string { + likeOf := mfStr(hEntry, "like-of") + if likeOf != "" { + return "like" + } + bookmarkOf := mfStr(hEntry, "bookmark-of") + if bookmarkOf != "" { + return "bookmark" + } + return "mention" +} + +// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources +func determineUrl(hEntry *microformats.Microformat, source string) string { + uid := mfStr(hEntry, "uid") + if uid != "" && strings.HasPrefix(uid, "http") { + return uid + } + url := mfStr(hEntry, "url") + if url != "" { + return url + } + return source +} + +func determineContent(hEntry *microformats.Microformat) string { + bridgyTwitterContent := mfStr(hEntry, "bridgy-twitter-content") + if bridgyTwitterContent != "" { + return shorten(bridgyTwitterContent) + } + summary := mfStr(hEntry, "summary") + if summary != "" { + return shorten(summary) + } + contentEntry := mfMap(hEntry, "content")["value"] + return shorten(contentEntry) +} \ No newline at end of file diff --git a/app/webmention/receive.go b/app/webmention/receive.go index 8d75a73..93e2433 100644 --- a/app/webmention/receive.go +++ b/app/webmention/receive.go @@ -18,39 +18,39 @@ import ( "willnorris.com/go/microformats" ) -type webmention struct { - source string - target string +type Mention struct { + Source string + Target string } -func (wm *webmention) String() string { - return fmt.Sprintf("source: %s, target: %s", wm.source, wm.target) +func (wm *Mention) String() string { + return fmt.Sprintf("source: %s, target: %s", wm.Source, wm.Target) } -func (wm *webmention) asPath(conf *common.Config) string { - filename := fmt.Sprintf("%x", md5.Sum([]byte("source=" + wm.source + ",target=" + wm.target))) - domain, _ := conf.FetchDomain(wm.target) +func (wm *Mention) asPath(conf *common.Config) string { + filename := fmt.Sprintf("%x", md5.Sum([]byte("source=" + wm.Source+ ",target=" + wm.Target))) + domain, _ := conf.FetchDomain(wm.Target) return conf.DataPath + "/" + domain + "/" + filename + ".json" } -func (wm *webmention) sourceUrl() *url.URL { - url, _ := url.Parse(wm.source) +func (wm *Mention) sourceUrl() *url.URL { + url, _ := url.Parse(wm.Source) return url } // used as a "class" to iject dependencies, just to be able to test. Do NOT like htis. // Is there a better way? e.g. in validate, I just pass rest.Client as an arg. Not great either. -type receiver struct { - restClient rest.Client - conf *common.Config +type Receiver struct { + RestClient rest.Client + Conf *common.Config } -func (recv *receiver) receive(wm webmention) { - log.Info().Str("webmention", wm.String()).Msg("OK: looks valid") - body, geterr := recv.restClient.GetBody(wm.source) +func (recv *Receiver) Receive(wm Mention) { + log.Info().Str("Webmention", wm.String()).Msg("OK: looks valid") + body, geterr := recv.RestClient.GetBody(wm.Source) if geterr != nil { - log.Warn().Str("source", wm.source).Msg(" ABORT: invalid url") + log.Warn().Str("source", wm.Source).Msg(" ABORT: invalid url") recv.deletePossibleOlderWebmention(wm) return } @@ -58,8 +58,8 @@ func (recv *receiver) receive(wm webmention) { recv.processSourceBody(body, wm) } -func (recv *receiver) deletePossibleOlderWebmention(wm webmention) { - os.Remove(wm.asPath(recv.conf)) +func (recv *Receiver) deletePossibleOlderWebmention(wm Mention) { + os.Remove(wm.asPath(recv.Conf)) } func getHEntry(data *microformats.Data) *microformats.Microformat { @@ -72,40 +72,40 @@ func getHEntry(data *microformats.Data) *microformats.Microformat { } -func (recv *receiver) processSourceBody(body string, wm webmention) { - if !strings.Contains(body, wm.target) { - log.Warn().Str("target", wm.target).Msg("ABORT: no mention of target found in html src of source!") +func (recv *Receiver) processSourceBody(body string, wm Mention) { + if !strings.Contains(body, wm.Target) { + log.Warn().Str("target", wm.Target).Msg("ABORT: no mention of target found in html src of source!") return } - r := strings.NewReader(body) - data := microformats.Parse(r, wm.sourceUrl()) - hEntry := getHEntry(data) - var indieweb *indiewebData - if hEntry == nil { - indieweb = recv.parseBodyAsNonIndiewebSite(body, wm) - } else { - indieweb = recv.parseBodyAsIndiewebSite(hEntry, wm) - } - + data := microformats.Parse(strings.NewReader(body), wm.sourceUrl()) + indieweb := recv.convertBodyToIndiewebData(body, wm, getHEntry(data)) + recv.saveWebmentionToDisk(wm, indieweb) - log.Info().Str("file", wm.asPath(recv.conf)).Msg("OK: webmention processed.") + log.Info().Str("file", wm.asPath(recv.Conf)).Msg("OK: Webmention processed.") } -func (recv *receiver) saveWebmentionToDisk(wm webmention, indieweb *indiewebData) { +func (recv *Receiver) convertBodyToIndiewebData(body string, wm Mention, hEntry *microformats.Microformat) *indiewebData { + if hEntry == nil { + return recv.parseBodyAsNonIndiewebSite(body, wm) + } + return recv.parseBodyAsIndiewebSite(hEntry, wm) +} + +func (recv *Receiver) saveWebmentionToDisk(wm Mention, indieweb *indiewebData) { jsonData, jsonErr := json.Marshal(indieweb) if jsonErr != nil { - log.Err(jsonErr).Msg("Unable to serialize webmention into JSON") + log.Err(jsonErr).Msg("Unable to serialize Webmention into JSON") } - err := ioutil.WriteFile(wm.asPath(recv.conf), jsonData, fs.ModePerm) + err := ioutil.WriteFile(wm.asPath(recv.Conf), jsonData, fs.ModePerm) if err != nil { - log.Err(err).Msg("Unable to save webmention to disk") + log.Err(err).Msg("Unable to save Webmention to disk") } } // TODO I'm smelling very unstable code, apply https://golang.org/doc/effective_go#recover here? // see https://github.com/willnorris/microformats/blob/main/microformats.go -func (recv *receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, wm webmention) *indiewebData { +func (recv *Receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, wm Mention) *indiewebData { name := mfStr(hEntry, "name") pic := mfStr(mfProp(hEntry, "author"), "photo") mfType := determineMfType(hEntry) @@ -117,85 +117,31 @@ func (recv *receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, Picture: pic, }, Content: determineContent(hEntry), - Url: determineUrl(hEntry, wm.source), - Published: determinePublishedDate(hEntry, recv.conf.UtcOffset), - Source: wm.source, - Target: wm.target, + Url: determineUrl(hEntry, wm.Source), + Published: determinePublishedDate(hEntry, recv.Conf.UtcOffset), + Source: wm.Source, + Target: wm.Target, IndiewebType: mfType, } } -func determinePublishedDate(hEntry *microformats.Microformat, utcOffset int) string { - publishedDate := mfStr(hEntry, "published") - if publishedDate == "" { - return publishedNow(utcOffset) - } - return publishedDate -} - -func determineAuthorName(hEntry *microformats.Microformat) string { - authorName := mfStr(mfProp(hEntry, "author"), "name") - if authorName == "" { - return mfProp(hEntry, "author").Value - } - return authorName -} - -func determineMfType(hEntry *microformats.Microformat) string { - likeOf := mfStr(hEntry, "like-of") - if likeOf != "" { - return "like" - } - bookmarkOf := mfStr(hEntry, "bookmark-of") - if bookmarkOf != "" { - return "bookmark" - } - return "mention" -} - -// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources -func determineUrl(hEntry *microformats.Microformat, source string) string { - uid := mfStr(hEntry, "uid") - if uid != "" && strings.HasPrefix(uid, "http") { - return uid - } - url := mfStr(hEntry, "url") - if url != "" { - return url - } - return source -} - -func determineContent(hEntry *microformats.Microformat) string { - bridgyTwitterContent := mfStr(hEntry, "bridgy-twitter-content") - if bridgyTwitterContent != "" { - return shorten(bridgyTwitterContent) - } - summary := mfStr(hEntry, "summary") - if summary != "" { - return shorten(summary) - } - contentEntry := mfMap(hEntry, "content")["value"] - return shorten(contentEntry) -} - -func (recv *receiver) parseBodyAsNonIndiewebSite(body string, wm webmention) *indiewebData { +func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm Mention) *indiewebData { r := regexp.MustCompile(`(.*?)<\/title>`) titleMatch := r.FindStringSubmatch(body) - title := wm.source + title := wm.Source if titleMatch != nil { title = titleMatch[1] } return &indiewebData{ Author: indiewebAuthor{ - Name: wm.source, + Name: wm.Source, }, Name: title, Content: title, - Published: publishedNow(recv.conf.UtcOffset), - Url: wm.source, + Published: publishedNow(recv.Conf.UtcOffset), + Url: wm.Source, IndiewebType: "mention", - Source: wm.source, - Target: wm.target, + Source: wm.Source, + Target: wm.Target, } } diff --git a/app/webmention/receive_test.go b/app/webmention/receive_test.go index 4a61092..e9ea5a7 100644 --- a/app/webmention/receive_test.go +++ b/app/webmention/receive_test.go @@ -22,9 +22,9 @@ var conf = &common.Config{ func TestConvertWebmentionToPath(t *testing.T) { - wm := webmention{ - source: "https://brainbaking.com", - target: "https://jefklakscodex.com/articles", + wm := Mention{ + Source: "https://brainbaking.com", + Target: "https://jefklakscodex.com/articles", } result := wm.asPath(conf) @@ -42,56 +42,56 @@ func writeSomethingTo(filename string) { func TestReceive(t *testing.T) { cases := []struct { label string - wm webmention - json string + wm Mention + json string } { { - label: "receive a webmention bookmark via twitter", - wm: webmention{ - source: "https://brainbaking.com/valid-bridgy-twitter-source.html", - target: "https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag", + label: "receive a Webmention bookmark via twitter", + wm: Mention{ + Source: "https://brainbaking.com/valid-bridgy-twitter-source.html", + Target: "https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag", }, json: `{"author":{"name":"Jamie Tanna","picture":"https://www.jvt.me/img/profile.png"},"name":"","content":"Recommended read:\nThe IndieWeb Mixed Bag - Thoughts about the (d)evolution of blog interactions\nhttps://brainbaking.com/post/2021/03/the-indieweb-mixed-bag/","published":"2021-03-15T12:42:00+0000","url":"https://brainbaking.com/mf2/2021/03/1bkre/","type":"bookmark","source":"https://brainbaking.com/valid-bridgy-twitter-source.html","target":"https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag"}`, }, { - label: "receive a brid.gy webmention like", - wm: webmention{ - source: "https://brainbaking.com/valid-bridgy-like.html", + label: "receive a brid.gy Webmention like", + wm: Mention{ + Source: "https://brainbaking.com/valid-bridgy-like.html", // wrapped in a a class="u-like-of" tag - target: "https://brainbaking.com/valid-indieweb-target.html", + Target: "https://brainbaking.com/valid-indieweb-target.html", }, // no dates in bridgy-to-mastodon likes... json: `{"author":{"name":"Stampeding Longhorn","picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"},"name":"","content":"","published":"2020-01-01T12:30:00","url":"https://chat.brainbaking.com/notice/A4nx1rFwKUJYSe4TqK#favorited-by-A4nwg4LYyh4WgrJOXg","type":"like","source":"https://brainbaking.com/valid-bridgy-like.html","target":"https://brainbaking.com/valid-indieweb-target.html"}`, }, { - label: "receive a brid.gy webmention that has a url and photo without value", - wm: webmention{ - source: "https://brainbaking.com/valid-bridgy-source.html", - target: "https://brainbaking.com/valid-indieweb-target.html", + label: "receive a brid.gy Webmention that has a url and photo without value", + wm: Mention{ + Source: "https://brainbaking.com/valid-bridgy-source.html", + Target: "https://brainbaking.com/valid-indieweb-target.html", }, json: `{"author":{"name":"Stampeding Longhorn", "picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"}, "content":"@wouter The cat pictures are awesome. for jest tests!", "name":"@wouter The cat pictures are awesome. for jest tests!", "published":"2021-03-02T16:17:18.000Z", "source":"https://brainbaking.com/valid-bridgy-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://social.linux.pizza/@StampedingLonghorn/105821099684887793"}`, }, { label: "receive saves a JSON file of indieweb-metadata if all is valid", - wm: webmention{ - source: "https://brainbaking.com/valid-indieweb-source.html", - target: "https://jefklakscodex.com/articles", + wm: Mention{ + Source: "https://brainbaking.com/valid-indieweb-source.html", + Target: "https://jefklakscodex.com/articles", }, json: `{"author":{"name":"Wouter Groeneveld","picture":"https://brainbaking.com//img/avatar.jpg"},"name":"I just learned about https://www.inklestudios.com/...","content":"This is cool, I just found out about valid indieweb target - so cool","published":"2021-03-06T12:41:00","url":"https://brainbaking.com/notes/2021/03/06h12m41s48/","type":"mention","source":"https://brainbaking.com/valid-indieweb-source.html","target":"https://jefklakscodex.com/articles"}`, }, { label: "receive saves a JSON file of indieweb-metadata with summary as content if present", - wm: webmention{ - source: "https://brainbaking.com/valid-indieweb-source-with-summary.html", - target: "https://brainbaking.com/valid-indieweb-target.html", + wm: Mention{ + Source: "https://brainbaking.com/valid-indieweb-source-with-summary.html", + Target: "https://brainbaking.com/valid-indieweb-target.html", }, json: `{"author":{"name":"Wouter Groeneveld", "picture":"https://brainbaking.com//img/avatar.jpg"}, "content":"This is cool, this is a summary!", "name":"I just learned about https://www.inklestudios.com/...", "published":"2021-03-06T12:41:00", "source":"https://brainbaking.com/valid-indieweb-source-with-summary.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/notes/2021/03/06h12m41s48/"}`, }, { label: "receive saves a JSON file of non-indieweb-data such as title if all is valid", - wm: webmention{ - source: "https://brainbaking.com/valid-nonindieweb-source.html", - target: "https://brainbaking.com/valid-indieweb-target.html", + wm: Mention{ + Source: "https://brainbaking.com/valid-nonindieweb-source.html", + Target: "https://brainbaking.com/valid-indieweb-target.html", }, json: `{"author":{"name":"https://brainbaking.com/valid-nonindieweb-source.html", "picture":""}, "content":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "name":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "published":"2020-01-01T12:30:00", "source":"https://brainbaking.com/valid-nonindieweb-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/valid-nonindieweb-source.html"}`, }, @@ -106,14 +106,14 @@ func TestReceive(t *testing.T) { return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC) } - receiver := &receiver { - conf: conf, - restClient: &mocks.RestClientMock{ + receiver := &Receiver{ + Conf: conf, + RestClient: &mocks.RestClientMock{ GetBodyFunc: mocks.RelPathGetBodyFunc(t), }, } - receiver.receive(tc.wm) + receiver.Receive(tc.wm) actualJson, _ := ioutil.ReadFile(tc.wm.asPath(conf)) assert.JSONEq(t, tc.json, string(actualJson)) @@ -125,9 +125,9 @@ func TestReceiveTargetDoesNotExistAnymoreDeletesPossiblyOlderWebmention(t *testi os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm) defer os.RemoveAll("testdata") - wm := webmention{ - source: "https://brainbaking.com", - target: "https://jefklakscodex.com/articles", + wm := Mention{ + Source: "https://brainbaking.com", + Target: "https://jefklakscodex.com/articles", } filename := wm.asPath(conf) writeSomethingTo(filename) @@ -137,31 +137,31 @@ func TestReceiveTargetDoesNotExistAnymoreDeletesPossiblyOlderWebmention(t *testi return "", errors.New("whoops") }, } - receiver := &receiver { - conf: conf, - restClient: client, + receiver := &Receiver{ + Conf: conf, + RestClient: client, } - receiver.receive(wm) + receiver.Receive(wm) assert.NoFileExists(t, filename) } func TestReceiveTargetThatDoesNotPointToTheSourceDoesNothing(t *testing.T) { - wm := webmention{ - source: "https://brainbaking.com/valid-indieweb-source.html", - target: "https://brainbaking.com/valid-indieweb-source.html", + wm := Mention{ + Source: "https://brainbaking.com/valid-indieweb-source.html", + Target: "https://brainbaking.com/valid-indieweb-source.html", } filename := wm.asPath(conf) writeSomethingTo(filename) - receiver := &receiver { - conf: conf, - restClient: &mocks.RestClientMock{ + receiver := &Receiver{ + Conf: conf, + RestClient: &mocks.RestClientMock{ GetBodyFunc: mocks.RelPathGetBodyFunc(t), }, } - receiver.receive(wm) + receiver.Receive(wm) assert.NoFileExists(t, filename) } @@ -169,12 +169,12 @@ func TestProcessSourceBodyAbortsIfNoMentionOfTargetFoundInSourceHtml(t *testing. os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm) defer os.RemoveAll("testdata") - wm := webmention{ - source: "https://brainbaking.com", - target: "https://jefklakscodex.com/articles", + wm := Mention{ + Source: "https://brainbaking.com", + Target: "https://jefklakscodex.com/articles", } - receiver := &receiver { - conf: conf, + receiver := &Receiver{ + Conf: conf, } receiver.processSourceBody("<html>my nice body</html>", wm) diff --git a/rest/utils.go b/rest/utils.go index 3ca5212..065ce44 100644 --- a/rest/utils.go +++ b/rest/utils.go @@ -11,5 +11,5 @@ func BadRequest(w http.ResponseWriter) { func Accept(w http.ResponseWriter) { w.WriteHeader(202) - w.Write([]byte("Thanks, bro. Will send these webmentions soon, pinky swear!")) + w.Write([]byte("Thanks, bro. Will process this soon, pinky swear!")) }