| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // 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/>. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | package transport | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-06-20 17:06:20 +00:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2024-07-30 11:58:31 +00:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2021-12-20 18:42:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2023-03-06 09:38:43 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/httpclient" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/transport/delivery" | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | func (t *transport) BatchDeliver(ctx context.Context, obj map[string]interface{}, recipients []*url.URL) error { | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 	var ( | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 		// accumulated delivery reqs. | 
					
						
							|  |  |  | 		reqs []*delivery.Delivery | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 		// accumulated preparation errs. | 
					
						
							|  |  |  | 		errs gtserror.MultiError | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Get current instance host info. | 
					
						
							|  |  |  | 		domain = config.GetAccountDomain() | 
					
						
							|  |  |  | 		host   = config.GetHost() | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Marshal object as JSON. | 
					
						
							|  |  |  | 	b, err := json.Marshal(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error marshaling json: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Extract object IDs. | 
					
						
							|  |  |  | 	actID := getActorID(obj) | 
					
						
							|  |  |  | 	objID := getObjectID(obj) | 
					
						
							|  |  |  | 	tgtID := getTargetID(obj) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, to := range recipients { | 
					
						
							|  |  |  | 		// Skip delivery to recipient if it is "us". | 
					
						
							|  |  |  | 		if to.Host == host || to.Host == domain { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Prepare http client request. | 
					
						
							|  |  |  | 		req, err := t.prepare(ctx, | 
					
						
							|  |  |  | 			actID, | 
					
						
							|  |  |  | 			objID, | 
					
						
							|  |  |  | 			tgtID, | 
					
						
							|  |  |  | 			b, | 
					
						
							|  |  |  | 			to, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			errs.Append(err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Append to request queue. | 
					
						
							|  |  |  | 		reqs = append(reqs, req) | 
					
						
							| 
									
										
										
										
											2022-04-18 17:17:05 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Push prepared request list to the delivery queue. | 
					
						
							|  |  |  | 	t.controller.state.Workers.Delivery.Queue.Push(reqs...) | 
					
						
							| 
									
										
										
										
											2022-04-18 17:17:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 	// Return combined err. | 
					
						
							|  |  |  | 	return errs.Combine() | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | func (t *transport) Deliver(ctx context.Context, obj map[string]interface{}, to *url.URL) error { | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 	// if 'to' host is our own, skip as we don't need to deliver to ourselves... | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	if to.Host == config.GetHost() || to.Host == config.GetAccountDomain() { | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Marshal object as JSON. | 
					
						
							|  |  |  | 	b, err := json.Marshal(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error marshaling json: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Prepare http client request. | 
					
						
							|  |  |  | 	req, err := t.prepare(ctx, | 
					
						
							|  |  |  | 		getActorID(obj), | 
					
						
							|  |  |  | 		getObjectID(obj), | 
					
						
							|  |  |  | 		getTargetID(obj), | 
					
						
							|  |  |  | 		b, | 
					
						
							|  |  |  | 		to, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Push prepared request to the delivery queue. | 
					
						
							|  |  |  | 	t.controller.state.Workers.Delivery.Queue.Push(req) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | // prepare will prepare a POST http.Request{} | 
					
						
							|  |  |  | // to recipient at 'to', wrapping in a queued | 
					
						
							|  |  |  | // request object with signing function. | 
					
						
							|  |  |  | func (t *transport) prepare( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	actorID string, | 
					
						
							|  |  |  | 	objectID string, | 
					
						
							|  |  |  | 	targetID string, | 
					
						
							|  |  |  | 	data []byte, | 
					
						
							|  |  |  | 	to *url.URL, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*delivery.Delivery, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Prepare POST signer. | 
					
						
							|  |  |  | 	sign := t.signPOST(data) | 
					
						
							| 
									
										
										
										
											2023-02-18 16:02:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-20 17:06:20 +00:00
										 |  |  | 	// Use *bytes.Reader for request body, | 
					
						
							|  |  |  | 	// as NewRequest() automatically will | 
					
						
							|  |  |  | 	// set .GetBody and content-length. | 
					
						
							|  |  |  | 	// (this handles necessary rewinding). | 
					
						
							|  |  |  | 	body := bytes.NewReader(data) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Update to-be-used request context with signing details. | 
					
						
							|  |  |  | 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID) | 
					
						
							|  |  |  | 	ctx = gtscontext.SetHTTPClientSignFunc(ctx, sign) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Prepare a new request with data body directed at URL. | 
					
						
							| 
									
										
										
										
											2024-06-20 17:06:20 +00:00
										 |  |  | 	r, err := http.NewRequestWithContext(ctx, "POST", to.String(), body) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 		return nil, gtserror.Newf("error preparing request: %w", err) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-20 17:06:20 +00:00
										 |  |  | 	// Set our predefined controller user-agent. | 
					
						
							|  |  |  | 	r.Header.Set("User-Agent", t.controller.userAgent) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Set the standard ActivityPub content-type + charset headers. | 
					
						
							|  |  |  | 	r.Header.Add("Content-Type", string(apiutil.AppActivityLDJSON)) | 
					
						
							|  |  |  | 	r.Header.Add("Accept-Charset", "utf-8") | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | 	// Validate the request before queueing for delivery. | 
					
						
							|  |  |  | 	if err := httpclient.ValidateRequest(r); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &delivery.Delivery{ | 
					
						
							|  |  |  | 		ActorID:  actorID, | 
					
						
							|  |  |  | 		ObjectID: objectID, | 
					
						
							|  |  |  | 		TargetID: targetID, | 
					
						
							|  |  |  | 		Request:  httpclient.WrapRequest(r), | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 11:58:31 +00:00
										 |  |  | func (t *transport) SignDelivery(dlv *delivery.Delivery) error { | 
					
						
							|  |  |  | 	if dlv.Request.GetBody == nil { | 
					
						
							|  |  |  | 		return gtserror.New("delivery request body not rewindable") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get a new copy of the request body. | 
					
						
							|  |  |  | 	body, err := dlv.Request.GetBody() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error getting request body: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Read body data into memory. | 
					
						
							|  |  |  | 	data, err := io.ReadAll(body) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error reading request body: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get signing function for POST data. | 
					
						
							|  |  |  | 	// (note that delivery is ALWAYS POST). | 
					
						
							|  |  |  | 	sign := t.signPOST(data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Extract delivery context. | 
					
						
							|  |  |  | 	ctx := dlv.Request.Context() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Update delivery request context with signing details. | 
					
						
							|  |  |  | 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID) | 
					
						
							|  |  |  | 	ctx = gtscontext.SetHTTPClientSignFunc(ctx, sign) | 
					
						
							|  |  |  | 	dlv.Request.Request = dlv.Request.Request.WithContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | // getObjectID extracts an object ID from 'serialized' ActivityPub object map. | 
					
						
							|  |  |  | func getObjectID(obj map[string]interface{}) string { | 
					
						
							|  |  |  | 	switch t := obj["object"].(type) { | 
					
						
							|  |  |  | 	case string: | 
					
						
							|  |  |  | 		return t | 
					
						
							|  |  |  | 	case map[string]interface{}: | 
					
						
							|  |  |  | 		id, _ := t["id"].(string) | 
					
						
							|  |  |  | 		return id | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | // getActorID extracts an actor ID from 'serialized' ActivityPub object map. | 
					
						
							|  |  |  | func getActorID(obj map[string]interface{}) string { | 
					
						
							|  |  |  | 	switch t := obj["actor"].(type) { | 
					
						
							|  |  |  | 	case string: | 
					
						
							|  |  |  | 		return t | 
					
						
							|  |  |  | 	case map[string]interface{}: | 
					
						
							|  |  |  | 		id, _ := t["id"].(string) | 
					
						
							|  |  |  | 		return id | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 10:45:35 +01:00
										 |  |  | // getTargetID extracts a target ID from 'serialized' ActivityPub object map. | 
					
						
							|  |  |  | func getTargetID(obj map[string]interface{}) string { | 
					
						
							|  |  |  | 	switch t := obj["target"].(type) { | 
					
						
							|  |  |  | 	case string: | 
					
						
							|  |  |  | 		return t | 
					
						
							|  |  |  | 	case map[string]interface{}: | 
					
						
							|  |  |  | 		id, _ := t["id"].(string) | 
					
						
							|  |  |  | 		return id | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-27 16:52:18 +02:00
										 |  |  | } |