Loading page…
Rust walkthroughs
Loading page…
http::HeaderValue::from_str validate header values against HTTP specifications?http::HeaderValue::from_str validates that the input string contains only valid HTTP header value characters according to RFC 7230, returning an error if the string contains invalid bytes. Valid header values are restricted to visible ASCII characters (0x21-0x7E), space (0x20), and horizontal tab (0x09)—control characters and non-ASCII bytes are forbidden. The method also allows obs-fold sequences (CRLF followed by space or tab) in specific contexts, though modern HTTP discourages their use. The validation ensures that the resulting HeaderValue can be safely transmitted over HTTP without encoding issues, as HTTP/1.1 headers are transmitted as ASCII text and intermediaries may reject malformed values.
use http::HeaderValue;
// HeaderValue represents a valid HTTP header value
// It's a thin wrapper around bytes guaranteed to be valid
// Create from static string (compile-time validation)
let content_type = HeaderValue::from_static("application/json");
// Create from dynamic string (runtime validation)
let custom = HeaderValue::from_str("text/html; charset=utf-8").unwrap();
// Header values can also be created from bytes
let bytes = HeaderValue::from_bytes(b"valid value").unwrap();HeaderValue is a type that guarantees its contents are valid for HTTP headers.
use http::HeaderValue;
fn main() {
// Valid: printable ASCII
let valid = HeaderValue::from_str("application/json").unwrap();
println!("Valid: {:?}", valid);
// Valid: contains space
let with_space = HeaderValue::from_str("text/html; charset=utf-8").unwrap();
println!("With space: {:?}", with_space);
// Valid: contains tab
let with_tab = HeaderValue::from_str("value\twith\ttab").unwrap();
println!("With tab: {:?}", with_tab);
// Invalid: contains newline
let result = HeaderValue::from_str("invalid\nvalue");
match result {
Ok(_) => println!("Unexpected success"),
Err(e) => println!("Error: {}", e), // Invalid header value
}
// Invalid: contains control character
let result2 = HeaderValue::from_str("invalid\x01value");
match result2 {
Ok(_) => println!("Unexpected success"),
Err(e) => println!("Error: {}", e),
}
}from_str validates at runtime, returning Result<HeaderValue, InvalidHeaderValue>.
use http::HeaderValue;
fn main() {
// RFC 7230 defines valid header value characters:
// - VCHAR: visible ASCII (0x21-0x7E)
// - SP: space (0x20)
// - HTAB: horizontal tab (0x09)
// - obs-fold: CRLF followed by SP or HTAB (deprecated)
// Visible ASCII characters (printable, no control chars)
let visible = HeaderValue::from_str("!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~").unwrap();
println!("All visible ASCII works");
// Space (0x20) is allowed
let spaces = HeaderValue::from_str("value with spaces").unwrap();
// Horizontal tab (0x09) is allowed
let tabs = HeaderValue::from_str("value\twith\ttabs").unwrap();
// Control characters (0x00-0x1F except HTAB) are forbidden
// NUL, CR, LF, etc.
assert!(HeaderValue::from_str("bad\x00value").is_err()); // NUL
assert!(HeaderValue::from_str("bad\rvalue").is_err()); // CR
assert!(HeaderValue::from_str("bad\nvalue").is_err()); // LF
assert!(HeaderValue::from_str("bad\x1Fvalue").is_err()); // Unit Separator
// DEL (0x7F) is also forbidden
assert!(HeaderValue::from_str("bad\x7Fvalue").is_err()); // DEL
// Non-ASCII bytes (> 0x7F) are forbidden in from_str
assert!(HeaderValue::from_str("café").is_err()); // é is non-ASCII
}The validation follows HTTP/1.1 specifications for header field values.
use http::HeaderValue;
fn validate_header_value(input: &str) -> Result<HeaderValue, String> {
HeaderValue::from_str(input).map_err(|e| {
format!("Invalid header value '{}': {}", input, e)
})
}
fn main() {
// Safe validation with error context
match validate_header_value("valid-value") {
Ok(value) => println!("Valid: {}", value),
Err(e) => println!("Error: {}", e),
}
match validate_header_value("invalid\nvalue") {
Ok(value) => println!("Valid: {}", value),
Err(e) => println!("Error: {}", e),
}
}The error type InvalidHeaderValue implements Display for error messages.
use http::HeaderValue;
fn main() {
// from_str: validates UTF-8 and HTTP validity
// Rejects non-ASCII bytes
let str_result = HeaderValue::from_str("value");
println!("from_str valid: {:?}", str_result.is_ok());
// from_bytes: only validates HTTP validity
// Allows any bytes that are valid for HTTP (including non-UTF-8)
let bytes_result = HeaderValue::from_bytes(b"value");
println!("from_bytes valid: {:?}", bytes_result.is_ok());
// from_bytes allows non-UTF-8 sequences that are HTTP-valid
// (though such usage is unusual and typically indicates binary data)
let binary_result = HeaderValue::from_bytes(&[0x20, 0x21, 0x22]);
println!("from_bytes binary: {:?}", binary_result.is_ok());
// Both reject control characters
assert!(HeaderValue::from_bytes(b"bad\nvalue").is_err());
assert!(HeaderValue::from_str("bad\nvalue").is_err());
}from_str validates UTF-8 encoding plus HTTP validity; from_bytes only validates HTTP validity.
use http::HeaderValue;
fn main() {
// from_static validates at compile time
// Panics if invalid (shouldn't happen for known-good constants)
const CONTENT_TYPE_JSON: HeaderValue = HeaderValue::from_static("application/json");
// Useful for known constants
let content_length = HeaderValue::from_static("0");
let connection = HeaderValue::from_static("keep-alive");
// Compile-time check ensures these are always valid
// No runtime overhead for validation
}from_static is const and validates at compile time for known-good values.
use http::HeaderValue;
fn main() {
// RFC 7230 allows "obs-fold" (obsolete line folding)
// CRLF followed by at least one SP or HTAB
// This is deprecated but still accepted
// Modern usage: single line
let modern = HeaderValue::from_str("value one two three").unwrap();
// Obs-fold form (historically valid)
// In practice, most implementations convert to spaces
// Note: from_str may or may not accept this depending on version
// Let's check:
let with_fold = "value\r\n one";
match HeaderValue::from_str(with_fold) {
Ok(v) => println!("Obs-fold accepted: {:?}", v),
Err(e) => println!("Obs-fold rejected: {}", e),
}
}Obs-fold is a deprecated legacy feature for multi-line header values.
use http::HeaderValue;
fn main() {
// Content-Type
let content_type = HeaderValue::from_str("application/json; charset=utf-8").unwrap();
// Content-Length (numeric)
let content_length = HeaderValue::from_str("12345").unwrap();
// Cache-Control
let cache_control = HeaderValue::from_str("max-age=3600, public").unwrap();
// Authorization (Bearer token)
let auth = HeaderValue::from_str("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9").unwrap();
// User-Agent
let user_agent = HeaderValue::from_str("Mozilla/5.0 (compatible; MyBot/1.0)").unwrap();
// Custom headers must also follow the rules
let custom = HeaderValue::from_str("X-Custom-Header-Value").unwrap();
}All standard headers follow the same validation rules.
use http::HeaderValue;
fn main() {
// Common invalid characters that cause validation to fail:
// 1. Newlines (CRLF injection prevention)
assert!(HeaderValue::from_str("value\nwith\nnewlines").is_err());
assert!(HeaderValue::from_str("value\r\nwith\r\ncrlf").is_err());
// 2. Null bytes
assert!(HeaderValue::from_str("value\x00null").is_err());
// 3. Non-ASCII characters (UTF-8 but outside ASCII)
assert!(HeaderValue::from_str("café").is_err()); // é = 0xC3 0xA9
assert!(HeaderValue::from_str("日本語").is_err()); // Japanese chars
assert!(HeaderValue::from_str("emoji 😀").is_err()); // Emoji
// 4. Other control characters
assert!(HeaderValue::from_str("value\x01control").is_err()); // SOH
assert!(HeaderValue::from_str("value\x02control").is_err()); // STX
assert!(HeaderValue::from_str("value\x1bcontrol").is_err()); // ESC
// 5. DEL character
assert!(HeaderValue::from_str("value\x7f").is_err());
}These restrictions prevent header injection attacks and ensure HTTP compatibility.
use http::HeaderValue;
fn safe_header_value(input: &str) -> HeaderValue {
// If valid, use directly
if let Ok(value) = HeaderValue::from_str(input) {
return value;
}
// If invalid, we need to decide how to handle:
// Option 1: percent-encode (depends on context)
// Not directly supported by http crate
// Option 2: Base64 encode for binary data
let encoded = base64_encode(input);
HeaderValue::from_str(&encoded).unwrap()
// Option 3: Use percent encoding for URLs in headers
// Use url::percent_encode for URL encoding
// Option 4: Use from_bytes if you have controlled binary data
// Only for valid HTTP bytes, not arbitrary binary
}
fn base64_encode(input: &str) -> String {
use base64::{Engine as _, engine::general_purpose};
general_purpose::STANDARD.encode(input.as_bytes())
}When dealing with potentially invalid values, you must sanitize or encode.
use http::HeaderValue;
fn main() {
// Non-ASCII values need encoding before use
// For Content-Disposition filenames with Unicode:
// RFC 6266 specifies the encoding
// Option 1: Percent encode (URL encoding)
fn percent_encode_filename(filename: &str) -> String {
use url::percent_encoding::{percent_encode, NON_ALPHANUMERIC};
percent_encode(filename.as_bytes(), NON_ALPHANUMERIC).to_string()
}
// Option 2: Base64 for binary data
fn base64_encode_value(data: &[u8]) -> String {
use base64::{Engine as _, engine::general_purpose};
general_purpose::STANDARD.encode(data)
}
// Option 3: RFC 5987 encoding for Content-Disposition
fn rfc5987_encode(value: &str) -> String {
// Format: charset'lang'encoded-value
// Example: UTF-8''%E6%97%A5%E6%9C%AC%E8%AA%9E
format!("UTF-8''{}",
url::percent_encoding::percent_encode(value.as_bytes(),
url::percent_encoding::NON_ALPHANUMERIC))
}
// After encoding, the result should be valid ASCII
let encoded = base64_encode_value("binary\x00data".as_bytes());
let header_value = HeaderValue::from_str(&encoded).unwrap();
}Encode non-ASCII or binary values to ASCII before creating header values.
use http::{HeaderMap, HeaderName, HeaderValue};
fn main() {
let mut headers = HeaderMap::new();
// Create headers with validated values
headers.insert(
HeaderName::from_static("content-type"),
HeaderValue::from_static("application/json"),
);
headers.insert(
HeaderName::from_static("content-length"),
HeaderValue::from_str("12345").unwrap(),
);
// Dynamic values need validation
let user_agent = format!("MyApp/{}", "1.0.0");
headers.insert(
HeaderName::from_static("user-agent"),
HeaderValue::from_str(&user_agent).unwrap(),
);
// Accessing header values
if let Some(content_type) = headers.get("content-type") {
println!("Content-Type: {}", content_type.to_str().unwrap());
}
}
// Helper to safely add headers
fn add_header(
headers: &mut HeaderMap,
name: &str,
value: &str
) -> Result<(), String> {
let header_name = HeaderName::from_static(name);
let header_value = HeaderValue::from_str(value)
.map_err(|e| format!("Invalid header value: {}", e))?;
headers.insert(header_name, header_value);
Ok(())
}HeaderMap stores HeaderValue instances, ensuring all values are pre-validated.
use http::HeaderValue;
fn main() {
let value = HeaderValue::from_str("application/json").unwrap();
// to_str converts back to &str
// Returns Result because even valid HTTP bytes might not be valid UTF-8
// (though from_str guarantees UTF-8, from_bytes might not)
let s = value.to_str().unwrap();
println!("Value as str: {}", s);
// to_bytes for raw access
let bytes = value.as_bytes();
println!("Value as bytes: {:?}", bytes);
// as_str for known-safe values (from_static)
// Only use when you're certain the value came from from_static or from_str
}to_str() safely converts back to a string reference when possible.
use http::HeaderValue;
fn main() {
// Header injection attacks are prevented by validation
// Attacker tries to inject new header via value
let malicious = "application/json\r\nX-Injected: malicious";
// This fails validation
assert!(HeaderValue::from_str(malicious).is_err());
// The CR and LF characters are rejected
// Without validation, an attacker could:
// HTTP/1.1 200 OK
// Content-Type: application/json
// X-Injected: malicious <- injected!
// Always validate untrusted input:
fn safe_from_user_input(input: &str) -> Option<HeaderValue> {
// Reject if contains control characters
if input.chars().any(|c| c.is_control() && c != '\t') {
return None;
}
HeaderValue::from_str(input).ok()
}
}Validation prevents header injection and request smuggling attacks.
use http::HeaderValue;
fn main() {
// Manual validation (error-prone)
fn manual_validate(value: &str) -> bool {
value.chars().all(|c| {
c.is_ascii_graphic() || c == ' ' || c == '\t'
})
}
// Problem: doesn't catch all edge cases
// Problem: doesn't handle obs-fold
// Problem: different interpretations of "valid"
// Using HeaderValue::from_str
// - Follows RFC 7230 precisely
// - Handles all edge cases
// - Consistent with other HTTP libraries
// - Returns meaningful error
// Example:
let test_value = "application/json";
// Manual might miss edge cases
assert!(manual_validate(test_value));
// HeaderValue is authoritative
assert!(HeaderValue::from_str(test_value).is_ok());
}Using HeaderValue::from_str ensures RFC-compliant validation.
use http::{HeaderValue, HeaderMap, HeaderName};
fn build_response_headers(
content_type: &str,
content_length: u64,
custom_headers: Vec<(&str, &str)>
) -> Result<HeaderMap, String> {
let mut headers = HeaderMap::new();
// Standard headers
headers.insert(
HeaderName::from_static("content-type"),
HeaderValue::from_str(content_type)
.map_err(|e| format!("Invalid content-type: {}", e))?
);
headers.insert(
HeaderName::from_static("content-length"),
HeaderValue::from_str(&content_length.to_string()).unwrap()
);
// Custom headers - validate each
for (name, value) in custom_headers {
let header_name = HeaderName::from_bytes(name.as_bytes())
.map_err(|e| format!("Invalid header name '{}': {}", name, e))?;
let header_value = HeaderValue::from_str(value)
.map_err(|e| format!("Invalid header value for '{}': {}", name, e))?;
headers.insert(header_name, header_value);
}
Ok(headers)
}
fn main() {
match build_response_headers(
"application/json",
12345,
vec![
("X-Request-Id", "abc-123"),
("X-Custom", "value"),
]
) {
Ok(headers) => println!("Headers: {:?}", headers),
Err(e) => println!("Error: {}", e),
}
}Production code should always validate header values from external sources.
use http::HeaderValue;
// Safely handle user-provided header values
fn parse_user_header_value(user_input: String) -> Result<HeaderValue, String> {
// Trim whitespace
let trimmed = user_input.trim();
// Validate
HeaderValue::from_str(trimmed)
.map_err(|e| {
format!(
"Header value contains invalid characters. \
Only printable ASCII, space, and tab are allowed: {}",
e
)
})
}
// Handle Unicode content
fn parse_filename_header(filename: &str) -> Result<HeaderValue, String> {
// ASCII filenames can go directly
if filename.is_ascii() {
return HeaderValue::from_str(filename)
.map_err(|e| e.to_string());
}
// Non-ASCII needs encoding (e.g., Content-Disposition filename)
// Use RFC 5987 encoding
let encoded = format!("UTF-8''{}",
url::percent_encoding::percent_encode(
filename.as_bytes(),
url::percent_encoding::NON_ALPHANUMERIC
)
);
HeaderValue::from_str(&encoded)
.map_err(|e| e.to_string())
}
fn main() {
// Test with user input
match parse_user_header_value("valid-value".to_string()) {
Ok(v) => println!("Valid: {}", v),
Err(e) => println!("Error: {}", e),
}
match parse_user_header_value("invalid\nvalue".to_string()) {
Ok(v) => println!("Valid: {}", v),
Err(e) => println!("Error: {}", e),
}
}User input must always be validated before use in headers.
Valid characters for HTTP header values (RFC 7230):
| Range | Characters | Allowed | |-------|------------|---------| | 0x00-0x08 | Control chars | ❌ No | | 0x09 | Horizontal tab | ✅ Yes | | 0x0A-0x1F | Control chars | ❌ No | | 0x20 | Space | ✅ Yes | | 0x21-0x7E | Printable ASCII | ✅ Yes | | 0x7F | DEL | ❌ No | | 0x80-0xFF | Non-ASCII | ❌ No (in from_str) |
Methods comparison:
| Method | Input | Validation | Use Case |
|--------|-------|------------|----------|
| from_static | &'static str | Compile-time | Known constants |
| from_str | &str | Runtime UTF-8 + HTTP | Dynamic text |
| from_bytes | &[u8] | Runtime HTTP only | Binary data |
| from_name | HeaderName | — | Typed headers |
Common invalid characters:
| Character | Byte | Why invalid | |-----------|------|-------------| | NUL | 0x00 | Control character | | CR | 0x0D | Control, injection risk | | LF | 0x0A | Control, injection risk | | ESC | 0x1B | Control character | | DEL | 0x7F | Control character | | é | 0xC3 0xA9 | Non-ASCII (UTF-8) |
Key insight: http::HeaderValue::from_str enforces HTTP/1.1 header value constraints by validating that input contains only visible ASCII characters (0x21-0x7E), space (0x20), and horizontal tab (0x09)—rejecting all control characters (including newlines) and non-ASCII bytes. This validation prevents header injection attacks where malicious CR/LF characters could inject fake headers into HTTP messages. The resulting HeaderValue type is a proof of validity that can be safely inserted into HeaderMap without additional checks. For non-ASCII content like filenames in Content-Disposition, applications must encode values before validation using RFC 5987 percent-encoding or base64. The distinction between from_str (validates UTF-8 plus HTTP constraints) and from_bytes (validates HTTP constraints only) matters for binary data that might be valid for HTTP but not valid UTF-8. Compile-time validation with from_static is preferred for constant header values to eliminate runtime overhead.