Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
until the handler finishes. ([#1384])

### Fixed
- `HTTP.sniff`, and `servecontent` without a `content_type`, now compile under
`juliac --trim=safe`. Content sniffing no longer dispatches at runtime.
- Requests with a zero-length streaming body can be copied for retries and
redirects. The first send retains and closes the producer; later sends replay
the empty payload without trying to clone the producer.
Expand Down
15 changes: 10 additions & 5 deletions src/http_sniff.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Content sniffing helpers used by multipart/form request bodies.
# Content sniffing for `sniff`, multipart/form request bodies, and `servecontent`.

const _SNIFF_MAX_LENGTH = 512
const _SNIFF_WHITESPACE = Set{UInt8}([UInt8('\t'), UInt8('\n'), UInt8('\u000c'), UInt8('\r'), UInt8(' ')])
Expand Down Expand Up @@ -258,7 +258,10 @@ function _sniff_match(::_SniffJSONSig, data::AbstractVector{UInt8}, firstnonws::
return true
end

const _SNIFF_SIGNATURES = Any[
# Checked in order before `_SNIFF_FALLBACK_SIGNATURES`. Keep each list to at
# most four concrete types so inference union-splits the `_sniff_match` and
# `_sniff_content_type` calls into static dispatch (required by `juliac --trim`).
const _SNIFF_SIGNATURES = Union{_SniffHTMLSig,_SniffMasked,_SniffExact}[
_SniffHTMLSig(collect(codeunits("<!DOCTYPE HTML"))),
_SniffHTMLSig(collect(codeunits("<HTML"))),
_SniffHTMLSig(collect(codeunits("<HEAD"))),
Expand Down Expand Up @@ -316,11 +319,10 @@ const _SNIFF_SIGNATURES = Any[
_SniffExact(UInt8[0x52, 0x61, 0x72, 0x20, 0x1A, 0x07, 0x00], "application/x-rar-compressed"),
_SniffExact(UInt8[0x50, 0x4B, 0x03, 0x04], "application/zip"),
_SniffExact(UInt8[0x1F, 0x8B, 0x08], "application/x-gzip"),
_SniffMP4Sig(),
_SniffJSONSig(),
_SniffTextSig(),
]

const _SNIFF_FALLBACK_SIGNATURES = (_SniffMP4Sig(), _SniffJSONSig(), _SniffTextSig())

function sniff(data::AbstractString)::String
bytes = collect(codeunits(String(data)))
return sniff(bytes)
Expand All @@ -344,5 +346,8 @@ function sniff(data::AbstractVector{UInt8})::String
for sig in _SNIFF_SIGNATURES
_sniff_match(sig, bytes, firstnonws) && return _sniff_content_type(sig)
end
for sig in _SNIFF_FALLBACK_SIGNATURES
_sniff_match(sig, bytes, firstnonws) && return _sniff_content_type(sig)
end
return "application/octet-stream"
end
5 changes: 5 additions & 0 deletions test/http_trim_open_fileserver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ function run_http_trim_open_fileserver()::Nothing

dotted_route = handler(HT.Request("GET", "/gallery.v2"))
dotted_route.status == 404 || error("expected dotted route status 404")

# Without `content_type` or a typed `name`, servecontent sniffs the body.
sniffed = HT.servecontent(HT.Request("GET", "/"), collect(codeunits("<html><p>hi</p></html>")))
HT.header(sniffed, "Content-Type") == "text/html; charset=utf-8" || error("unexpected sniffed content type")
HT.sniff("{\"ok\":true}") == "application/json; charset=utf-8" || error("unexpected sniffed JSON content type")
finally
trim_shutdown_runtime()
end
Expand Down
Loading