From 00f927886d3004914bceffbcddd97cbb304255ed Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Mon, 19 Apr 2021 21:38:53 +0200 Subject: [PATCH] revert to embedded anonymous images if downloading pictures fails --- app/pictures/anonymous.jpg | Bin 0 -> 1730 bytes app/pictures/handler.go | 31 ++++++++++++++++++-- app/webmention/recv/receive.go | 11 ++++++-- app/webmention/recv/receive_test.go | 42 ++++++++++++++++++++++++++++ mocks/restclient.go | 2 +- 5 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 app/pictures/anonymous.jpg diff --git a/app/pictures/anonymous.jpg b/app/pictures/anonymous.jpg new file mode 100644 index 0000000000000000000000000000000000000000..128ca66c110b20612a148c044f877a6f6c09fc17 GIT binary patch literal 1730 zcmb7DTU3)*7CwJMOo%}fA=YXjKf!8x%Aej(l*f4|irh@+z;X)S4! zlhY z&XCwhfy^Jt{u`BIw*e9sW}{#!AQY?=Vznrwm{2KMt8XyMRSk$rC9$4}fnsQQE%J;S zXpv4b$bUXtSy(vzgZ0Hy^{b7BNY-Fc*NRixx-s>hgQiJ#d7kH+Jau-;P4nf4t!{Jd znNSI3{m8O2{}(%$P5H9f!@KvKSmKtLkjt1zs?phuL0Mn#3zn;+Oow?Xlf^&0436g{ zjD4K2qdaKfXy=3Zsb}qp(a|#BA#pqz0097TWE@mTH~`e1_7trf-Ih+@eO1jR_YWQU zRU5BDc=Evio(Sg2yWt-&y1FW2tBk?oCaEWR$xGVtqe-^-Qs9a>CRS#Ajf z_xG=F9+Kw7D$0_@AF)vkQAtGMALx`ilnAg#kMwOEys*`5(I|ENJv7?2f3MX<6w$)|(z8ai6zg~kf!8)x( ze<025`+Q%cf!yCGUe-KVX+FPX2iv)A zARcxe2lGMTa6DJ-(J zq^}kFowduQ9r^|jOY37VBdiIdWD#IdN;q;%kyBl0=g9E3Y*BEiwn-M(J!HwMWq9TM86j^6GlJiO}RY4Z23tU#( z%Z1vE6OUbDqGc2F-+yDO|GROjvyJ2ZuJ_*K1380vEq<34v>-kVL%W^hRc9E zJ1I^AnTE=0dF5=yU#(O0#kiG<*W3zNC%*3RPp zi;2QDB(Sh5ben;OU7j0PGf zGi)Jg6aP+$5Vu_q59fcet-zx_YB-nS9!8}C0kSy#V`^2%9sTK(u@=fyEv?{rw(w-O z?K{E0)Xgfe3bmqIfb#@$k@ z69crad7_`w#n#DQ(QPleKCk%xa)9XY&IgUw@JvtVbkW4|>HQ9I0tW$Ez!OA(aFb{D zK>9&LOm1|Bh7%CHjZ{5Oy1nDmu19L!xG_jt`Q0w#(V^CZ@?+nsnDU^S_I(~GAKY4lWB z7nMD5S#*-y(s?ZjF-v+&V@jmurOu0c=j1liI{{hA7gSe^y2XbYQ+Hmg8l9yFND9ha zzhbdiOoE1?WGGQ4Lj#Ww9yMSnm4LY}C%ZSg%4WN(N-lnS;k~~5+>V$owb#W®k% p8&GsQc>eh;CaQy8P_SFFmL|9_L#~V+J!J<%^MdSY*$(^Ie*izzs9FF3 literal 0 HcmV?d00001 diff --git a/app/pictures/handler.go b/app/pictures/handler.go index 1873d09..79be4f1 100644 --- a/app/pictures/handler.go +++ b/app/pictures/handler.go @@ -2,22 +2,47 @@ package pictures import ( "brainbaking.com/go-jamming/db" + _ "embed" "github.com/gorilla/mux" + "github.com/rs/zerolog/log" "net/http" ) +//go:embed anonymous.jpg +var anonymous []byte + +const ( + Anonymous = "anonymous" +) + +func init() { + if anonymous == nil { + log.Fatal().Msg("embedded anonymous image missing?") + } +} + // Handle handles picture GET calls. // It does not validate the picture query as it's part of a composite key anyway. func Handle(repo db.MentionRepo) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { picDomain := mux.Vars(r)["picture"] + if picDomain == Anonymous { + servePicture(w, anonymous) + return + } + picData := repo.GetPicture(picDomain) if picData == nil { http.NotFound(w, r) return } - w.WriteHeader(http.StatusOK) - // TODO response headers? is this a jpeg, png, gif, webm? should we? - w.Write(picData) + servePicture(w, picData) } } + +// servePicture writes an OK and raw bytes. +// For some reason, headers - although they should be there - aren't needed? +func servePicture(w http.ResponseWriter, bytes []byte) { + w.WriteHeader(http.StatusOK) + w.Write(bytes) +} diff --git a/app/webmention/recv/receive.go b/app/webmention/recv/receive.go index df6675c..31d1079 100644 --- a/app/webmention/recv/receive.go +++ b/app/webmention/recv/receive.go @@ -2,6 +2,7 @@ package recv import ( "brainbaking.com/go-jamming/app/mf" + "brainbaking.com/go-jamming/app/pictures" "brainbaking.com/go-jamming/common" "brainbaking.com/go-jamming/db" "brainbaking.com/go-jamming/rest" @@ -97,16 +98,22 @@ func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm mf.Mention) *mf } } +// saveAuthorPictureLocally tries to download the author picture. +// If it succeeds, it alters the picture path to a local /pictures/x one. +// If it fails, it falls back to a local default image. +// This *should* also validate image byte headers, like https://stackoverflow.com/questions/670546/determine-if-file-is-an-image func (recv *Receiver) saveAuthorPictureLocally(indieweb *mf.IndiewebData) { _, picData, err := recv.RestClient.GetBody(indieweb.Author.Picture) if err != nil { - log.Warn().Err(err).Str("url", indieweb.Author.Picture).Msg("Unable to download author picture. Ignoring.") + log.Warn().Err(err).Str("url", indieweb.Author.Picture).Msg("Unable to download author picture. Reverting to anonymous.") + indieweb.Author.Picture = fmt.Sprintf("/pictures/%s", pictures.Anonymous) return } srcDomain := rest.Domain(indieweb.Source) _, dberr := recv.Repo.SavePicture(picData, srcDomain) if dberr != nil { - log.Warn().Err(err).Str("url", indieweb.Author.Picture).Msg("Unable to save downloaded author picture. Ignoring.") + log.Warn().Err(err).Str("url", indieweb.Author.Picture).Msg("Unable to save downloaded author picture. Reverting to anonymous.") + indieweb.Author.Picture = fmt.Sprintf("/pictures/%s", pictures.Anonymous) return } diff --git a/app/webmention/recv/receive_test.go b/app/webmention/recv/receive_test.go index 1814838..3da7d11 100644 --- a/app/webmention/recv/receive_test.go +++ b/app/webmention/recv/receive_test.go @@ -22,6 +22,48 @@ var conf = &common.Config{ ConString: ":memory:", } +func TestSaveAuthorPictureLocally(t *testing.T) { + cases := []struct { + label string + pictureUrl string + expectedPictureUrl string + }{ + { + "Absolute URL gets 'downloaded' and replaced by relative", + "https://brainbaking.com/picture.jpg", + "/pictures/brainbaking.com", + }, + { + "Absolute URL gets replaced by anonymous if download fails", + "https://brainbaking.com/thedogatemypic-nowitsmissing-shiii.png", + "/pictures/anonymous", + }, + } + + for _, tc := range cases { + t.Run(tc.label, func(t *testing.T) { + repo := db.NewMentionRepo(conf) + recv := &Receiver{ + Conf: conf, + Repo: repo, + RestClient: &mocks.RestClientMock{ + GetBodyFunc: mocks.RelPathGetBodyFunc(t, "../../../mocks/"), + }, + } + + indieweb := &mf.IndiewebData{ + Source: "https://brainbaking.com", + Author: mf.IndiewebAuthor{ + Picture: tc.pictureUrl, + }, + } + recv.saveAuthorPictureLocally(indieweb) + + assert.Equal(t, tc.expectedPictureUrl, indieweb.Author.Picture) + }) + } +} + func TestReceive(t *testing.T) { cases := []struct { label string diff --git a/mocks/restclient.go b/mocks/restclient.go index 517c67d..3bc2432 100644 --- a/mocks/restclient.go +++ b/mocks/restclient.go @@ -50,7 +50,7 @@ func RelPathGetBodyFunc(t *testing.T, relPath string) func(string) (http.Header, mockfile := relPath + strings.ReplaceAll(url, "https://brainbaking.com/", "") html, err := ioutil.ReadFile(mockfile) if err != nil { - t.Error(err) + return nil, "", err } headerData, headerFileErr := ioutil.ReadFile(strings.ReplaceAll(mockfile, ".html", "-headers.json"))