This repository has no description
1package state
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "strings"
8
9 comatproto "github.com/bluesky-social/indigo/api/atproto"
10 "tangled.org/core/xrpc"
11)
12
13const maxBlobSize = 1_000_000
14
15func (s *State) MarkdownPreview(w http.ResponseWriter, r *http.Request) {
16 body := r.FormValue("body")
17 s.pages.MarkdownPreviewFragment(w, body)
18}
19
20// MarkupUpload proxies an image upload to the user's PDS via uploadBlob and
21// returns the blob ref plus a blob+at://<did>/<cid> URI. The browser can't call
22// uploadBlob directly (the DPoP key lives server-side), so this is the bridge.
23func (s *State) MarkupUpload(w http.ResponseWriter, r *http.Request) {
24 l := s.logger.With("handler", "MarkupUpload")
25
26 user := s.oauth.GetMultiAccountUser(r)
27 if user == nil {
28 writeUploadError(w, http.StatusUnauthorized, "not logged in")
29 return
30 }
31 l = l.With("did", user.Did)
32
33 contentType := r.Header.Get("Content-Type")
34 if !strings.HasPrefix(contentType, "image/") {
35 writeUploadError(w, http.StatusUnsupportedMediaType, "only image uploads are allowed")
36 return
37 }
38
39 // cap the body at the lexicon's maxSize (MaxBytesReader errors past it)
40 r.Body = http.MaxBytesReader(w, r.Body, maxBlobSize)
41 defer r.Body.Close()
42
43 client, err := s.oauth.AuthorizedClient(r)
44 if err != nil {
45 l.Error("failed to get authorized client", "err", err)
46 writeUploadError(w, http.StatusBadGateway, "failed to connect to your PDS")
47 return
48 }
49
50 // pre-warm DPoP nonce
51 if _, err := comatproto.ServerGetSession(r.Context(), client); err != nil {
52 l.Error("failed to pre-warm session", "err", err)
53 writeUploadError(w, http.StatusInternalServerError, "failed to pre-warm session")
54 return
55 }
56
57 resp, err := xrpc.RepoUploadBlob(r.Context(), client, r.Body, contentType)
58 if err != nil {
59 // MaxBytesReader's over-limit error surfaces through LexDo
60 if strings.Contains(err.Error(), "request body too large") {
61 l.Warn("upload exceeds size limit")
62 writeUploadError(w, http.StatusRequestEntityTooLarge, "image too large (max 1MB)")
63 return
64 }
65 l.Error("failed to upload blob", "err", err)
66 writeUploadError(w, http.StatusBadGateway, "failed to upload image to your PDS")
67 return
68 }
69
70 blob := resp.Blob
71 cid := blob.Ref.String()
72 l.Info("uploaded blob", "cid", cid, "size", blob.Size)
73
74 w.Header().Set("Content-Type", "application/json")
75 if err := json.NewEncoder(w).Encode(map[string]any{
76 "blob": blob,
77 "did": user.Did,
78 "uri": fmt.Sprintf("blob+at://%s/%s", user.Did, cid),
79 }); err != nil {
80 l.Error("failed to encode upload response", "err", err)
81 }
82}
83
84func writeUploadError(w http.ResponseWriter, status int, msg string) {
85 w.Header().Set("Content-Type", "application/json")
86 w.WriteHeader(status)
87 _ = json.NewEncoder(w).Encode(map[string]string{"error": msg})
88}