MCPcopy Create free account
hub / github.com/Mnexa-AI/e2a / handleAttachmentDownload

Method handleAttachmentDownload

internal/httpapi/attachments.go:217–293  ·  view source on GitHub ↗

handleAttachmentDownload streams one attachment's bytes. It is a RAW chi route (not Huma, not bearer): the capability TOKEN authorizes the download, so the URL can be handed to a sandboxed tool without leaking the agent's credential — the same model as the HITL magic-link. The token binds message+in

(w http.ResponseWriter, r *http.Request)

Source from the content-addressed store, hash-verified

215// keyed by agent id), so a valid token can only stream the exact attachment it
216// was minted for.
217func (s *Server) handleAttachmentDownload(w http.ResponseWriter, r *http.Request) {
218 // Per-IP rate limit FIRST. This raw capability-token route sits OUTSIDE the
219 // Huma rate-limit middleware, and each accepted hit runs GetAgent +
220 // GetMessage + a full MIME re-parse — so throttle before any of that work to
221 // bound token-replay and index/message probing. Keyed by client IP (no bearer
222 // here); shares the per-IP convention of the registration/feedback limiters.
223 if s.deps.DownloadLimit != nil {
224 ok, retryAfter, limit, remaining, reset := s.deps.DownloadLimit(clientIP(r))
225 w.Header().Set("RateLimit-Limit", strconv.Itoa(limit))
226 w.Header().Set("RateLimit-Remaining", strconv.Itoa(remaining))
227 w.Header().Set("RateLimit-Reset", strconv.Itoa(reset))
228 if !ok {
229 secs := int(retryAfter.Round(time.Second).Seconds())
230 if secs < 1 {
231 secs = 1
232 }
233 w.Header().Set("Retry-After", strconv.Itoa(secs))
234 writeRawError(w, r, http.StatusTooManyRequests, "rate_limited", "rate limit exceeded",
235 map[string]any{"retry_after_seconds": secs})
236 return
237 }
238 }
239
240 email := identity.NormalizeEmail(chi.URLParam(r, "email"))
241 id := chi.URLParam(r, "id")
242 index, err := strconv.Atoi(chi.URLParam(r, "index"))
243 if err != nil || index < 0 {
244 http.Error(w, "invalid attachment index", http.StatusBadRequest)
245 return
246 }
247 token := r.URL.Query().Get("token")
248 if token == "" {
249 http.Error(w, "token query parameter required", http.StatusUnauthorized)
250 return
251 }
252 if s.deps.AttachmentStore == nil || s.deps.GetAgent == nil || s.deps.GetMessage == nil {
253 http.Error(w, "attachment download unavailable", http.StatusInternalServerError)
254 return
255 }
256 // Capability check: the token must authorize exactly this message+index.
257 if !s.deps.AttachmentStore.VerifyDownload(token, id, index) {
258 http.Error(w, "invalid or expired download token", http.StatusForbidden)
259 return
260 }
261 // Bind the message to the path agent (GetMessage is keyed by agent id), so a
262 // token can't be replayed against a path naming a different agent.
263 ag, err := s.deps.GetAgent(r.Context(), email)
264 if err != nil || ag == nil {
265 http.Error(w, "agent not found", http.StatusNotFound)
266 return
267 }
268 msg, err := s.deps.GetMessage(r.Context(), id, ag.ID)
269 if err != nil || msg == nil {
270 http.Error(w, "message not found", http.StatusNotFound)
271 return
272 }
273 att, ok := mailparse.AttachmentAt(msg.RawMessage, index)
274 if !ok {

Callers

nothing calls this directly

Calls 9

NormalizeEmailFunction · 0.92
AttachmentAtFunction · 0.92
writeRawErrorFunction · 0.85
QueryMethod · 0.80
clientIPFunction · 0.70
GetMethod · 0.65
VerifyDownloadMethod · 0.65
ErrorMethod · 0.45
WriteMethod · 0.45

Tested by

no test coverage detected