Imported from shiguredo/http11-rs (
skills/shiguredo-http11/SKILL.md). Install upstream withnpx skills add shiguredo/http11-rs --skill shiguredo-http11. Copyright stays with the author.
shiguredo_http11
Sans I/O 設計に基づく HTTP/1.1 パーサー/シリアライザーライブラリ。
特徴
- 依存なし: 外部依存ゼロ (
core/allocのみ) no_std完全対応:std非依存 (allocクレートは必要)- Sans I/O: I/O を完全に分離した設計 (Tokio, async-std, 同期 I/O など任意の環境で使用可能)
- 柔軟性: HTTP/1.1 のほか RTSP/1.0, RTSP/2.0 等のテキストプロトコルにも応用可能
- ストリーミング対応: 大容量ボディをメモリ効率的に処理
- 圧縮トレイト:
Compressor/Decompressorで任意の実装を組み込み可能 (本体は実装を含まない) - RFC 準拠: RFC 9110, 9111, 9112 等の最新仕様に対応
- セキュリティ: Request Smuggling, Response Splitting 対策
バージョン情報
- crate 名:
shiguredo_http11 - バージョン: 2026.6.0
- Rust Edition: 2024
- 最小 Rust バージョン: 1.93
- ライセンス: Apache-2.0
公開 enum (全 Error 系・StatusClass / BodyKind / CompressionStatus / Authorization / AuthChallenge 等) には #[non_exhaustive] が付与されている。利用側の match には _ アームを置くこと。
コア API
構築型 (コンパイル時検査)
| 型 | 説明 | 主要 API |
|---|---|---|
HeaderName |
HTTP ヘッダー名 (RFC 9110 Section 5.1, field-name = token)。Eq/Hash は case-insensitive | from_static(b"Name") (const, コンパイル時検査), new(impl AsRef<[u8]>) (Result, ランタイム検査), as_str(), as_bytes(), TryFrom<&'static str> / TryFrom<&'static [u8]> (builder 用) |
HeaderNameError |
HeaderName の構築エラー |
Empty { input }, InvalidByte { byte, position, input }, input(), into_input() |
Method |
HTTP メソッド (RFC 9110 Section 9.1, method = token)。Eq/Hash は case-sensitive | from_static(b"METHOD") (const, コンパイル時検査), new(impl AsRef<[u8]>) (Result, ランタイム検査), as_str(), as_bytes(), TryFrom<&'static str> / TryFrom<&'static [u8]> (builder 用) |
MethodError |
Method の構築エラー |
Empty { input }, InvalidByte { byte, position, input }, input(), into_input() |
Scheme |
URI スキーム (RFC 3986 Section 3.1)。Eq/Hash は case-insensitive | from_static(b"scheme") (const, コンパイル時検査), new(impl AsRef<[u8]>) (Result, ランタイム検査), as_str(), as_bytes() |
SchemeError |
Scheme の構築エラー |
Empty, InvalidByte { byte, position } |
Method の標準定数: Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::HEAD, Method::OPTIONS, Method::CONNECT, Method::TRACE, Method::PATCH, Method::QUERY
Scheme の標準定数: Scheme::HTTP, Scheme::HTTPS, Scheme::WS, Scheme::WSS, Scheme::RTSP
from_static はバイトリテラル (b"...") を受け取る const fn であり、不正な入力はコンパイル時に panic する。ランタイムで動的に構築する場合は new() を使い Result をハンドリングする。
builder 用途では "GET" / "Host" 等の 'static str リテラルを直接渡せる (TryFrom<&'static str> による)。非 'static な &str はコンパイルエラーになるため、動的入力は new() で構築した値を渡す。
エンコード用 (送信側)
| 型 | 説明 | 主要メソッド |
|---|---|---|
Request |
HTTP リクエスト | new(Method, uri) (Result), with_version(Method, uri, version) (Result), header(HeaderName, value) (Result, builder), add_header(HeaderName, value) (Result, mutator, チェイン可), set_header(HeaderName, value) (Result, mutator, チェイン可), body() (builder), set_body() (mutator), clear_body() (mutator), without_body() (builder), body_bytes() (getter), method(), uri(), version(), encode() (Result), encode_headers() (Result), is_keep_alive(), is_chunked() |
Response |
HTTP レスポンス | new(status_code, reason_phrase) (Result), with_version(version, status_code, reason_phrase) (Result), with_status(StatusCode) (infallible), header(HeaderName, value) (Result, builder), add_header(HeaderName, value) (Result, mutator, チェイン可), set_header(HeaderName, value) (Result, mutator, チェイン可), body() (builder), set_body() (mutator), clear_body() (mutator), without_body() (builder), body_bytes() (getter), omit_body() (builder), set_omit_body() (mutator), is_body_omitted(), status_code(), reason_phrase(), encode() (Result), encode_headers() (Result), status_class(), is_keep_alive() |
StatusCode |
IANA 登録済み HTTP ステータスコード (const 値) | OK, CREATED, NO_CONTENT, NOT_MODIFIED, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR 等の const 定数, code(), canonical_reason(), class(), from_code(u16) (未登録コードは None) |
StatusClass |
RFC 9110 Section 15 のクラス分類 enum | Informational, Successful, Redirection, ClientError, ServerError, from_status_code(u16) (範囲外は None) |
RequestEncoder<C> |
圧縮対応リクエストエンコーダー | new(), with_compressor(), compress_body(), finish(), reset() |
ResponseEncoder<C> |
圧縮対応レスポンスエンコーダー | new(), with_compressor(), compress_body(), finish(), reset() |
encode() / encode_headers() は意味論違反 (Host 欠落、Content-Length 不一致、Transfer-Encoding と Content-Length の競合、1xx/204/205 へのボディ等) を Result<Vec<u8>, EncodeError> で返す。構築時バリデーションを通過した値でも encode 時に検出されるため、呼び出し側は ? 等で伝播する。
Request / Response のヘッダー名引数は "Host" 等の 'static str リテラル、HeaderName 型、のいずれかを渡せる (impl TryInto<HeaderName>)。メソッド引数は "GET" 等のリテラル、Method 定数 (Method::GET) のいずれかを渡せる (impl TryInto<Method>)。値引数 (value / uri / version / reason_phrase / body) は impl Into<String> / impl Into<Vec<u8>> を受け、String や Vec<u8> をムーブで渡せる。&str / &[u8] も従来どおり利用可能。
デコード用 (受信側)
| 型 | 説明 | 主要メソッド |
|---|---|---|
RequestDecoder<D> |
リクエストデコーダー | new(), with_limits(), with_decompressor(), with_decompressor_and_limits(), feed(), feed_unchecked(), mut_buf(), advance_buf(), available_buf(), decode(), decode_headers(), peek_body(), peek_body_decompressed(), consume_body(), progress(), remaining(), limits(), reset(), is_tunnel(), take_remaining() (CONNECT 用) |
ResponseDecoder<D> |
レスポンスデコーダー | 同上 + mark_eof(), is_close_delimited(), set_request_method() (HEAD/CONNECT 判定用のリクエストメソッドを設定) |
RequestHead |
デコード済みリクエストヘッダー | new(Method, uri) (Result), with_version(Method, uri, version) (Result), header(HeaderName, value) (Result, builder), add_header(HeaderName, value) (Result, mutator), method() / uri() / version() / headers() (参照 getter), into_method() / into_uri() / into_version() / into_headers() / into_parts() (消費メソッド) |
ResponseHead |
デコード済みレスポンスヘッダー | new(status_code, reason_phrase) (Result), with_version(version, status_code, reason_phrase) (Result), header(HeaderName, value) (Result, builder), add_header(HeaderName, value) (Result, mutator), status_code() / reason_phrase() / version() / headers() / status_class() (参照 getter), into_version() / into_reason_phrase() / into_headers() / into_parts() (消費メソッド) |
HttpHead |
ヘッダー操作トレイト (Request / Response / RequestHead / ResponseHead が実装) |
version(), headers(), get_header(), is_keep_alive(), is_chunked() |
request_target::RequestTargetForm |
request-target 形式 (encoder/decoder で共通) | Origin, Absolute, Authority, Asterisk |
HttpHead トレイト
Request / Response / RequestHead / ResponseHead が実装する共通トレイト。エンコーダー側とデコーダー側でヘッダー操作の一貫性を保証する。
| メソッド | 説明 |
|---|---|
version() |
HTTP バージョンを取得 |
headers() |
ヘッダーリストを取得 (&[(HeaderName, String)]) |
get_header(name) |
ヘッダーを取得 (大文字小文字を区別しない) |
get_headers(name) |
同名ヘッダーをすべて取得 |
has_header(name) |
ヘッダーの存在確認 |
connection() |
Connection ヘッダーを取得 |
is_keep_alive() |
キープアライブ接続か判定 |
content_length() |
Content-Length を取得 (Result<Option<u64>, Error>、複数値・不正値を検出) |
is_chunked() |
Transfer-Encoding の最後が chunked か判定 |
ボディ処理
| 型 | 説明 |
|---|---|
BodyKind::ContentLength(u64) |
Content-Length による固定長 |
BodyKind::Chunked |
Transfer-Encoding: chunked |
BodyKind::CloseDelimited |
接続終了まで (レスポンスのみ) |
BodyKind::Tunnel |
CONNECT 2xx レスポンス後のトンネルモード (Transfer-Encoding/Content-Length は無視) |
BodyKind::None |
ボディなし |
BodyProgress::Advanced |
状態機械が前進し、続けて呼び出すことで処理を進められる |
BodyProgress::NeedData |
追加データが必要。呼び出し側はループを抜けて I/O に戻る |
BodyProgress::Complete { trailers } |
完了 (トレーラーヘッダー含む) |
エンコーダー関数
| 関数 | 戻り値 | 説明 |
|---|---|---|
encode_request(&Request) |
Result<Vec<u8>, EncodeError> |
リクエスト全体をエンコード |
encode_response(&Response) |
Result<Vec<u8>, EncodeError> |
レスポンス全体をエンコード |
encode_request_headers(&Request) |
Result<Vec<u8>, EncodeError> |
ヘッダーのみエンコード |
encode_response_headers(&Response) |
Result<Vec<u8>, EncodeError> |
ヘッダーのみエンコード |
encode_chunk(&[u8]) |
Vec<u8> |
単一チャンクをエンコード (空入力は終端チャンク) |
encode_chunks(&[&[u8]]) |
Vec<u8> |
複数チャンクをエンコード (終端含む) |
圧縮トレイト
| 型/トレイト | 説明 |
|---|---|
Compressor |
圧縮トレイト: compress(), finish(), reset() |
Decompressor |
展開トレイト: decompress(), reset() |
NoCompression |
デフォルト実装 (圧縮なし) |
CompressionStatus |
Continue { consumed, produced }, Complete { consumed, produced }, OutputFull { consumed, produced } |
CompressionError |
BufferTooSmall { required, available }, InvalidData(String), Internal(String), UnexpectedEof, AlreadyFinished |
CompressionStatus には consumed(), produced(), is_complete(), is_output_full() ヘルパーがある。
ライブラリ本体は圧縮実装を含まないため、利用者が noflate / flate2 (gzip), brotli, zstd などを使って実装する。サンプル (examples/) は noflate ベースで実装されている。
RequestDecoder / ResponseDecoder は Keep-Alive 接続におけるメッセージ境界 (decode() 完了時および decode_headers() Complete→StartLine 遷移時) で Decompressor::reset() を自動的に呼び出すため、前メッセージの内部状態が次メッセージに漏れない。
ヘッダーパースモジュール
| モジュール | 主要型 | RFC |
|---|---|---|
accept |
Accept, AcceptCharset, AcceptEncoding, AcceptLanguage, QValue |
RFC 9110 |
accept_query |
AcceptQuery, MediaRangeItem, AcceptQueryError |
RFC 10008, RFC 9651 |
auth |
BasicAuth, DigestAuth, DigestChallenge, BearerToken, BearerChallenge, WwwAuthenticate, Authorization, AuthChallenge, ProxyAuthorization, ProxyAuthenticate, AuthError |
RFC 7617, 7616, 6750 |
cache |
CacheControl, Age, Expires |
RFC 9111 |
conditional |
IfMatch, IfNoneMatch, IfModifiedSince, IfUnmodifiedSince, IfRange |
RFC 9110 |
content_disposition |
ContentDisposition, DispositionType |
RFC 6266 |
content_encoding |
ContentEncoding |
RFC 9110 |
content_language |
ContentLanguage |
RFC 9110 |
content_location |
ContentLocation |
RFC 9110 |
content_type |
ContentType |
RFC 9110 |
cookie |
Cookie, SetCookie, SameSite |
RFC 6265 |
date |
HttpDate (IMF-fixdate / asctime は parse、rfc850-date は parse_rfc850(input, reference_year)), DateError |
RFC 9110 |
digest_fields |
ContentDigest, ReprDigest, WantContentDigest, WantReprDigest |
RFC 9530 |
etag |
EntityTag, ETagList |
RFC 9110 |
expect |
Expect |
RFC 9110 |
host |
Host (IPv4, IPv6, IPv-future 対応) |
RFC 9110 |
multipart |
MultipartParser (new(boundary) -> Result, with_max_buffer_size, feed -> Result<(), MultipartError>), MultipartBuilder (with_boundary(boundary) -> Result), Part, MultipartError |
RFC 7578 |
range |
Range, RangeSpec, ContentRange, AcceptRanges |
RFC 9110 |
trailer |
Trailer (禁止フィールド検証) |
RFC 9110, 9112 |
upgrade |
Upgrade |
RFC 9110 |
uri |
Uri, UriError, percent_encode(), percent_encode_path(), percent_encode_query(), percent_decode(), percent_decode_bytes(), resolve(), normalize() |
RFC 3986 |
vary |
Vary |
RFC 9110 |
コード例
クライアント実装
use shiguredo_http11::{Method, Request, ResponseDecoder};
use std::io::Read;
// リクエスト作成
// 構築時バリデーション (CRLF/NUL 拒否) を行い Result<_, EncodeError> を返す
let request = Request::new(Method::GET, "/")?
.header("Host", "example.com")?
.header("Connection", "close")?;
let bytes = request.encode()?;
// bytes をネットワークに送信...
// レスポンスデコード: 内部バッファに直接 read してコピーを排除
let mut decoder = ResponseDecoder::new();
const READ_CHUNK: usize = 8192;
loop {
let want = decoder.available_buf().min(READ_CHUNK);
if want == 0 {
return Err("decoder buffer full".into());
}
let buf = decoder.mut_buf(want)?;
let n = stream.read(buf)?;
if n == 0 {
decoder.advance_buf(0);
decoder.mark_eof();
break;
}
decoder.advance_buf(n);
if let Some(response) = decoder.decode()? {
println!("Status: {}", response.status_code());
break;
}
}
サーバー実装
use shiguredo_http11::{RequestDecoder, Response, StatusCode};
use std::io::Read;
// リクエストデコード: 内部バッファに直接 read してコピーを排除
let mut decoder = RequestDecoder::new();
const READ_CHUNK: usize = 8192;
let request = loop {
let want = decoder.available_buf().min(READ_CHUNK);
if want == 0 {
return Err("decoder buffer full".into());
}
let buf = decoder.mut_buf(want)?;
let n = stream.read(buf)?;
if n == 0 {
decoder.advance_buf(0);
return Err("client disconnected".into());
}
decoder.advance_buf(n);
if let Some(req) = decoder.decode()? {
break req;
}
};
// レスポンス作成
// IANA 登録済みステータスコードは `Response::with_status(StatusCode::OK)` で
// infallible に構築できる (canonical reason phrase が自動付与される)。
// カスタムバージョンや任意の reason phrase が必要な場合は
// `Response::new` / `with_version` を使う (Result<_, EncodeError> を返す)。
let response = Response::with_status(StatusCode::OK)
.header("Content-Type", "text/plain")?
.body(b"Hello, World!".to_vec());
let bytes = response.encode()?;
// bytes をネットワークに送信...
HEAD リクエストの処理
HEAD リクエストへのレスポンスは GET と同じヘッダーを返すがボディは送信しない (RFC 9110 Section 9.3.2)。
use shiguredo_http11::{Method, Request, Response, ResponseDecoder, StatusCode};
// サーバー側: Response::omit_body() でボディ送信を抑止
let is_head = request.method() == &Method::HEAD;
let body = b"Hello, World!";
let mut response = Response::with_status(StatusCode::OK)
.header("Content-Type", "text/plain")?
.header("Content-Length", &body.len().to_string())?
.omit_body(is_head);
if !is_head {
response = response.body(body.to_vec());
}
let bytes = response.encode()?;
// クライアント側: ResponseDecoder::set_request_method("HEAD") でボディなしを通知
let mut decoder = ResponseDecoder::new();
decoder.set_request_method("HEAD");
CONNECT トンネルの処理
CONNECT リクエストへの 2xx レスポンスはトンネルモードに切り替わり、Transfer-Encoding と Content-Length は無視される (BodyKind::Tunnel)。
use shiguredo_http11::ResponseDecoder;
let mut decoder = ResponseDecoder::new();
decoder.set_request_method("CONNECT");
// CONNECT 2xx 応答後のボディは Tunnel として扱われる。
// CONNECT 2xx を検出した時点で ResponseHead.headers からは
// Transfer-Encoding / Content-Length が除去される (RFC 9110 §9.3.6 / §7.8)。
サーバー側 (RequestDecoder) も CONNECT メソッド受信時に BodyKind::Tunnel を返し、is_tunnel() / take_remaining() でトンネル接続を引き継げる。
直接書き込み API (mut_buf / advance_buf / available_buf)
OS の read 等にデコーダーの内部バッファを直接渡せる API。
mut_buf(len) -> Result<&mut [u8], Error>: 内部バッファ末尾にlenバイトの書き込み枠 (ゼロ初期化済み) を確保。max_buffer_sizeを超える場合はBufferOverflowを返し、バッファ状態は不変。advance_buf(n): 直前のmut_bufで確保した枠のうち、実際に書き込まれたnバイトを確定する。n = 0で枠全体を破棄 (EOF や read 失敗時のリセット用)。available_buf() -> usize: 書き込み可能な残り容量 (max_buffer_size - 現在のバッファ長)。pendingを含めて差し引いた値。
feed / feed_unchecked との使い分け
両者は入力経路の違う別の最適解として共存する。用途で使い分ける:
- これから書き込む先のバッファが必要なケース (OS の
readでソケットから受信する等) はmut_buf/advance_buf。OS が内部バッファに直接書き込めるので、スタックバッファ → 内部Vec<u8>のコピーが発生しない。 - 既にバイト列が
&[u8]として手元にあるケース (io_uring 等の完了通知型 I/O、テスト用バイトリテラル、別経路から受け取ったバイト列の中継等) はfeed/feed_unchecked。mut_buf経由だと「ゼロ初期化 + memcpy」の二段になるが、feedは素直に 1 memcpy で済む。
use shiguredo_http11::ResponseDecoder;
let mut decoder = ResponseDecoder::new();
const READ_CHUNK: usize = 8192;
loop {
let want = decoder.available_buf().min(READ_CHUNK);
if want == 0 {
return Err("decoder buffer full".into());
}
let buf = decoder.mut_buf(want)?;
let n = stream.read(buf)?;
if n == 0 {
decoder.advance_buf(0);
decoder.mark_eof();
if let Some(response) = decoder.decode()? {
return Ok(response);
}
return Err("Connection closed before response complete".into());
}
decoder.advance_buf(n);
if let Some(response) = decoder.decode()? {
return Ok(response);
}
}
mut_buf 後 advance_buf を呼ばずに feed / decode_headers / peek_body / consume_body 等を呼ぶと debug ビルドで panic する (誤用検出)。
ストリーミングボディ処理
use shiguredo_http11::{RequestDecoder, BodyKind, BodyProgress};
use std::io::Read;
let mut decoder = RequestDecoder::new();
const READ_CHUNK: usize = 8192;
// ヘッダーがそろうまで mut_buf 経由で受信
let (head, body_kind) = loop {
let want = decoder.available_buf().min(READ_CHUNK);
let buf = decoder.mut_buf(want)?;
let n = stream.read(buf)?;
if n == 0 {
decoder.advance_buf(0);
return Err("client disconnected before headers".into());
}
decoder.advance_buf(n);
if let Some(result) = decoder.decode_headers()? {
break result;
}
};
// ボディをストリーミングで読み取り
let mut body = Vec::new();
if let BodyKind::ContentLength(_) | BodyKind::Chunked = body_kind {
'outer: loop {
loop {
if let Some(data) = decoder.peek_body() {
body.extend_from_slice(data);
let len = data.len();
match decoder.consume_body(len)? {
BodyProgress::Complete { .. } => break 'outer,
// NeedData (chunked CRLF 不足) でも内側ループ継続。
// 直後の peek_body() は None を返すため progress 分岐に進む。
BodyProgress::Advanced | BodyProgress::NeedData => continue,
}
}
// peek_body() が None → 状態機械を進める
match decoder.progress()? {
BodyProgress::Complete { .. } => break 'outer,
BodyProgress::Advanced => continue,
// バッファ不足: 内側ループを抜けて I/O 読み取りに戻る
BodyProgress::NeedData => break,
}
}
// 追加データを内部バッファに直接 read
let want = decoder.available_buf().min(READ_CHUNK);
let buf = decoder.mut_buf(want)?;
let n = stream.read(buf)?;
if n == 0 {
decoder.advance_buf(0);
return Err("client disconnected during body".into());
}
decoder.advance_buf(n);
}
}
Chunked Transfer Encoding
use shiguredo_http11::{Response, StatusCode, encode_chunk};
let response = Response::with_status(StatusCode::OK)
.header("Transfer-Encoding", "chunked")?;
// ヘッダーを送信
let headers = response.encode_headers()?;
send(&headers);
// チャンクを順次送信
send(&encode_chunk(b"Hello, "));
send(&encode_chunk(b"World!"));
send(&encode_chunk(b"")); // 終端チャンク
エンコード時のエラー処理
encode() / encode_headers() は構築時バリデーション後の意味論違反 (Host 欠落、Content-Length 不一致、Transfer-Encoding と Content-Length の競合等) を Result<Vec<u8>, EncodeError> で返す。
use shiguredo_http11::{EncodeError, Method, Request};
let request = Request::new(Method::GET, "/")?; // Host ヘッダーなし
match request.encode() {
Ok(bytes) => { /* 送信 */ }
Err(EncodeError::MissingHostHeader) => {
// Host ヘッダーがない (HTTP/1.1 必須)
}
Err(e) => eprintln!("encode error: {}", e),
}
rfc850-date のパース
HttpDate::parse は IMF-fixdate と asctime のみ受理し、rfc850-date を検出した場合は DateError::Rfc850Date を返す。rfc850-date の 2 桁年を解決するには parse_rfc850(input, reference_year) を使う (RFC 9110 §5.6.7 の 50 年ルール)。
use shiguredo_http11::date::{HttpDate, DateError};
let input = "Sunday, 06-Nov-94 08:49:37 GMT";
let date = match HttpDate::parse(input) {
Ok(d) => d,
Err(DateError::Rfc850Date) => HttpDate::parse_rfc850(input, 2026)?,
Err(e) => return Err(e),
};
SetCookie::parse / Expires::parse / IfModifiedSince::parse / IfUnmodifiedSince::parse / IfRange::parse は内部でこのフォールバックを行うため、シグネチャに reference_year: u16 引数を取る。
圧縮トレイトの実装
use shiguredo_http11::compression::{Compressor, Decompressor, CompressionStatus, CompressionError};
use shiguredo_http11::{ResponseDecoder, ResponseEncoder};
// 展開器を組み込んだデコーダー
let decoder = ResponseDecoder::with_decompressor(MyGzipDecompressor::new());
// 圧縮器を組み込んだエンコーダー
let mut encoder = ResponseEncoder::with_compressor(MyGzipCompressor::new());
// 展開器と制限を同時に指定
let decoder = RequestDecoder::with_decompressor_and_limits(
MyGzipDecompressor::new(),
DecoderLimits::default(),
);
DecoderLimits
| フィールド | デフォルト値 | 説明 |
|---|---|---|
max_buffer_size |
64KB | 最大バッファサイズ |
max_headers_count |
100 | 最大ヘッダー数 |
max_header_line_size |
8KB | 最大ヘッダー行長 |
max_body_size |
10MB | 最大ボディサイズ |
max_chunk_line_size |
64B | 最大チャンクサイズ行長 (16 進数) |
use shiguredo_http11::{RequestDecoder, DecoderLimits};
// カスタム制限
let limits = DecoderLimits {
max_body_size: 100 * 1024 * 1024, // 100MB
..DecoderLimits::default()
};
let mut decoder = RequestDecoder::with_limits(limits);
// 制限なし (信頼済み入力やテスト用途のみ。OOM に注意)
let decoder = RequestDecoder::with_limits(DecoderLimits::unlimited());
エラー型
Error (デコード/ランタイムエラー)
| バリアント | 説明 |
|---|---|
Error::InvalidData(String) |
不正なデータ |
Error::BufferOverflow { size, limit } |
バッファサイズ超過 |
Error::TooManyHeaders { count, limit } |
ヘッダー数超過 |
Error::HeaderLineTooLong { size, limit } |
ヘッダー行長超過 |
Error::BodyTooLarge { size, limit } |
ボディサイズ超過 |
Error::ChunkLineTooLong { size, limit } |
チャンクサイズ行長超過 |
Error::Compression(CompressionError) |
圧縮/展開エラー |
EncodeError (エンコード時のバリデーションエラー)
主なバリアント:
| バリアント | 説明 |
|---|---|
MissingHostHeader |
HTTP/1.1 リクエストに Host ヘッダーがない (RFC 9112 §3.2) |
ConflictingTransferEncodingAndContentLength |
Transfer-Encoding と Content-Length 同時指定 (RFC 9112 §6.2) |
ForbiddenTransferEncoding { status_code } |
1xx/204/205 で Transfer-Encoding 指定 (RFC 9112 §6.1, RFC 9110 §15.3.6) |
ForbiddenContentLength { status_code } |
1xx/204 で Content-Length 指定 (RFC 9110 §8.6) |
ForbiddenBodyFor205 |
205 Reset Content にボディあり (RFC 9110 §15.3.6) |
ContentLengthMismatch { header_value, body_length } |
Content-Length とボディ長が不一致 |
DuplicateContentLength |
Content-Length が複数で値が不一致 (RFC 9110 §8.6) |
InvalidContentLengthValue { value } |
Content-Length が 1*DIGIT ABNF 違反 |
InvalidMethod { method } |
不正なメソッド名 |
InvalidRequestTarget { uri } |
不正な request-target |
InvalidRequestTargetForm { method, uri } |
メソッドと request-target 形式の不整合 (RFC 9112 §3.2) |
InvalidVersion { version } |
不正な HTTP バージョン |
InvalidHeaderName { name } |
不正なヘッダー名 |
InvalidHeaderValue { name, value } |
不正なヘッダー値 |
InvalidStatusCode { code } |
不正なステータスコード |
InvalidReasonPhrase { phrase } |
不正な reason-phrase |
DuplicateHostHeader |
Host ヘッダーが重複 |
InvalidHostHeader { value } |
不正な Host ヘッダー値 |
HostAuthorityMismatch { host, authority } |
Host と request-target authority が不一致 |
UserinfoInHttpUri { uri } |
http/https URI に userinfo (RFC 9110 §4.2.4) |
NonEmptyHostWithoutAuthority { host, uri } |
authority のない URI で Host が非空 (RFC 9112 §3.2) |
EmptyHostInHttpUri { uri } |
http/https URI の host が空 (RFC 9110 §4.2) |
セキュリティ
- Response Splitting 対策: ヘッダー行の CR/LF と obs-fold を拒否
- Request Smuggling 対策: Transfer-Encoding と Content-Length の同時指定を拒否
- Request Smuggling 対策: Content-Length の不一致検出
- DoS 対策:
DecoderLimitsによる各種制限 - CONNECT の扱い: RFC 9110 §9.3.6 に準拠
RFC 準拠
| RFC | 名称 | 対応機能 |
|---|---|---|
| RFC 3986 | URI: Generic Syntax | URI パース、パーセントエンコーディング、resolve, normalize |
| RFC 6265 | HTTP State Management | Cookie, Set-Cookie |
| RFC 6266 | Content-Disposition | ファイル添付 |
| RFC 6750 | Bearer Token | Bearer 認証 |
| RFC 7578 | multipart/form-data | フォームデータ |
| RFC 7616 | Digest Auth | Digest 認証 |
| RFC 7617 | Basic Auth | Basic 認証 |
| RFC 8187 | Encoded HTTP Header Parameters | filename* 等のエンコード |
| RFC 9110 | HTTP Semantics | メソッド、ステータス、ヘッダー、条件付きリクエスト、Range |
| RFC 9111 | HTTP Caching | Cache-Control, Age, Expires |
| RFC 9112 | HTTP/1.1 | Transfer-Encoding, Content-Length, 接続管理, request-target 形式 |
| RFC 9530 | Digest Fields | Content-Digest, Repr-Digest, Want-Content-Digest, Want-Repr-Digest |
| RFC 9651 | Structured Field Values for HTTP | Accept-Query の List パース |
| RFC 10008 | The HTTP QUERY Method | QUERY メソッド, Accept-Query |
既知の制限事項
- UTF-8 強制: RFC 9112 §2.2 ではメッセージをオクテット列として解析すべき (SHOULD) としているが、本実装はチャンクサイズ行・トレーラー・ヘッダー値などを Rust の
Stringとして扱うため、非 UTF-8 バイト列 (obs-text 0x80-0xFF を含む) を拒否する。 - request-line 前の空行: RFC 9112 §2.2 では先頭の空行を少なくとも 1 行は無視すべき (SHOULD) としているが、本実装は厳格にパースする。アプリケーション層で除去すること。
- rfc850-date の reference_year: グローバル可変状態を持たないため、rfc850-date を含む可能性があるヘッダーをパースする API (
SetCookie::parse等) はreference_year: u16引数を要求する。アプリケーション側で現在年を渡す責務を負う。