request.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package kinds
  2. import (
  3. "strings"
  4. "net/http"
  5. "net/url"
  6. "errors"
  7. "io/ioutil"
  8. "encoding/json"
  9. "fmt"
  10. )
  11. var client = &http.Client{}
  12. //var cache = TODO
  13. const requiredContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
  14. const optionalContentType = "application/activity+json"
  15. func Fetch(url *url.URL) (Content, error) {
  16. link := url.String()
  17. req, err := http.NewRequest("GET", link, nil) // `nil` is body
  18. if err != nil {
  19. return nil, err
  20. }
  21. // add the accept header, some servers only respond if the optional
  22. // content type is included as well
  23. // § 3.2
  24. req.Header.Add("Accept", fmt.Sprintf("%s, %s", requiredContentType, optionalContentType))
  25. resp, err := client.Do(req)
  26. if err != nil {
  27. return nil, err
  28. }
  29. defer resp.Body.Close()
  30. body, err := ioutil.ReadAll(resp.Body)
  31. if resp.StatusCode != 200 {
  32. return nil, errors.New("The server returned a status code of " + resp.Status)
  33. }
  34. // TODO: delete the pointless first if right here
  35. // TODO: for the sake of static servers, accept application/json
  36. // as well iff it contains the @context key (but double check
  37. // that it is absolutely necessary)
  38. if contentType := resp.Header.Get("Content-Type"); contentType == "" {
  39. return nil, errors.New("The server's response did not contain a content type")
  40. } else if !strings.Contains(contentType, requiredContentType) && !strings.Contains(contentType, optionalContentType) {
  41. return nil, errors.New("The server responded with the invalid content type of " + contentType)
  42. }
  43. var unstructured map[string]any
  44. if err := json.Unmarshal(body, &unstructured); err != nil {
  45. return nil, err
  46. }
  47. return Construct(unstructured, url)
  48. }
  49. func FetchWebFinger(username string) (Actor, error) {
  50. // description of WebFinger: https://www.rfc-editor.org/rfc/rfc7033.html
  51. username = strings.TrimPrefix(username, "@")
  52. split := strings.Split(username, "@")
  53. var account, domain string
  54. if len(split) != 2 {
  55. return nil, errors.New("webfinger address must have a separating @ symbol")
  56. } else {
  57. account = split[0]
  58. domain = split[1]
  59. }
  60. query := url.Values{}
  61. query.Add("resource", fmt.Sprintf("acct:%s@%s", account, domain))
  62. query.Add("rel", "self")
  63. link := url.URL{
  64. Scheme: "https",
  65. Host: domain,
  66. Path: "/.well-known/webfinger",
  67. RawQuery: query.Encode(),
  68. }
  69. req, err := http.NewRequest("GET", link.String(), nil) // `nil` is body
  70. if err != nil {
  71. return nil, err
  72. }
  73. resp, err := client.Do(req)
  74. if err != nil {
  75. return nil, err
  76. }
  77. defer resp.Body.Close()
  78. body, err := ioutil.ReadAll(resp.Body)
  79. if resp.StatusCode != 200 {
  80. return nil, errors.New(fmt.Sprintf("the server responded to the WebFinger query %s with %s", link.String(), resp.Status))
  81. } else if contentType := resp.Header.Get("Content-Type"); !strings.Contains(contentType, "application/jrd+json") && !strings.Contains(contentType, "application/json") {
  82. return nil, errors.New("the server responded to the WebFinger query with invalid Content-Type " + contentType)
  83. }
  84. var jrd Dict
  85. if err := json.Unmarshal(body, &jrd); err != nil {
  86. return nil, err
  87. }
  88. jrdLinks, err := GetList(jrd, "links")
  89. if err != nil {
  90. return nil, err
  91. }
  92. var underlyingLink *url.URL = nil
  93. for _, el := range jrdLinks {
  94. jrdLink, ok := el.(Dict)
  95. if ok {
  96. rel, err := Get[string](jrdLink, "rel")
  97. if err != nil { continue }
  98. if rel != "self" { continue }
  99. mediaType, err := Get[string](jrdLink, "type")
  100. if err != nil { continue }
  101. if !strings.Contains(mediaType, requiredContentType) && !strings.Contains(mediaType, optionalContentType) {
  102. continue
  103. }
  104. href, err := GetURL(jrdLink, "href")
  105. if err != nil { continue }
  106. underlyingLink = href
  107. break
  108. }
  109. }
  110. if underlyingLink == nil {
  111. return nil, errors.New("no matching href was found in the links array of " + link.String())
  112. }
  113. content, err := Fetch(underlyingLink)
  114. if err != nil { return nil, err }
  115. actor, ok := content.(Actor)
  116. if !ok { return nil, errors.New("content returned by the WebFinger request was not an Actor") }
  117. return actor, nil
  118. }
  119. func FetchUnknown(unknown string) (Content, error) {
  120. if strings.HasPrefix(unknown, "@") {
  121. return FetchWebFinger(unknown)
  122. }
  123. url, err := url.Parse(unknown)
  124. if err != nil {
  125. return nil, err
  126. }
  127. return Fetch(url)
  128. }