summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLibravatar Daenney <daenney@users.noreply.github.com>2023-03-09 18:55:45 +0100
committerLibravatar GitHub <noreply@github.com>2023-03-09 17:55:45 +0000
commita312238e7909c6451e608a91c326ad250dda875c (patch)
tree1395a27178a7ffd78486e3ddb00cd29dfce27cd8 /internal
parent[bug] Handle 410 on webfinger properly (#1601) (diff)
downloadgotosocial-a312238e7909c6451e608a91c326ad250dda875c.tar.xz
[feature] Provide .well-known/host-meta endpoint (#1604)
* [feature] Provide .well-known/host-meta endpoint This adds the host-meta endpoint as Mastodon clients use this to discover the API domain to use when the host and account domains aren't the same. * Address review comments
Diffstat (limited to 'internal')
-rw-r--r--internal/api/util/mime.go1
-rw-r--r--internal/api/util/negotiate.go5
-rw-r--r--internal/api/wellknown.go4
-rw-r--r--internal/api/wellknown/hostmeta/hostmeta.go45
-rw-r--r--internal/api/wellknown/hostmeta/hostmetaget.go73
-rw-r--r--internal/processing/fedi/wellknown.go20
6 files changed, 148 insertions, 0 deletions
diff --git a/internal/api/util/mime.go b/internal/api/util/mime.go
index 30f0f0d6e..cfdc3b08b 100644
--- a/internal/api/util/mime.go
+++ b/internal/api/util/mime.go
@@ -25,6 +25,7 @@ type MIME string
const (
AppJSON MIME = `application/json`
AppXML MIME = `application/xml`
+ AppXMLXRD MIME = `application/xrd+xml`
AppRSSXML MIME = `application/rss+xml`
AppActivityJSON MIME = `application/activity+json`
AppActivityLDJSON MIME = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
diff --git a/internal/api/util/negotiate.go b/internal/api/util/negotiate.go
index 3a5f21775..06a202815 100644
--- a/internal/api/util/negotiate.go
+++ b/internal/api/util/negotiate.go
@@ -58,6 +58,11 @@ var HTMLOrActivityPubHeaders = []MIME{
AppActivityLDJSON,
}
+var HostMetaHeaders = []MIME{
+ AppXMLXRD,
+ AppXML,
+}
+
// NegotiateAccept takes the *gin.Context from an incoming request, and a
// slice of Offers, and performs content negotiation for the given request
// with the given content-type offers. It will return a string representation
diff --git a/internal/api/wellknown.go b/internal/api/wellknown.go
index 7edbb4d7d..a837667fb 100644
--- a/internal/api/wellknown.go
+++ b/internal/api/wellknown.go
@@ -20,6 +20,7 @@ package api
import (
"github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/api/wellknown/hostmeta"
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
@@ -30,6 +31,7 @@ import (
type WellKnown struct {
nodeInfo *nodeinfo.Module
webfinger *webfinger.Module
+ hostMeta *hostmeta.Module
}
func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
@@ -45,11 +47,13 @@ func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
w.nodeInfo.Route(wellKnownGroup.Handle)
w.webfinger.Route(wellKnownGroup.Handle)
+ w.hostMeta.Route(wellKnownGroup.Handle)
}
func NewWellKnown(p *processing.Processor) *WellKnown {
return &WellKnown{
nodeInfo: nodeinfo.New(p),
webfinger: webfinger.New(p),
+ hostMeta: hostmeta.New(p),
}
}
diff --git a/internal/api/wellknown/hostmeta/hostmeta.go b/internal/api/wellknown/hostmeta/hostmeta.go
new file mode 100644
index 000000000..17eb748ec
--- /dev/null
+++ b/internal/api/wellknown/hostmeta/hostmeta.go
@@ -0,0 +1,45 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package hostmeta
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/processing"
+)
+
+const (
+ HostMetaContentType = "application/xrd+xml"
+ HostMetaPath = "/host-meta"
+)
+
+type Module struct {
+ processor *processing.Processor
+}
+
+func New(processor *processing.Processor) *Module {
+ return &Module{
+ processor: processor,
+ }
+}
+
+func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
+ attachHandler(http.MethodGet, HostMetaPath, m.HostMetaGETHandler)
+}
diff --git a/internal/api/wellknown/hostmeta/hostmetaget.go b/internal/api/wellknown/hostmeta/hostmetaget.go
new file mode 100644
index 000000000..f45b2cf9c
--- /dev/null
+++ b/internal/api/wellknown/hostmeta/hostmetaget.go
@@ -0,0 +1,73 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package hostmeta
+
+import (
+ "bytes"
+ "encoding/xml"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+)
+
+// HostMetaGETHandler swagger:operation GET /.well-known/host-meta hostMetaGet
+//
+// Returns a compliant hostmeta response to web host metadata queries.
+//
+// See: https://www.rfc-editor.org/rfc/rfc6415.html
+//
+// ---
+// tags:
+// - .well-known
+//
+// produces:
+// - application/xrd+xml"
+//
+// responses:
+// '200':
+// schema:
+// "$ref": "#/definitions/hostmeta"
+func (m *Module) HostMetaGETHandler(c *gin.Context) {
+ if _, err := apiutil.NegotiateAccept(c, apiutil.HostMetaHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ hostMeta := m.processor.Fedi().HostMetaGet()
+
+ // this setup with a separate buffer we encode into is used because
+ // xml.Marshal does not emit xml.Header by itself
+ var buf bytes.Buffer
+
+ // Preallocate buffer of reasonable length.
+ buf.Grow(len(xml.Header) + 64)
+
+ // No need to check for error on write to buffer.
+ _, _ = buf.WriteString(xml.Header)
+
+ // Encode host-meta as XML to in-memory buffer.
+ if err := xml.NewEncoder(&buf).Encode(hostMeta); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
+ return
+ }
+
+ c.Data(http.StatusOK, HostMetaContentType, buf.Bytes())
+}
diff --git a/internal/processing/fedi/wellknown.go b/internal/processing/fedi/wellknown.go
index 6f113ac5d..7be75649b 100644
--- a/internal/processing/fedi/wellknown.go
+++ b/internal/processing/fedi/wellknown.go
@@ -28,6 +28,10 @@ import (
)
const (
+ hostMetaXMLNS = "http://docs.oasis-open.org/ns/xri/xrd-1.0"
+ hostMetaRel = "lrdd"
+ hostMetaType = "application/xrd+xml"
+ hostMetaTemplate = ".well-known/webfinger?resource={uri}"
nodeInfoVersion = "2.0"
nodeInfoSoftwareName = "gotosocial"
nodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/" + nodeInfoVersion
@@ -96,6 +100,22 @@ func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserr
}, nil
}
+// HostMetaGet returns a host-meta struct in response to a host-meta request.
+func (p *Processor) HostMetaGet() *apimodel.HostMeta {
+ protocol := config.GetProtocol()
+ host := config.GetHost()
+ return &apimodel.HostMeta{
+ XMLNS: hostMetaXMLNS,
+ Link: []apimodel.Link{
+ {
+ Rel: hostMetaRel,
+ Type: hostMetaType,
+ Template: fmt.Sprintf("%s://%s/%s", protocol, host, hostMetaTemplate),
+ },
+ },
+ }
+}
+
// WebfingerGet handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
func (p *Processor) WebfingerGet(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) {
// Get the local account the request is referring to.