1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
package logic
import (
"bytes"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"github.com/go-fed/httpsig"
"io"
"net/http"
"rss_parrot/dto"
"rss_parrot/shared"
"strings"
"time"
)
type IActivitySender interface {
Send(privKey *rsa.PrivateKey, sendingUser, inboxUrl string, activity *dto.ActivityOut) error
}
const activityTimeoutSec = 10
type activitySender struct {
cfg *shared.Config
logger shared.ILogger
userAgent shared.IUserAgent
metrics IMetrics
idb shared.IdBuilder
}
func NewActivitySender(cfg *shared.Config,
logger shared.ILogger,
userAgent shared.IUserAgent,
metrics IMetrics,
) IActivitySender {
return &activitySender{cfg, logger, userAgent, metrics, shared.IdBuilder{Host: cfg.Host}}
}
func (sender *activitySender) Send(
privKey *rsa.PrivateKey,
sendingUser,
inboxUrl string,
activity *dto.ActivityOut,
) error {
obs := sender.metrics.StartApubRequestOut("post")
defer obs.Finish()
host := strings.Replace(inboxUrl, "https://", "", -1)
slashIx := strings.IndexByte(host, '/')
if slashIx == -1 {
return fmt.Errorf("invalid inbox url: %v", inboxUrl)
}
host = host[:slashIx]
bodyJson, _ := json.Marshal(activity)
dateStr := time.Now().UTC().Format(http.TimeFormat)
// DBG
//fmt.Println(string(bodyJson))
req, err := http.NewRequest("POST", inboxUrl, bytes.NewBuffer(bodyJson))
sender.userAgent.AddUserAgent(req)
req.Header.Set("Content-Type", "application/activity+json")
req.Header.Set("host", host)
req.Header.Set("date", dateStr)
signer, _, err := httpsig.NewSigner(
[]httpsig.Algorithm{httpsig.RSA_SHA256},
httpsig.DigestSha256,
[]string{httpsig.RequestTarget, "Host", "date", "digest"},
httpsig.Signature,
0)
if err != nil {
return err
}
keyId := sender.idb.UserKeyId(sendingUser)
err = signer.SignRequest(privKey, keyId, req, bodyJson)
if err != nil {
return err
}
client := http.Client{}
client.Timeout = time.Second * activityTimeoutSec
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
// DBG
//sender.logger.Debug(string(respBody))
if resp.StatusCode >= 300 {
msg := fmt.Sprintf("got status %s: response: %s", resp.Status, respBody)
sender.logger.Warnf("Activity POST failed to %s: %s", inboxUrl, msg)
return errors.New(msg)
}
return nil
}
|