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.
 
 
 
 
 

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