streamDownload serves the encrypted file as a binary stream.
(w http.ResponseWriter, r *http.Request)
| 169 | |
| 170 | // streamDownload serves the encrypted file as a binary stream. |
| 171 | func (y *Server) streamDownload(w http.ResponseWriter, r *http.Request) { |
| 172 | w.Header().Set("Cache-Control", "private, no-cache") |
| 173 | |
| 174 | key := mux.Vars(r)["key"] |
| 175 | session, sessionErr := y.getSession(r) |
| 176 | audit := y.newAuditor("file.downloaded", y.getRealClientIP(r), session) |
| 177 | audit.setSecretID(key) |
| 178 | |
| 179 | // Read metadata without consuming it (Status never deletes). |
| 180 | secret, err := y.DB.Status(streamKeyPrefix + key) |
| 181 | if err != nil { |
| 182 | y.Logger.Debug("Stream secret not found", zap.Error(err)) |
| 183 | audit.failure("not found") |
| 184 | jsonError(w, http.StatusNotFound, "Secret not found") |
| 185 | return |
| 186 | } |
| 187 | |
| 188 | if !y.authorizeSecretAccess(w, secret, session, sessionErr, audit) { |
| 189 | return |
| 190 | } |
| 191 | |
| 192 | isOneTime := secret.OneTime |
| 193 | |
| 194 | // For one-time secrets: atomically claim ownership by deleting the metadata |
| 195 | // key BEFORE loading the file. |
| 196 | if isOneTime && !y.claimOneTimeSecret(w, streamKeyPrefix+key, audit) { |
| 197 | return |
| 198 | } |
| 199 | |
| 200 | // Load file from store |
| 201 | ctx := r.Context() |
| 202 | reader, size, err := y.FileStore.Load(ctx, key) |
| 203 | if err != nil { |
| 204 | y.Logger.Error("Failed to load streaming file", zap.Error(err)) |
| 205 | // DB metadata exists but the file is gone — clean up the stale DB entry. |
| 206 | if !isOneTime { |
| 207 | if _, delErr := y.DB.Delete(streamKeyPrefix + key); delErr != nil { |
| 208 | y.Logger.Error("Failed to clean up stale stream metadata", zap.Error(delErr)) |
| 209 | } |
| 210 | } |
| 211 | audit.failure("file not found in store") |
| 212 | jsonError(w, http.StatusNotFound, "File not found") |
| 213 | return |
| 214 | } |
| 215 | defer reader.Close() |
| 216 | |
| 217 | // Set response headers |
| 218 | w.Header().Set("Content-Type", "application/octet-stream") |
| 219 | w.Header().Set("Access-Control-Expose-Headers", "Content-Length") |
| 220 | if size > 0 { |
| 221 | w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) |
| 222 | } |
| 223 | |
| 224 | // Stream the file |
| 225 | if _, err := io.Copy(w, reader); err != nil { |
| 226 | y.Logger.Error("Failed to stream file", zap.Error(err)) |
| 227 | return |
| 228 | } |