A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

811 lines
21 KiB

  1. /*
  2. * Copyright © 2018-2020 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "bytes"
  13. "crypto/sha256"
  14. "database/sql"
  15. "encoding/base64"
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "net/http/httputil"
  21. "net/url"
  22. "strconv"
  23. "time"
  24. "github.com/gorilla/mux"
  25. "github.com/writeas/activity/streams"
  26. "github.com/writeas/httpsig"
  27. "github.com/writeas/impart"
  28. "github.com/writeas/nerds/store"
  29. "github.com/writeas/web-core/activitypub"
  30. "github.com/writeas/web-core/activitystreams"
  31. "github.com/writeas/web-core/log"
  32. )
  33. const (
  34. // TODO: delete. don't use this!
  35. apCustomHandleDefault = "blog"
  36. apCacheTime = time.Minute
  37. )
  38. type RemoteUser struct {
  39. ID int64
  40. ActorID string
  41. Inbox string
  42. SharedInbox string
  43. Handle string
  44. }
  45. func (ru *RemoteUser) AsPerson() *activitystreams.Person {
  46. return &activitystreams.Person{
  47. BaseObject: activitystreams.BaseObject{
  48. Type: "Person",
  49. Context: []interface{}{
  50. activitystreams.Namespace,
  51. },
  52. ID: ru.ActorID,
  53. },
  54. Inbox: ru.Inbox,
  55. Endpoints: activitystreams.Endpoints{
  56. SharedInbox: ru.SharedInbox,
  57. },
  58. }
  59. }
  60. func activityPubClient() *http.Client {
  61. return &http.Client{
  62. Timeout: 15 * time.Second,
  63. }
  64. }
  65. func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Request) error {
  66. w.Header().Set("Server", serverSoftware)
  67. vars := mux.Vars(r)
  68. alias := vars["alias"]
  69. // TODO: enforce visibility
  70. // Get base Collection data
  71. var c *Collection
  72. var err error
  73. if app.cfg.App.SingleUser {
  74. c, err = app.db.GetCollectionByID(1)
  75. } else {
  76. c, err = app.db.GetCollection(alias)
  77. }
  78. if err != nil {
  79. return err
  80. }
  81. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  82. if err != nil {
  83. log.Error("fetch collection activities: %v", err)
  84. return ErrInternalGeneral
  85. }
  86. if silenced {
  87. return ErrCollectionNotFound
  88. }
  89. c.hostName = app.cfg.App.Host
  90. p := c.PersonObject()
  91. setCacheControl(w, apCacheTime)
  92. return impart.RenderActivityJSON(w, p, http.StatusOK)
  93. }
  94. func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Request) error {
  95. w.Header().Set("Server", serverSoftware)
  96. vars := mux.Vars(r)
  97. alias := vars["alias"]
  98. // TODO: enforce visibility
  99. // Get base Collection data
  100. var c *Collection
  101. var err error
  102. if app.cfg.App.SingleUser {
  103. c, err = app.db.GetCollectionByID(1)
  104. } else {
  105. c, err = app.db.GetCollection(alias)
  106. }
  107. if err != nil {
  108. return err
  109. }
  110. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  111. if err != nil {
  112. log.Error("fetch collection outbox: %v", err)
  113. return ErrInternalGeneral
  114. }
  115. if silenced {
  116. return ErrCollectionNotFound
  117. }
  118. c.hostName = app.cfg.App.Host
  119. if app.cfg.App.SingleUser {
  120. if alias != c.Alias {
  121. return ErrCollectionNotFound
  122. }
  123. }
  124. res := &CollectionObj{Collection: *c}
  125. app.db.GetPostsCount(res, false)
  126. accountRoot := c.FederatedAccount()
  127. page := r.FormValue("page")
  128. p, err := strconv.Atoi(page)
  129. if err != nil || p < 1 {
  130. // Return outbox
  131. oc := activitystreams.NewOrderedCollection(accountRoot, "outbox", res.TotalPosts)
  132. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  133. }
  134. // Return outbox page
  135. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
  136. ocp.OrderedItems = []interface{}{}
  137. posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
  138. for _, pp := range *posts {
  139. pp.Collection = res
  140. o := pp.ActivityObject(app)
  141. a := activitystreams.NewCreateActivity(o)
  142. ocp.OrderedItems = append(ocp.OrderedItems, *a)
  143. }
  144. setCacheControl(w, apCacheTime)
  145. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  146. }
  147. func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Request) error {
  148. w.Header().Set("Server", serverSoftware)
  149. vars := mux.Vars(r)
  150. alias := vars["alias"]
  151. // TODO: enforce visibility
  152. // Get base Collection data
  153. var c *Collection
  154. var err error
  155. if app.cfg.App.SingleUser {
  156. c, err = app.db.GetCollectionByID(1)
  157. } else {
  158. c, err = app.db.GetCollection(alias)
  159. }
  160. if err != nil {
  161. return err
  162. }
  163. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  164. if err != nil {
  165. log.Error("fetch collection followers: %v", err)
  166. return ErrInternalGeneral
  167. }
  168. if silenced {
  169. return ErrCollectionNotFound
  170. }
  171. c.hostName = app.cfg.App.Host
  172. accountRoot := c.FederatedAccount()
  173. folls, err := app.db.GetAPFollowers(c)
  174. if err != nil {
  175. return err
  176. }
  177. page := r.FormValue("page")
  178. p, err := strconv.Atoi(page)
  179. if err != nil || p < 1 {
  180. // Return outbox
  181. oc := activitystreams.NewOrderedCollection(accountRoot, "followers", len(*folls))
  182. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  183. }
  184. // Return outbox page
  185. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "followers", len(*folls), p)
  186. ocp.OrderedItems = []interface{}{}
  187. /*
  188. for _, f := range *folls {
  189. ocp.OrderedItems = append(ocp.OrderedItems, f.ActorID)
  190. }
  191. */
  192. setCacheControl(w, apCacheTime)
  193. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  194. }
  195. func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Request) error {
  196. w.Header().Set("Server", serverSoftware)
  197. vars := mux.Vars(r)
  198. alias := vars["alias"]
  199. // TODO: enforce visibility
  200. // Get base Collection data
  201. var c *Collection
  202. var err error
  203. if app.cfg.App.SingleUser {
  204. c, err = app.db.GetCollectionByID(1)
  205. } else {
  206. c, err = app.db.GetCollection(alias)
  207. }
  208. if err != nil {
  209. return err
  210. }
  211. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  212. if err != nil {
  213. log.Error("fetch collection following: %v", err)
  214. return ErrInternalGeneral
  215. }
  216. if silenced {
  217. return ErrCollectionNotFound
  218. }
  219. c.hostName = app.cfg.App.Host
  220. accountRoot := c.FederatedAccount()
  221. page := r.FormValue("page")
  222. p, err := strconv.Atoi(page)
  223. if err != nil || p < 1 {
  224. // Return outbox
  225. oc := activitystreams.NewOrderedCollection(accountRoot, "following", 0)
  226. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  227. }
  228. // Return outbox page
  229. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "following", 0, p)
  230. ocp.OrderedItems = []interface{}{}
  231. setCacheControl(w, apCacheTime)
  232. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  233. }
  234. func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request) error {
  235. w.Header().Set("Server", serverSoftware)
  236. vars := mux.Vars(r)
  237. alias := vars["alias"]
  238. var c *Collection
  239. var err error
  240. if app.cfg.App.SingleUser {
  241. c, err = app.db.GetCollectionByID(1)
  242. } else {
  243. c, err = app.db.GetCollection(alias)
  244. }
  245. if err != nil {
  246. // TODO: return Reject?
  247. return err
  248. }
  249. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  250. if err != nil {
  251. log.Error("fetch collection inbox: %v", err)
  252. return ErrInternalGeneral
  253. }
  254. if silenced {
  255. return ErrCollectionNotFound
  256. }
  257. c.hostName = app.cfg.App.Host
  258. if debugging {
  259. dump, err := httputil.DumpRequest(r, true)
  260. if err != nil {
  261. log.Error("Can't dump: %v", err)
  262. } else {
  263. log.Info("Rec'd! %q", dump)
  264. }
  265. }
  266. var m map[string]interface{}
  267. if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
  268. return err
  269. }
  270. a := streams.NewAccept()
  271. p := c.PersonObject()
  272. var to *url.URL
  273. var isFollow, isUnfollow bool
  274. fullActor := &activitystreams.Person{}
  275. var remoteUser *RemoteUser
  276. res := &streams.Resolver{
  277. FollowCallback: func(f *streams.Follow) error {
  278. isFollow = true
  279. // 1) Use the Follow concrete type here
  280. // 2) Errors are propagated to res.Deserialize call below
  281. m["@context"] = []string{activitystreams.Namespace}
  282. b, _ := json.Marshal(m)
  283. if debugging {
  284. log.Info("Follow: %s", b)
  285. }
  286. _, followID := f.GetId()
  287. if followID == nil {
  288. log.Error("Didn't resolve follow ID")
  289. } else {
  290. aID := c.FederatedAccount() + "#accept-" + store.GenerateFriendlyRandomString(20)
  291. acceptID, err := url.Parse(aID)
  292. if err != nil {
  293. log.Error("Couldn't parse generated Accept URL '%s': %v", aID, err)
  294. }
  295. a.SetId(acceptID)
  296. }
  297. a.AppendObject(f.Raw())
  298. _, to = f.GetActor(0)
  299. obj := f.Raw().GetObjectIRI(0)
  300. a.AppendActor(obj)
  301. // First get actor information
  302. if to == nil {
  303. return fmt.Errorf("No valid `to` string")
  304. }
  305. fullActor, remoteUser, err = getActor(app, to.String())
  306. if err != nil {
  307. return err
  308. }
  309. return impart.RenderActivityJSON(w, m, http.StatusOK)
  310. },
  311. UndoCallback: func(u *streams.Undo) error {
  312. isUnfollow = true
  313. m["@context"] = []string{activitystreams.Namespace}
  314. b, _ := json.Marshal(m)
  315. if debugging {
  316. log.Info("Undo: %s", b)
  317. }
  318. a.AppendObject(u.Raw())
  319. _, to = u.GetActor(0)
  320. // TODO: get actor from object.object, not object
  321. obj := u.Raw().GetObjectIRI(0)
  322. a.AppendActor(obj)
  323. if to != nil {
  324. // Populate fullActor from DB?
  325. remoteUser, err = getRemoteUser(app, to.String())
  326. if err != nil {
  327. if iErr, ok := err.(*impart.HTTPError); ok {
  328. if iErr.Status == http.StatusNotFound {
  329. log.Error("No remoteuser info for Undo event!")
  330. }
  331. }
  332. return err
  333. } else {
  334. fullActor = remoteUser.AsPerson()
  335. }
  336. } else {
  337. log.Error("No to on Undo!")
  338. }
  339. return impart.RenderActivityJSON(w, m, http.StatusOK)
  340. },
  341. }
  342. if err := res.Deserialize(m); err != nil {
  343. // 3) Any errors from #2 can be handled, or the payload is an unknown type.
  344. log.Error("Unable to resolve Follow: %v", err)
  345. if debugging {
  346. log.Error("Map: %s", m)
  347. }
  348. return err
  349. }
  350. go func() {
  351. if to == nil {
  352. log.Error("No to! %v", err)
  353. return
  354. }
  355. time.Sleep(2 * time.Second)
  356. am, err := a.Serialize()
  357. if err != nil {
  358. log.Error("Unable to serialize Accept: %v", err)
  359. return
  360. }
  361. am["@context"] = []string{activitystreams.Namespace}
  362. err = makeActivityPost(app.cfg.App.Host, p, fullActor.Inbox, am)
  363. if err != nil {
  364. log.Error("Unable to make activity POST: %v", err)
  365. return
  366. }
  367. if isFollow {
  368. t, err := app.db.Begin()
  369. if err != nil {
  370. log.Error("Unable to start transaction: %v", err)
  371. return
  372. }
  373. var followerID int64
  374. if remoteUser != nil {
  375. followerID = remoteUser.ID
  376. } else {
  377. // Add follower locally, since it wasn't found before
  378. res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox)
  379. if err != nil {
  380. // if duplicate key, res will be nil and panic on
  381. // res.LastInsertId below
  382. t.Rollback()
  383. log.Error("Couldn't add new remoteuser in DB: %v\n", err)
  384. return
  385. }
  386. followerID, err = res.LastInsertId()
  387. if err != nil {
  388. t.Rollback()
  389. log.Error("no lastinsertid for followers, rolling back: %v", err)
  390. return
  391. }
  392. // Add in key
  393. _, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, followerID, fullActor.PublicKey.PublicKeyPEM)
  394. if err != nil {
  395. if !app.db.isDuplicateKeyErr(err) {
  396. t.Rollback()
  397. log.Error("Couldn't add follower keys in DB: %v\n", err)
  398. return
  399. }
  400. }
  401. }
  402. // Add follow
  403. _, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", c.ID, followerID)
  404. if err != nil {
  405. if !app.db.isDuplicateKeyErr(err) {
  406. t.Rollback()
  407. log.Error("Couldn't add follower in DB: %v\n", err)
  408. return
  409. }
  410. }
  411. err = t.Commit()
  412. if err != nil {
  413. t.Rollback()
  414. log.Error("Rolling back after Commit(): %v\n", err)
  415. return
  416. }
  417. } else if isUnfollow {
  418. // Remove follower locally
  419. _, err = app.db.Exec("DELETE FROM remotefollows WHERE collection_id = ? AND remote_user_id = (SELECT id FROM remoteusers WHERE actor_id = ?)", c.ID, to.String())
  420. if err != nil {
  421. log.Error("Couldn't remove follower from DB: %v\n", err)
  422. }
  423. }
  424. }()
  425. return nil
  426. }
  427. func makeActivityPost(hostName string, p *activitystreams.Person, url string, m interface{}) error {
  428. log.Info("POST %s", url)
  429. b, err := json.Marshal(m)
  430. if err != nil {
  431. return err
  432. }
  433. r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
  434. r.Header.Add("Content-Type", "application/activity+json")
  435. r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
  436. h := sha256.New()
  437. h.Write(b)
  438. r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
  439. // Sign using the 'Signature' header
  440. privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
  441. if err != nil {
  442. return err
  443. }
  444. signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
  445. err = signer.SignSigHeader(r)
  446. if err != nil {
  447. log.Error("Can't sign: %v", err)
  448. }
  449. if debugging {
  450. dump, err := httputil.DumpRequestOut(r, true)
  451. if err != nil {
  452. log.Error("Can't dump: %v", err)
  453. } else {
  454. log.Info("%s", dump)
  455. }
  456. }
  457. resp, err := activityPubClient().Do(r)
  458. if err != nil {
  459. return err
  460. }
  461. if resp != nil && resp.Body != nil {
  462. defer resp.Body.Close()
  463. }
  464. body, err := ioutil.ReadAll(resp.Body)
  465. if err != nil {
  466. return err
  467. }
  468. if debugging {
  469. log.Info("Status : %s", resp.Status)
  470. log.Info("Response: %s", body)
  471. }
  472. return nil
  473. }
  474. func resolveIRI(hostName, url string) ([]byte, error) {
  475. log.Info("GET %s", url)
  476. r, _ := http.NewRequest("GET", url, nil)
  477. r.Header.Add("Accept", "application/activity+json")
  478. r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
  479. if debugging {
  480. dump, err := httputil.DumpRequestOut(r, true)
  481. if err != nil {
  482. log.Error("Can't dump: %v", err)
  483. } else {
  484. log.Info("%s", dump)
  485. }
  486. }
  487. resp, err := activityPubClient().Do(r)
  488. if err != nil {
  489. return nil, err
  490. }
  491. if resp != nil && resp.Body != nil {
  492. defer resp.Body.Close()
  493. }
  494. body, err := ioutil.ReadAll(resp.Body)
  495. if err != nil {
  496. return nil, err
  497. }
  498. if debugging {
  499. log.Info("Status : %s", resp.Status)
  500. log.Info("Response: %s", body)
  501. }
  502. return body, nil
  503. }
  504. func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
  505. if debugging {
  506. log.Info("Deleting federated post!")
  507. }
  508. p.Collection.hostName = app.cfg.App.Host
  509. actor := p.Collection.PersonObject(collID)
  510. na := p.ActivityObject(app)
  511. // Add followers
  512. p.Collection.ID = collID
  513. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  514. if err != nil {
  515. log.Error("Couldn't delete post (get followers)! %v", err)
  516. return err
  517. }
  518. inboxes := map[string][]string{}
  519. for _, f := range *followers {
  520. inbox := f.SharedInbox
  521. if inbox == "" {
  522. inbox = f.Inbox
  523. }
  524. if _, ok := inboxes[inbox]; ok {
  525. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  526. } else {
  527. inboxes[inbox] = []string{f.ActorID}
  528. }
  529. }
  530. for si, instFolls := range inboxes {
  531. na.CC = []string{}
  532. for _, f := range instFolls {
  533. na.CC = append(na.CC, f)
  534. }
  535. err = makeActivityPost(app.cfg.App.Host, actor, si, activitystreams.NewDeleteActivity(na))
  536. if err != nil {
  537. log.Error("Couldn't delete post! %v", err)
  538. }
  539. }
  540. return nil
  541. }
  542. func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
  543. if debugging {
  544. if isUpdate {
  545. log.Info("Federating updated post!")
  546. } else {
  547. log.Info("Federating new post!")
  548. }
  549. }
  550. actor := p.Collection.PersonObject(collID)
  551. na := p.ActivityObject(app)
  552. // Add followers
  553. p.Collection.ID = collID
  554. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  555. if err != nil {
  556. log.Error("Couldn't post! %v", err)
  557. return err
  558. }
  559. log.Info("Followers for %d: %+v", collID, followers)
  560. inboxes := map[string][]string{}
  561. for _, f := range *followers {
  562. inbox := f.SharedInbox
  563. if inbox == "" {
  564. inbox = f.Inbox
  565. }
  566. if _, ok := inboxes[inbox]; ok {
  567. // check if we're already sending to this shared inbox
  568. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  569. } else {
  570. // add the new shared inbox to the list
  571. inboxes[inbox] = []string{f.ActorID}
  572. }
  573. }
  574. var activity *activitystreams.Activity
  575. // for each one of the shared inboxes
  576. for si, instFolls := range inboxes {
  577. // add all followers from that instance
  578. // to the CC field
  579. na.CC = []string{}
  580. for _, f := range instFolls {
  581. na.CC = append(na.CC, f)
  582. }
  583. // create a new "Create" activity
  584. // with our article as object
  585. if isUpdate {
  586. activity = activitystreams.NewUpdateActivity(na)
  587. } else {
  588. activity = activitystreams.NewCreateActivity(na)
  589. activity.To = na.To
  590. activity.CC = na.CC
  591. }
  592. // and post it to that sharedInbox
  593. err = makeActivityPost(app.cfg.App.Host, actor, si, activity)
  594. if err != nil {
  595. log.Error("Couldn't post! %v", err)
  596. }
  597. }
  598. // re-create the object so that the CC list gets reset and has
  599. // the mentioned users. This might seem wasteful but the code is
  600. // cleaner than adding the mentioned users to CC here instead of
  601. // in p.ActivityObject()
  602. na = p.ActivityObject(app)
  603. for _, tag := range na.Tag {
  604. if tag.Type == "Mention" {
  605. activity = activitystreams.NewCreateActivity(na)
  606. activity.To = na.To
  607. activity.CC = na.CC
  608. // This here might be redundant in some cases as we might have already
  609. // sent this to the sharedInbox of this instance above, but we need too
  610. // much logic to catch this at the expense of the odd extra request.
  611. // I don't believe we'd ever have too many mentions in a single post that this
  612. // could become a burden.
  613. remoteUser, err := getRemoteUser(app, tag.HRef)
  614. err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity)
  615. if err != nil {
  616. log.Error("Couldn't post! %v", err)
  617. }
  618. }
  619. }
  620. return nil
  621. }
  622. func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
  623. u := RemoteUser{ActorID: actorID}
  624. err := app.db.QueryRow("SELECT id, inbox, shared_inbox, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &u.Handle)
  625. switch {
  626. case err == sql.ErrNoRows:
  627. return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."}
  628. case err != nil:
  629. log.Error("Couldn't get remote user %s: %v", actorID, err)
  630. return nil, err
  631. }
  632. return &u, nil
  633. }
  634. // getRemoteUserFromHandle retrieves the profile page of a remote user
  635. // from the @user@server.tld handle
  636. func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) {
  637. u := RemoteUser{Handle: handle}
  638. err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox)
  639. switch {
  640. case err == sql.ErrNoRows:
  641. return nil, ErrRemoteUserNotFound
  642. case err != nil:
  643. log.Error("Couldn't get remote user %s: %v", handle, err)
  644. return nil, err
  645. }
  646. return &u, nil
  647. }
  648. func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser, error) {
  649. log.Info("Fetching actor %s locally", actorIRI)
  650. actor := &activitystreams.Person{}
  651. remoteUser, err := getRemoteUser(app, actorIRI)
  652. if err != nil {
  653. if iErr, ok := err.(impart.HTTPError); ok {
  654. if iErr.Status == http.StatusNotFound {
  655. // Fetch remote actor
  656. log.Info("Not found; fetching actor %s remotely", actorIRI)
  657. actorResp, err := resolveIRI(app.cfg.App.Host, actorIRI)
  658. if err != nil {
  659. log.Error("Unable to get actor! %v", err)
  660. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
  661. }
  662. if err := unmarshalActor(actorResp, actor); err != nil {
  663. log.Error("Unable to unmarshal actor! %v", err)
  664. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
  665. }
  666. } else {
  667. return nil, nil, err
  668. }
  669. } else {
  670. return nil, nil, err
  671. }
  672. } else {
  673. actor = remoteUser.AsPerson()
  674. }
  675. return actor, remoteUser, nil
  676. }
  677. // unmarshal actor normalizes the actor response to conform to
  678. // the type Person from github.com/writeas/web-core/activitysteams
  679. //
  680. // some implementations return different context field types
  681. // this converts any non-slice contexts into a slice
  682. func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error {
  683. // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
  684. // flexActor overrides the Context field to allow
  685. // all valid representations during unmarshal
  686. flexActor := struct {
  687. activitystreams.Person
  688. Context json.RawMessage `json:"@context,omitempty"`
  689. }{}
  690. if err := json.Unmarshal(actorResp, &flexActor); err != nil {
  691. return err
  692. }
  693. actor.Endpoints = flexActor.Endpoints
  694. actor.Followers = flexActor.Followers
  695. actor.Following = flexActor.Following
  696. actor.ID = flexActor.ID
  697. actor.Icon = flexActor.Icon
  698. actor.Inbox = flexActor.Inbox
  699. actor.Name = flexActor.Name
  700. actor.Outbox = flexActor.Outbox
  701. actor.PreferredUsername = flexActor.PreferredUsername
  702. actor.PublicKey = flexActor.PublicKey
  703. actor.Summary = flexActor.Summary
  704. actor.Type = flexActor.Type
  705. actor.URL = flexActor.URL
  706. func(val interface{}) {
  707. switch val.(type) {
  708. case []interface{}:
  709. // already a slice, do nothing
  710. actor.Context = val.([]interface{})
  711. default:
  712. actor.Context = []interface{}{val}
  713. }
  714. }(flexActor.Context)
  715. return nil
  716. }
  717. func setCacheControl(w http.ResponseWriter, ttl time.Duration) {
  718. w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", ttl.Seconds()))
  719. }