gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

transport.go (5214B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package transport
     19 
     20 import (
     21 	"context"
     22 	"crypto"
     23 	"errors"
     24 	"io"
     25 	"net/http"
     26 	"net/url"
     27 	"sync"
     28 	"time"
     29 
     30 	"github.com/go-fed/httpsig"
     31 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
     32 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     33 	"github.com/superseriousbusiness/gotosocial/internal/httpclient"
     34 )
     35 
     36 // Transport implements the pub.Transport interface with some additional functionality for fetching remote media.
     37 //
     38 // Since the transport has the concept of 'shortcuts' for fetching data locally rather than remotely, it is
     39 // not *always* the case that calling a Transport function does an http call, but it usually will for remote
     40 // hosts or resources for which a shortcut isn't provided by the transport controller (also in this package).
     41 //
     42 // For any of the transport functions, if a Fastfail context is passed in as the first parameter, the function
     43 // will return after the first transport failure, instead of retrying + backing off.
     44 type Transport interface {
     45 	/*
     46 		POST functions
     47 	*/
     48 
     49 	// Deliver sends an ActivityStreams object.
     50 	Deliver(ctx context.Context, b []byte, to *url.URL) error
     51 
     52 	// BatchDeliver sends an ActivityStreams object to multiple recipients.
     53 	BatchDeliver(ctx context.Context, b []byte, recipients []*url.URL) error
     54 
     55 	/*
     56 		GET functions
     57 	*/
     58 
     59 	// Dereference fetches the ActivityStreams object located at this IRI with a GET request.
     60 	Dereference(ctx context.Context, iri *url.URL) ([]byte, error)
     61 
     62 	// DereferenceMedia fetches the given media attachment IRI, returning the reader and filesize.
     63 	DereferenceMedia(ctx context.Context, iri *url.URL) (io.ReadCloser, int64, error)
     64 
     65 	// DereferenceInstance dereferences remote instance information, first by checking /api/v1/instance, and then by checking /.well-known/nodeinfo.
     66 	DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error)
     67 
     68 	// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.
     69 	Finger(ctx context.Context, targetUsername string, targetDomain string) ([]byte, error)
     70 }
     71 
     72 // transport implements the Transport interface.
     73 type transport struct {
     74 	controller *controller
     75 	pubKeyID   string
     76 	privkey    crypto.PrivateKey
     77 
     78 	signerExp  time.Time
     79 	getSigner  httpsig.Signer
     80 	postSigner httpsig.Signer
     81 	signerMu   sync.Mutex
     82 }
     83 
     84 // GET will perform given http request using transport client, retrying on certain preset errors.
     85 func (t *transport) GET(r *http.Request) (*http.Response, error) {
     86 	if r.Method != http.MethodGet {
     87 		return nil, errors.New("must be GET request")
     88 	}
     89 	ctx := r.Context() // extract, set pubkey ID.
     90 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID)
     91 	r = r.WithContext(ctx) // replace request ctx.
     92 	r.Header.Set("User-Agent", t.controller.userAgent)
     93 	return t.controller.client.DoSigned(r, t.signGET())
     94 }
     95 
     96 // POST will perform given http request using transport client, retrying on certain preset errors.
     97 func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) {
     98 	if r.Method != http.MethodPost {
     99 		return nil, errors.New("must be POST request")
    100 	}
    101 	ctx := r.Context() // extract, set pubkey ID.
    102 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID)
    103 	r = r.WithContext(ctx) // replace request ctx.
    104 	r.Header.Set("User-Agent", t.controller.userAgent)
    105 	return t.controller.client.DoSigned(r, t.signPOST(body))
    106 }
    107 
    108 // signGET will safely sign an HTTP GET request.
    109 func (t *transport) signGET() httpclient.SignFunc {
    110 	return func(r *http.Request) (err error) {
    111 		t.safesign(func() {
    112 			err = t.getSigner.SignRequest(t.privkey, t.pubKeyID, r, nil)
    113 		})
    114 		return
    115 	}
    116 }
    117 
    118 // signPOST will safely sign an HTTP POST request for given body.
    119 func (t *transport) signPOST(body []byte) httpclient.SignFunc {
    120 	return func(r *http.Request) (err error) {
    121 		t.safesign(func() {
    122 			err = t.postSigner.SignRequest(t.privkey, t.pubKeyID, r, body)
    123 		})
    124 		return
    125 	}
    126 }
    127 
    128 // safesign will perform sign function within mutex protection,
    129 // and ensured that httpsig.Signers are up-to-date.
    130 func (t *transport) safesign(sign func()) {
    131 	// Perform within mu safety
    132 	t.signerMu.Lock()
    133 	defer t.signerMu.Unlock()
    134 
    135 	if now := time.Now(); now.After(t.signerExp) {
    136 		const expiry = 120
    137 
    138 		// Signers have expired and require renewal
    139 		t.getSigner, _ = NewGETSigner(expiry)
    140 		t.postSigner, _ = NewPOSTSigner(expiry)
    141 		t.signerExp = now.Add(time.Second * expiry)
    142 	}
    143 
    144 	// Perform signing
    145 	sign()
    146 }