Like [`Self::post_json`] but with a hard cap on the streamed response body in bytes, intended for endpoints that can legitimately return large payloads (`diff`). Two layers of defence: 1. If the server sends a `Content-Length` greater than `max_bytes`, the request is rejected before any body is consumed. 2. Otherwise the body is streamed; once the accumulated buffer exceeds `max_bytes`, the strea
(
&self,
op: &'static str,
body: &Req,
max_bytes: u64,
)
| 301 | /// Used by [`WorkspaceGit::diff`]; protects against a misbehaving |
| 302 | /// gitserver that ignores the client's soft `max_diff_bytes`. |
| 303 | async fn post_streamed<Req>( |
| 304 | &self, |
| 305 | op: &'static str, |
| 306 | body: &Req, |
| 307 | max_bytes: u64, |
| 308 | ) -> Result<Vec<u8>> |
| 309 | where |
| 310 | Req: Serialize + ?Sized, |
| 311 | { |
| 312 | use futures::StreamExt; |
| 313 | |
| 314 | let url = self.endpoint(op); |
| 315 | let mut req = self.http.post(&url).json(body); |
| 316 | if let Some(token) = self.bearer_token.as_deref() { |
| 317 | if !token.is_empty() { |
| 318 | req = req.bearer_auth(token); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | let start = std::time::Instant::now(); |
| 323 | let send_result = req.send().await; |
| 324 | let status_code = send_result.as_ref().ok().map(|r| r.status().as_u16()); |
| 325 | let resp = match send_result { |
| 326 | Ok(r) => r, |
| 327 | Err(e) => { |
| 328 | emit_remote_git_event(op, &self.repo_id, status_code, false, start.elapsed(), None); |
| 329 | return Err(anyhow!("remote git call '{}' transport error: {}", op, e)); |
| 330 | } |
| 331 | }; |
| 332 | |
| 333 | // Layer 1: eager rejection on advertised oversized body. |
| 334 | if let Some(len) = resp.content_length() { |
| 335 | if len > max_bytes { |
| 336 | emit_remote_git_event( |
| 337 | op, |
| 338 | &self.repo_id, |
| 339 | status_code, |
| 340 | false, |
| 341 | start.elapsed(), |
| 342 | Some(len), |
| 343 | ); |
| 344 | return Err(anyhow!( |
| 345 | "remote git '{}' Content-Length {} exceeds client cap {} bytes; \ |
| 346 | refusing to download. Raise max_diff_bytes if the body is legitimate.", |
| 347 | op, |
| 348 | len, |
| 349 | max_bytes |
| 350 | )); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | // Layer 2: stream-bound accumulation. |
| 355 | let status = resp.status(); |
| 356 | let mut stream = resp.bytes_stream(); |
| 357 | let mut buf: Vec<u8> = Vec::new(); |
| 358 | while let Some(chunk) = stream.next().await { |
| 359 | let chunk = chunk.map_err(|e| anyhow!("remote git '{}' stream error: {}", op, e))?; |
| 360 | if (buf.len() as u64).saturating_add(chunk.len() as u64) > max_bytes { |