mirror of
https://gitlab.melroy.org/melroy/fediresolve.git
synced 2025-06-07 20:08:57 +00:00
Try to implement HTTP Signure
This commit is contained in:
parent
d71faa3dd5
commit
dc32928e84
3 changed files with 74 additions and 81 deletions
2
go.mod
2
go.mod
|
@ -9,11 +9,13 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
)
|
||||
|
|
9
go.sum
9
go.sum
|
@ -1,6 +1,8 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
@ -20,9 +22,16 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
|||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -8,9 +8,6 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dennis/fediresolve/formatter"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Resolver handles the resolution of Fediverse URLs and handles
|
||||
|
@ -181,23 +178,75 @@ func (r *Resolver) resolveURL(inputURL string) (string, error) {
|
|||
// Parse the URL
|
||||
parsedURL, err := url.Parse(inputURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid URL: %v", err)
|
||||
return "", fmt.Errorf("error parsing URL: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the URL has a scheme
|
||||
if parsedURL.Scheme == "" {
|
||||
inputURL = "https://" + inputURL
|
||||
parsedURL, err = url.Parse(inputURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid URL: %v", err)
|
||||
// Check if this is a cross-instance URL (e.g., https://mastodon.social/@user@another.instance/123)
|
||||
username := parsedURL.Path
|
||||
if len(username) > 0 && username[0] == '/' {
|
||||
username = username[1:]
|
||||
}
|
||||
|
||||
// Check if the username contains an @ symbol (indicating a cross-instance URL)
|
||||
if strings.HasPrefix(username, "@") && strings.Contains(username[1:], "@") {
|
||||
// This is a cross-instance URL
|
||||
fmt.Println("Detected cross-instance URL. Original instance:", strings.Split(username[1:], "/")[0])
|
||||
|
||||
// Extract the original instance, username, and post ID
|
||||
parts := strings.Split(username, "/")
|
||||
if len(parts) >= 2 {
|
||||
userParts := strings.Split(parts[0][1:], "@") // Remove the leading @ and split by @
|
||||
if len(userParts) == 2 {
|
||||
username := userParts[0]
|
||||
originalDomain := userParts[1]
|
||||
postID := parts[1]
|
||||
|
||||
fmt.Printf("Detected cross-instance URL. Original instance: %s, username: %s, post ID: %s\n",
|
||||
originalDomain, username, postID)
|
||||
|
||||
// Try different URL formats that might work for the original instance
|
||||
formats := []string{
|
||||
"https://%s/@%s/%s",
|
||||
"https://%s/users/%s/statuses/%s",
|
||||
"https://%s/notes/%s",
|
||||
"https://%s/notice/%s",
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
originalURL := fmt.Sprintf(format, originalDomain, username, postID)
|
||||
fmt.Printf("Attempting to fetch from original instance: %s\n", originalURL)
|
||||
|
||||
// Try to fetch directly first
|
||||
fmt.Printf("Trying with ActivityPub direct fetch: %s\n", originalURL)
|
||||
result, err := r.fetchActivityPubObject(originalURL)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If direct fetch fails and it's an auth error, try with HTTP signatures
|
||||
if strings.Contains(err.Error(), "401 Unauthorized") || strings.Contains(err.Error(), "403 Forbidden") {
|
||||
fmt.Printf("Direct fetch failed with auth error, trying with HTTP signatures: %s\n", originalURL)
|
||||
result, sigErr := r.fetchWithSignature(originalURL)
|
||||
if sigErr == nil {
|
||||
return result, nil
|
||||
}
|
||||
fmt.Printf("HTTP signatures fetch also failed: %v\n", sigErr)
|
||||
}
|
||||
// If this fails, continue trying other formats
|
||||
}
|
||||
|
||||
// If all formats fail, return the last error
|
||||
return "", fmt.Errorf("failed to fetch content from original instance %s: all URL formats tried", originalDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to fetch the ActivityPub object directly
|
||||
// If not a cross-instance URL, fetch the ActivityPub object directly
|
||||
return r.fetchActivityPubObject(inputURL)
|
||||
}
|
||||
|
||||
// fetchActivityPubObject fetches an ActivityPub object from a URL
|
||||
// This function now uses a signature-first approach by default
|
||||
func (r *Resolver) fetchActivityPubObject(objectURL string) (string, error) {
|
||||
fmt.Printf("Fetching ActivityPub object from: %s\n", objectURL)
|
||||
|
||||
|
@ -210,75 +259,8 @@ func (r *Resolver) fetchActivityPubObject(objectURL string) (string, error) {
|
|||
// Ensure the URL has a scheme
|
||||
if parsedURL.Scheme == "" {
|
||||
objectURL = "https://" + objectURL
|
||||
parsedURL, err = url.Parse(objectURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid URL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the request
|
||||
req, err := http.NewRequest("GET", objectURL, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
|
||||
// Set Accept headers to request ActivityPub data
|
||||
// Use multiple Accept headers to increase compatibility with different servers
|
||||
req.Header.Set("Accept", "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/json")
|
||||
req.Header.Set("User-Agent", "FediResolve/1.0 (https://github.com/dennis/fediresolve)")
|
||||
|
||||
// Perform the request
|
||||
fmt.Printf("Sending request with headers: %v\n", req.Header)
|
||||
resp, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error fetching ActivityPub data: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf("Received response with status: %s\n", resp.Status)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Try to read the error response body for debugging
|
||||
errorBody, _ := ioutil.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("ActivityPub request failed with status: %s\nResponse body: %s",
|
||||
resp.Status, string(errorBody))
|
||||
}
|
||||
|
||||
// Read and parse the ActivityPub response
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading ActivityPub response: %v", err)
|
||||
}
|
||||
|
||||
// Debug output
|
||||
fmt.Printf("ActivityPub response content type: %s\n", resp.Header.Get("Content-Type"))
|
||||
|
||||
// Check if the response is empty
|
||||
if len(body) == 0 {
|
||||
return "", fmt.Errorf("received empty response body")
|
||||
}
|
||||
|
||||
// Try to decode the JSON response
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(body, &data); err != nil {
|
||||
// If we can't parse as JSON, return the raw response for debugging
|
||||
return "", fmt.Errorf("error decoding ActivityPub response: %v\nResponse body: %s",
|
||||
err, string(body))
|
||||
}
|
||||
|
||||
// Check if this is a shared/forwarded object and we need to fetch the original
|
||||
jsonData, _ := json.Marshal(data)
|
||||
jsonStr := string(jsonData)
|
||||
|
||||
// Check for various ActivityPub types that might reference an original object
|
||||
if gjson.Get(jsonStr, "type").String() == "Announce" {
|
||||
// This is a boost/share, get the original object
|
||||
originalURL := gjson.Get(jsonStr, "object").String()
|
||||
if originalURL != "" && (strings.HasPrefix(originalURL, "http://") || strings.HasPrefix(originalURL, "https://")) {
|
||||
fmt.Printf("Found Announce, following original at: %s\n", originalURL)
|
||||
return r.fetchActivityPubObject(originalURL)
|
||||
}
|
||||
}
|
||||
|
||||
// Format the result
|
||||
return formatter.Format(data)
|
||||
// Use our signature-first approach by default
|
||||
return r.fetchActivityPubObjectWithSignature(objectURL)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue