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.
 
 
 
 
 

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