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) + } + } +}