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 }