diff options
author | 2023-11-27 15:02:52 +0100 | |
---|---|---|
committer | 2023-11-27 14:02:52 +0000 | |
commit | 5eddef6c9b66fd35dc9473578d4e1a3b1b8d7b08 (patch) | |
tree | ee67f8a683a351580bc0b6365067001c34ff1cc1 /internal/processing/admin/debug_apurl.go | |
parent | [performance] http response encoding / writing improvements (#2374) (diff) | |
download | gotosocial-5eddef6c9b66fd35dc9473578d4e1a3b1b8d7b08.tar.xz |
[feature] Add `/api/v1/admin/debug/apurl` endpoint (#2359)
Diffstat (limited to 'internal/processing/admin/debug_apurl.go')
-rw-r--r-- | internal/processing/admin/debug_apurl.go | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/processing/admin/debug_apurl.go b/internal/processing/admin/debug_apurl.go new file mode 100644 index 000000000..d308ff7eb --- /dev/null +++ b/internal/processing/admin/debug_apurl.go @@ -0,0 +1,126 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 admin + +import ( + "context" + "io" + "net/http" + "net/url" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/gtscontext" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// DebugAPUrl performs a GET to the given url, using the +// signature of the given admin account. The GET will +// have Accept set to the ActivityPub content types. +// +// Only urls with schema http or https are allowed. +// +// Calls to blocked domains are not allowed, not only +// because it's unfair to call them when they can't +// call us, but because it probably won't work anyway +// if they try to dereference the calling account. +// +// Errors returned from this function should be fairly +// verbose, to help with debugging. +func (p *Processor) DebugAPUrl( + ctx context.Context, + adminAcct *gtsmodel.Account, + urlStr string, +) (*apimodel.DebugAPUrlResponse, gtserror.WithCode) { + // Validate URL. + if urlStr == "" { + err := gtserror.New("empty URL") + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + url, err := url.Parse(urlStr) + if err != nil { + err := gtserror.Newf("invalid URL: %w", err) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + if url == nil || (url.Scheme != "http" && url.Scheme != "https") { + err = gtserror.New("invalid URL scheme, acceptable schemes are http or https") + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + // Ensure URL not blocked. + blocked, err := p.state.DB.IsDomainBlocked(ctx, url.Host) + if err != nil { + err = gtserror.Newf("db error checking for domain block: %w", err) + return nil, gtserror.NewErrorInternalError(err, err.Error()) + } + + if blocked { + err = gtserror.Newf("target domain %s is blocked", url.Host) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + // All looks fine. Prepare the transport and (signed) GET request. + tsport, err := p.transportController.NewTransportForUsername(ctx, adminAcct.Username) + if err != nil { + err = gtserror.Newf("error creating transport: %w", err) + return nil, gtserror.NewErrorInternalError(err, err.Error()) + } + + req, err := http.NewRequestWithContext( + // Caller will want a snappy + // response so don't retry. + gtscontext.SetFastFail(ctx), + http.MethodGet, urlStr, nil, + ) + if err != nil { + err = gtserror.Newf("error creating request: %w", err) + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + req.Header.Add("Accept", string(apiutil.AppActivityLDJSON)+","+string(apiutil.AppActivityJSON)) + req.Header.Add("Accept-Charset", "utf-8") + req.Header.Set("Host", url.Host) + + // Perform the HTTP request, + // and return everything. + rsp, err := tsport.GET(req) + if err != nil { + err = gtserror.Newf("error doing dereference: %w", err) + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + defer rsp.Body.Close() + + b, err := io.ReadAll(rsp.Body) + if err != nil { + err := gtserror.Newf("error reading response body bytes: %w", err) + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + debugResponse := &apimodel.DebugAPUrlResponse{ + RequestURL: urlStr, + RequestHeaders: req.Header, + ResponseHeaders: rsp.Header, + ResponseCode: rsp.StatusCode, + ResponseBody: string(b), + } + + return debugResponse, nil +} |