From 163e22c49ceee615508c16e17ff3bae7f45cc5df Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 10 Mar 2021 11:29:20 +0100 Subject: [PATCH] Improve content negotiation and activity streams --- activityStreams.go | 29 +++++++++++++++++++++++++++-- config.go | 6 ++++-- errors.go | 10 ++++++++-- go.mod | 1 + go.sum | 2 ++ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/activityStreams.go b/activityStreams.go index a9b0f9e..3134126 100644 --- a/activityStreams.go +++ b/activityStreams.go @@ -5,20 +5,28 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "fmt" "net/http" - "strings" "github.com/araddon/dateparse" + "github.com/elnormous/contenttype" ) var asContext = []string{"https://www.w3.org/ns/activitystreams"} +var asCheckMediaTypes = []contenttype.MediaType{ + contenttype.NewMediaType(contentTypeHTML), + contenttype.NewMediaType(contentTypeAS), + contenttype.NewMediaType("application/ld+json"), +} + const asRequestKey requestContextKey = "asRequest" func checkActivityStreamsRequest(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if ap := appConfig.ActivityPub; ap != nil && ap.Enabled { - if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) { + // Check if accepted media type is not HTML + if mt, _, err := contenttype.GetAcceptableMediaType(r, asCheckMediaTypes); err == nil && mt.String() != asCheckMediaTypes[0].String() { next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), asRequestKey, true))) return } @@ -41,6 +49,7 @@ type asNote struct { ID string `json:"id,omitempty"` URL string `json:"url,omitempty"` AttributedTo string `json:"attributedTo,omitempty"` + Tag []*asTag `json:"tag,omitempty"` } type asPerson struct { @@ -62,6 +71,12 @@ type asAttachment struct { URL string `json:"url,omitempty"` } +type asTag struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Href string `json:"href,omitempty"` +} + type asPublicKey struct { ID string `json:"id,omitempty"` Owner string `json:"owner,omitempty"` @@ -106,6 +121,16 @@ func (p *post) toASNote() *asNote { }) } } + // Tags + for _, tagTax := range appConfig.ActivityPub.TagsTaxonomies { + for _, tag := range p.Parameters[tagTax] { + as.Tag = append(as.Tag, &asTag{ + Type: "Hashtag", + Name: tag, + Href: appConfig.Server.PublicAddress + appConfig.Blogs[p.Blog].getRelativePath(fmt.Sprintf("/%s/%s", tagTax, urlize(tag))), + }) + } + } // Dates dateFormat := "2006-01-02T15:04:05-07:00" if p.Published != "" { diff --git a/config.go b/config.go index 8e9be39..eb68b68 100644 --- a/config.go +++ b/config.go @@ -182,8 +182,9 @@ type configRegexRedirect struct { } type configActivityPub struct { - Enabled bool `mapstructure:"enabled"` - KeyPath string `mapstructure:"keyPath"` + Enabled bool `mapstructure:"enabled"` + KeyPath string `mapstructure:"keyPath"` + TagsTaxonomies []string `mapstructure:"tagsTaxonomies"` } type configNotifications struct { @@ -230,6 +231,7 @@ func initConfig() error { viper.SetDefault("micropub.photoParam", "images") viper.SetDefault("micropub.photoDescriptionParam", "imagealts") viper.SetDefault("activityPub.keyPath", "data/private.pem") + viper.SetDefault("activityPub.tagsTaxonomies", []string{"tags"}) // Unmarshal config err = viper.Unmarshal(appConfig) if err != nil { diff --git a/errors.go b/errors.go index 55322ee..1808b69 100644 --- a/errors.go +++ b/errors.go @@ -3,7 +3,8 @@ package main import ( "fmt" "net/http" - "strings" + + "github.com/elnormous/contenttype" ) type errorData struct { @@ -15,8 +16,13 @@ func serve404(w http.ResponseWriter, r *http.Request) { serveError(w, r, fmt.Sprintf("%s was not found", r.RequestURI), http.StatusNotFound) } +var errorCheckMediaTypes = []contenttype.MediaType{ + contenttype.NewMediaType(contentTypeHTML), +} + func serveError(w http.ResponseWriter, r *http.Request, message string, status int) { - if !strings.Contains(strings.ToLower(r.Header.Get("Accept")), contentTypeHTML) { + if mt, _, err := contenttype.GetAcceptableMediaType(r, errorCheckMediaTypes); err != nil || mt.String() != errorCheckMediaTypes[0].String() { + // Request doesn't accept HTML http.Error(w, message, status) return } diff --git a/go.mod b/go.mod index 300a609..6b49d21 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/caddyserver/certmagic v0.12.0 github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/elnormous/contenttype v0.0.0-20210110050721-79150725153f github.com/go-chi/chi/v5 v5.0.0 github.com/go-fed/httpsig v1.1.0 github.com/go-sql-driver/mysql v1.5.0 // indirect diff --git a/go.sum b/go.sum index f4d78b8..cf199ec 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elnormous/contenttype v0.0.0-20210110050721-79150725153f h1:juwLa2Kbp2uwOGMOagrkTYXN/5+7sbINMmIZSluH2Gc= +github.com/elnormous/contenttype v0.0.0-20210110050721-79150725153f/go.mod h1:ngVcyGGU8pnn4QJ5sL4StrNgc/wmXZXy5IQSBuHOFPg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=