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.
 
 
 
 
 

875 lines
22 KiB

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