mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[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
This commit is contained in:
@@ -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"`
|
||||
|
@@ -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
|
||||
|
@@ -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),
|
||||
}
|
||||
}
|
||||
|
45
internal/api/wellknown/hostmeta/hostmeta.go
Normal file
45
internal/api/wellknown/hostmeta/hostmeta.go
Normal file
@@ -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)
|
||||
}
|
73
internal/api/wellknown/hostmeta/hostmetaget.go
Normal file
73
internal/api/wellknown/hostmeta/hostmetaget.go
Normal file
@@ -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())
|
||||
}
|
Reference in New Issue
Block a user