From 3986c8eec16b6ebff216c39c637f1762494defc8 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 20 May 2019 14:34:11 -0700 Subject: [PATCH 1/5] add missing string variable in log statement in export.go + go fmt --- export.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/export.go b/export.go index e23b850..42715b9 100644 --- a/export.go +++ b/export.go @@ -14,9 +14,10 @@ import ( "archive/zip" "bytes" "encoding/csv" - "github.com/writeas/web-core/log" "strings" "time" + + "github.com/writeas/web-core/log" ) func exportPostsCSV(u *User, posts *[]PublicPost) []byte { @@ -37,7 +38,7 @@ func exportPostsCSV(u *User, posts *[]PublicPost) []byte { w := csv.NewWriter(&b) w.WriteAll(r) // calls Flush internally if err := w.Error(); err != nil { - log.Info("error writing csv:", err) + log.Info("error writing csv: %v", err) } return b.Bytes() From ff2d3fc3d5fcf237590eee2ce871deb6faf458f4 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 20 May 2019 14:41:26 -0700 Subject: [PATCH 2/5] fixes issue #100 - can't follow from pubgate this moves the unmarshaling of a remote actor out into a new helper which accounts for the possibility of a context being a list or a single entity. i.e. a string or an object. basics tests are provided for both situations also go fmt'd the file activitypub.go --- .vscode/settings.json | 3 +++ activitypub.go | 61 +++++++++++++++++++++++++++++++++++++------ activitypub_test.go | 31 ++++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 activitypub_test.go diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f06b593 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "go.formatTool": "goimports" +} \ No newline at end of file diff --git a/activitypub.go b/activitypub.go index 3b0ac82..0291bf9 100644 --- a/activitypub.go +++ b/activitypub.go @@ -17,6 +17,13 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "time" + "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" "github.com/writeas/activity/streams" @@ -26,12 +33,6 @@ import ( "github.com/writeas/web-core/activitypub" "github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/log" - "io/ioutil" - "net/http" - "net/http/httputil" - "net/url" - "strconv" - "time" ) const ( @@ -647,8 +648,7 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser, log.Error("Unable to get actor! %v", err) return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."} } - if err := json.Unmarshal(actorResp, &actor); err != nil { - // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string + if err := unmarshalActor(actorResp, actor); err != nil { log.Error("Unable to unmarshal actor! %v", err) return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."} } @@ -663,3 +663,48 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser, } return actor, remoteUser, nil } + +// unmarshal actor normalizes the actor response to conform to +// the type Person from github.com/writeas/web-core/activitysteams +// +// some implementations return different context field types +// this converts any non-slice contexts into a slice +func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error { + // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string + + // flexActor overrides the Context field to allow + // all valid representations during unmarshal + flexActor := struct { + activitystreams.Person + Context json.RawMessage `json:"@context,omitempty"` + }{} + if err := json.Unmarshal(actorResp, &flexActor); err != nil { + return err + } + + actor.Endpoints = flexActor.Endpoints + actor.Followers = flexActor.Followers + actor.Following = flexActor.Following + actor.ID = flexActor.ID + actor.Icon = flexActor.Icon + actor.Inbox = flexActor.Inbox + actor.Name = flexActor.Name + actor.Outbox = flexActor.Outbox + actor.PreferredUsername = flexActor.PreferredUsername + actor.PublicKey = flexActor.PublicKey + actor.Summary = flexActor.Summary + actor.Type = flexActor.Type + actor.URL = flexActor.URL + + func(val interface{}) { + switch val.(type) { + case []interface{}: + // already a slice, do nothing + actor.Context = val.([]interface{}) + default: + actor.Context = []interface{}{val} + } + }(flexActor.Context) + + return nil +} diff --git a/activitypub_test.go b/activitypub_test.go new file mode 100644 index 0000000..7a1a89a --- /dev/null +++ b/activitypub_test.go @@ -0,0 +1,31 @@ +package writefreely + +import ( + "testing" + + "github.com/writeas/web-core/activitystreams" +) + +var actorTestTable = []struct { + Name string + Resp []byte +}{ + { + "Context as a string", + []byte(`{"@context":"https://www.w3.org/ns/activitystreams"}`), + }, + { + "Context as a list", + []byte(`{"@context":["one string", "two strings"]}`), + }, +} + +func TestUnmarshalActor(t *testing.T) { + for _, tc := range actorTestTable { + actor := activitystreams.Person{} + err := unmarshalActor(tc.Resp, &actor) + if err != nil { + t.Errorf("%s failed with error %s", tc.Name, err) + } + } +} From 95215aa39db51f9510ded34480a62fb61242c4dd Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 20 May 2019 14:41:26 -0700 Subject: [PATCH 3/5] fixes issue #100 - can't follow from pubgate this moves the unmarshaling of a remote actor out into a new helper which accounts for the possibility of a context being a list or a single entity. i.e. a string or an object. basics tests are provided for both situations also go fmt'd the file activitypub.go --- activitypub.go | 61 +++++++++++++++++++++++++++++++++++++++------ activitypub_test.go | 31 +++++++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 activitypub_test.go diff --git a/activitypub.go b/activitypub.go index 3b0ac82..0291bf9 100644 --- a/activitypub.go +++ b/activitypub.go @@ -17,6 +17,13 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "time" + "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" "github.com/writeas/activity/streams" @@ -26,12 +33,6 @@ import ( "github.com/writeas/web-core/activitypub" "github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/log" - "io/ioutil" - "net/http" - "net/http/httputil" - "net/url" - "strconv" - "time" ) const ( @@ -647,8 +648,7 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser, log.Error("Unable to get actor! %v", err) return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."} } - if err := json.Unmarshal(actorResp, &actor); err != nil { - // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string + if err := unmarshalActor(actorResp, actor); err != nil { log.Error("Unable to unmarshal actor! %v", err) return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."} } @@ -663,3 +663,48 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser, } return actor, remoteUser, nil } + +// unmarshal actor normalizes the actor response to conform to +// the type Person from github.com/writeas/web-core/activitysteams +// +// some implementations return different context field types +// this converts any non-slice contexts into a slice +func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error { + // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string + + // flexActor overrides the Context field to allow + // all valid representations during unmarshal + flexActor := struct { + activitystreams.Person + Context json.RawMessage `json:"@context,omitempty"` + }{} + if err := json.Unmarshal(actorResp, &flexActor); err != nil { + return err + } + + actor.Endpoints = flexActor.Endpoints + actor.Followers = flexActor.Followers + actor.Following = flexActor.Following + actor.ID = flexActor.ID + actor.Icon = flexActor.Icon + actor.Inbox = flexActor.Inbox + actor.Name = flexActor.Name + actor.Outbox = flexActor.Outbox + actor.PreferredUsername = flexActor.PreferredUsername + actor.PublicKey = flexActor.PublicKey + actor.Summary = flexActor.Summary + actor.Type = flexActor.Type + actor.URL = flexActor.URL + + func(val interface{}) { + switch val.(type) { + case []interface{}: + // already a slice, do nothing + actor.Context = val.([]interface{}) + default: + actor.Context = []interface{}{val} + } + }(flexActor.Context) + + return nil +} diff --git a/activitypub_test.go b/activitypub_test.go new file mode 100644 index 0000000..7a1a89a --- /dev/null +++ b/activitypub_test.go @@ -0,0 +1,31 @@ +package writefreely + +import ( + "testing" + + "github.com/writeas/web-core/activitystreams" +) + +var actorTestTable = []struct { + Name string + Resp []byte +}{ + { + "Context as a string", + []byte(`{"@context":"https://www.w3.org/ns/activitystreams"}`), + }, + { + "Context as a list", + []byte(`{"@context":["one string", "two strings"]}`), + }, +} + +func TestUnmarshalActor(t *testing.T) { + for _, tc := range actorTestTable { + actor := activitystreams.Person{} + err := unmarshalActor(tc.Resp, &actor) + if err != nil { + t.Errorf("%s failed with error %s", tc.Name, err) + } + } +} From d8fa85432dab62e36dd64827194e938448383ecf Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 3 Jun 2019 11:53:17 -0700 Subject: [PATCH 4/5] fix for Pubgate user not having SharedInbox --- activitypub.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/activitypub.go b/activitypub.go index 0291bf9..0971286 100644 --- a/activitypub.go +++ b/activitypub.go @@ -549,10 +549,14 @@ func deleteFederatedPost(app *app, p *PublicPost, collID int64) error { inboxes := map[string][]string{} for _, f := range *followers { - if _, ok := inboxes[f.SharedInbox]; ok { - inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID) + inbox := f.SharedInbox + if inbox == "" { + inbox = f.Inbox + } + if _, ok := inboxes[inbox]; ok { + inboxes[inbox] = append(inboxes[inbox], f.ActorID) } else { - inboxes[f.SharedInbox] = []string{f.ActorID} + inboxes[inbox] = []string{f.ActorID} } } @@ -592,10 +596,14 @@ func federatePost(app *app, p *PublicPost, collID int64, isUpdate bool) error { inboxes := map[string][]string{} for _, f := range *followers { - if _, ok := inboxes[f.SharedInbox]; ok { - inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID) + inbox := f.SharedInbox + if inbox == "" { + inbox = f.Inbox + } + if _, ok := inboxes[inbox]; ok { + inboxes[inbox] = append(inboxes[inbox], f.ActorID) } else { - inboxes[f.SharedInbox] = []string{f.ActorID} + inboxes[inbox] = []string{f.ActorID} } } From 08799b220bc14477c38f6fc2766051ea8263fd97 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 3 Jun 2019 11:55:42 -0700 Subject: [PATCH 5/5] revert accidental .vscode folder inclusion --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index f06b593..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "go.formatTool": "goimports" -} \ No newline at end of file