Repository navigation
Conversation
Compressed TUS uploads currently get decompressed and recompressed at forwarding hops. Preserve their bytes and Content-Encoding until they reach the Relay writing to objectstore. Select decompression during route initialization based on objectstore availability. Keep decoded-size validation, upload offsets, signed attachment lengths, and attachment-byte accounting at the storage Relay. Other routes retain decompression. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Remove the duplicated test router and codec helper. Cover routing, encoding rejection, normalization, and malformed passthrough through actual Relay instances; retain direct classifier and Tower response parity tests. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Preserve both changelog entries and add the upload PR reference. Co-authored-by: Codex <noreply@openai.com>
Keep HEAD, PATCH, and the decompressed method fallback together. Register the complete upload route after the other public routes receive decompression, removing the trailing router merge. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Select passthrough or configured encoding before constructing the upstream request. Read the encoding config only for decoded bodies, and pass the prepared stream and encoding to the request constructor. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Supply the decoded-body marker directly at route initialization, matching the forwarding adapter and removing the redundant handler wrapper. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Clarify that compression overhead can increase payload size and that the decoded-size limit is enforced upstream, with no separate compressed-byte cap. Co-authored-by: Codex <noreply@openai.com>
Keep passthrough bytes, headers, and retry-state assertions local. Leave multi-hop forwarding and final decompression to the existing integration tests. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Convert delivery and upload failures in a small helper using await??. Keep the PATCH handler focused on the operation and preserve logging for both error paths. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Apply max_upload_size to incoming bytes at forwarding hops and retain decoded-size checks at the objectstore Relay. Remove the unlimited compressed-body branch and cover wire and decoded rejection for fixed-length and chunked requests. BREAKING CHANGE: Compressed TUS requests exceeding max_upload_size are rejected by forwarding Relays even when their decoded bodies fit. Refs INGEST-1222 Co-authored-by: Codex <noreply@openai.com>
Use an explicit match for strict Content-Encoding parsing and explain whether upload limits count compressed or decoded bytes. Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0d7c0e5. Configure here.
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
jjbayer
left a comment
There was a problem hiding this comment.
Thanks for making the change, this looks like it's almost good to go!
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
8bc8011 to
d8220b1
Compare
| let stream = BoundedStream::new( | ||
| MeteredStream::new(stream, referrer), | ||
| 0, | ||
| config.max_upload_size(), |
There was a problem hiding this comment.
If the mode has a length / chunk_length, we could still use that as an upper bound instead of max_upload_size, wdyt? In practice a compressed stream should always be smaller than its decoded length.
Not a blocker because the objectstore relay enforces the bounds in the end.
There was a problem hiding this comment.
yes, should be smaller, not sure if that's always the case, maybe someone compresses twice or sends an encrypted blob ? even a short string due to overhead compresses to more bytes...
There was a problem hiding this comment.
Fair enough, double compression is a realistic scenario 👍
Let's keep max_upload_size then. Might be worth a code comment explaining why we cannot apply the declared length as an upper bound.
| let compression = match HttpCompression::from_encoding(encoding) { | ||
| None => return Either::Right(stream), | ||
| Some(compression) => compression, | ||
| }; |
There was a problem hiding this comment.
nit
| let compression = match HttpCompression::from_encoding(encoding) { | |
| None => return Either::Right(stream), | |
| Some(compression) => compression, | |
| }; | |
| let Some(compression) = HttpCompression::from_encoding(encoding) else { | |
| return Either::Right(stream); | |
| }; |
| use http_body_util::BodyExt; | ||
|
|
||
| let body = | ||
| http_body_util::StreamBody::new(stream.map(|result| result.map(hyper::body::Frame::data))); | ||
| let mut request = http::Request::new(body); | ||
| if let Some(codec) = compression { | ||
| request.headers_mut().insert( | ||
| http::header::CONTENT_ENCODING, | ||
| HeaderValue::from_static(codec.encoding().name().unwrap()), | ||
| ); | ||
| } | ||
| let request = crate::middlewares::decompress_request(request, UPLOAD_PATCH_PATH.into()) | ||
| .await | ||
| .expect("the upload encoding has already been validated"); | ||
| request | ||
| .into_body() | ||
| .into_data_stream() | ||
| .map(|result| result.map_err(std::io::Error::other)) | ||
| .boxed() |
There was a problem hiding this comment.
Creating an artificial request here seems a bit awkward. I get the idea of using the same decompression mechanism as for other endpoints, but I think it would be better here to do the reverse of encode_body, i.e. match on compression and call a DeflateDecoder, GzipDecoder, etc.
That would simplify the code and get rid of the .expect().
There was a problem hiding this comment.
yes, i agree, i stopped thinking after we stopped using tower when everything moved to upload.rs
will make sure to support the same behavior.
Select codecs directly in the objectstore upload path and share byte counters between HTTP bodies and streams. Preserve zlib-wrapped deflate, concatenated zstd frames, and reporting on EOF, error, or drop. Co-authored-by: Codex <noreply@openai.com>
Keep the reporter alive with the decoded body or stream instead of emitting at EOF or error. Replace optional reporters with typed guards and verify reporting when retained, partial, and unread streams drop. Co-authored-by: Codex <noreply@openai.com>
HTTP deflate requires a zlib wrapper. Replace the raw encoder and add a regression that decodes the emitted bytes with ZlibDecoder. Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Move the upload changelog entry back to Unreleased Internal after the 26.10.1 release. Co-authored-by: Codex <noreply@openai.com>
| let stream = BoundedStream::new( | ||
| MeteredStream::new(stream, referrer), | ||
| 0, | ||
| config.max_upload_size(), |
There was a problem hiding this comment.
Fair enough, double compression is a realistic scenario 👍
Let's keep max_upload_size then. Might be worth a code comment explaining why we cannot apply the declared length as an upper bound.
| use http_body_util::BodyExt; | ||
|
|
||
| let body = | ||
| http_body_util::StreamBody::new(stream.map(|result| result.map(hyper::body::Frame::data))); | ||
| let mut request = http::Request::new(body); | ||
| if let Some(codec) = compression { | ||
| request.headers_mut().insert( | ||
| http::header::CONTENT_ENCODING, | ||
| HeaderValue::from_static(codec.encoding().name().unwrap()), | ||
| ); | ||
| } | ||
| let request = crate::middlewares::decompress_request(request, UPLOAD_PATCH_PATH.into()) | ||
| .await | ||
| .expect("the upload encoding has already been validated"); | ||
| request | ||
| .into_body() | ||
| .into_data_stream() | ||
| .map(|result| result.map_err(std::io::Error::other)) | ||
| .boxed() |
Co-authored-by: Codex <noreply@openai.com>
Keep both upload forwarding and Unreal integration entries under Unreleased Internal. Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Apply CORS to the retained method fallback so OPTIONS requests receive CORS responses. Add coverage for preflight requests without a content encoding. Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Hold back the final decoded chunk until the input stream reaches EOF, so checksum and length errors cannot be skipped by the Objectstore client. Validate zero-length bodies before sending them. Co-authored-by: Codex <noreply@openai.com>
Compile the stream-completion helper only for processing builds and tests. Track its removal after the fixed Objectstore client release with INGEST-1348. Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
A codec can finish before its encoded source. Check the underlying reader before reporting EOF to reject trailing input and propagate late source errors. Co-authored-by: Codex <noreply@openai.com>

Preserve compressed TUS upload bytes and Content-Encoding when forwarding. Decode and enforce decoded-size limits in the Upload service’s objectstore branch, reusing the existing decompressor and byte counters.
Bound incoming request bytes at every hop. Record the decompression ratio when the input body finishes or is dropped.
Refs INGEST-1222
Follow-up: INGEST-1348 removes the stream-validation workaround after upgrading to a fixed Objectstore client release.