CrackedRuby CrackedRuby

Overview

HTTP headers consist of key-value pairs transmitted between clients and servers during HTTP communication. Each header conveys metadata about the request or response, enabling functionality beyond the basic message body transmission. Headers control caching behavior, specify content types, manage authentication, define encoding preferences, and coordinate various aspects of client-server interaction.

The HTTP specification divides headers into four categories: general headers applicable to both requests and responses, request headers sent from client to server, response headers returned from server to client, and entity headers describing the message body. This categorization reflects the bidirectional nature of HTTP communication and the distinct roles each participant plays.

Headers operate at the application layer of the network stack, positioned between the HTTP method/status line and the message body. Servers and clients parse headers before processing body content, making header data available for routing decisions, authentication checks, content negotiation, and other preprocessing operations.

# Basic header structure in HTTP
GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
Authorization: Bearer token123

# Response with headers
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
Set-Cookie: session=abc123

Key Principles

HTTP headers follow a structured format where each header consists of a case-insensitive name, a colon separator, and a value. Multiple headers with the same name can appear in a single message, and header order generally does not affect semantics except for certain precedence rules in specific headers.

Request Headers originate from the client and inform the server about the client's capabilities, preferences, and context. The Host header identifies the target server, essential for virtual hosting scenarios where multiple domains share a single IP address. User-Agent reveals the client software making the request, though this data often proves unreliable due to spoofing. Accept headers negotiate content types, languages, and encodings the client can process. Authorization headers carry authentication credentials in various formats.

Response Headers flow from server to client, describing the server's capabilities, the resource's characteristics, and instructions for handling the response. Content-Type declares the media type of the response body using MIME types. Content-Length specifies body size in bytes, enabling connection reuse and progress indicators. Cache-Control directs caching behavior at intermediate proxies and client caches. Set-Cookie establishes stateful sessions over the stateless HTTP protocol.

General Headers apply to both requests and responses. Connection controls whether the network connection stays open after the current transaction completes. Date timestamps the message generation time. Via tracks intermediate proxies in the request/response chain, revealing routing paths and proxy transformations.

Entity Headers describe the message body's properties regardless of message direction. Content-Encoding indicates compression algorithms applied to the body. Content-Language specifies the natural language of the content. Content-Location provides an alternative URL for the resource when it differs from the request URL.

Header parsing requires handling multiple edge cases. Whitespace around the colon separator gets trimmed. Values can span multiple lines using continuation syntax with leading whitespace, though this practice has fallen out of favor in HTTP/2. Headers containing non-ASCII characters require encoding schemes like RFC 2047 for compatibility.

# Header continuation (deprecated in HTTP/2)
Subject: This is a long subject line
 that continues on the next line

# Properly encoded non-ASCII header
Subject: =?utf-8?B?SGVsbG8gV29ybGQ=?=

Ruby Implementation

Ruby provides multiple layers of abstraction for working with HTTP headers. The standard library includes Net::HTTP for low-level HTTP operations, while Rack standardizes the interface between web servers and Ruby applications. Higher-level frameworks like Rails build additional conveniences on these foundations.

Net::HTTP exposes headers through bracket notation on request and response objects. Setting headers modifies the outgoing request before transmission, while reading headers accesses data from received responses. Header names get normalized to standard capitalization automatically.

require 'net/http'

uri = URI('https://api.example.com/data')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
request['Accept'] = 'application/json'
request['User-Agent'] = 'MyApp/1.0'
request['Authorization'] = 'Bearer token123'

response = http.request(request)
content_type = response['Content-Type']
cache_control = response['Cache-Control']
# => "max-age=3600, public"

Rack normalizes header handling across different Ruby web servers by converting header names to uppercase with HTTP_ prefixes and replacing hyphens with underscores. This transformation enables consistent access to headers regardless of the underlying server implementation. Special headers like Content-Type and Content-Length omit the HTTP_ prefix to distinguish them from general headers.

# Rack environment hash
env = {
  'HTTP_ACCEPT' => 'application/json',
  'HTTP_USER_AGENT' => 'Mozilla/5.0',
  'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
  'CONTENT_LENGTH' => '42'
}

# Accessing headers in Rack middleware
class HeaderLogger
  def initialize(app)
    @app = app
  end
  
  def call(env)
    accept = env['HTTP_ACCEPT']
    content_type = env['CONTENT_TYPE']
    
    status, headers, body = @app.call(env)
    
    # Set response headers
    headers['X-Processing-Time'] = '0.123'
    headers['X-Server-Version'] = '1.0.0'
    
    [status, headers, body]
  end
end

Rails adds convenience methods for common header operations through the ActionDispatch framework. The request object provides typed accessors for frequently used headers, handling parsing and type conversion automatically. The response object offers methods for setting headers with validation and formatting.

# Rails controller header access
class ApiController < ApplicationController
  def index
    # Reading request headers
    accept_language = request.headers['Accept-Language']
    auth_token = request.headers['Authorization']
    
    # Using convenience methods
    content_type = request.content_type
    user_agent = request.user_agent
    
    # Setting response headers
    response.headers['X-API-Version'] = '2.0'
    response.headers['X-Rate-Limit'] = '100'
    
    # Using typed setters
    response.content_type = 'application/json'
    response.cache_control[:public] = true
    response.cache_control[:max_age] = 3600
    
    render json: data
  end
end

Faraday provides a higher-level HTTP client with middleware support for common header patterns. Middleware can inject headers automatically, log header values, or transform headers based on conditions. This architecture separates header management from business logic.

require 'faraday'

conn = Faraday.new(url: 'https://api.example.com') do |f|
  f.headers['User-Agent'] = 'MyApp/1.0'
  f.headers['Accept'] = 'application/json'
  
  f.request :authorization, 'Bearer', -> { fetch_token }
  f.request :retry, max: 3
  
  f.response :logger
  f.adapter Faraday.default_adapter
end

response = conn.get('/users') do |req|
  req.headers['X-Request-ID'] = SecureRandom.uuid
end

Security Implications

HTTP headers serve as critical security controls, though improper configuration creates vulnerabilities. Security headers instruct browsers to enforce protections that mitigate common web attacks. Missing or misconfigured security headers rank among the top web application security risks.

Content-Security-Policy (CSP) prevents cross-site scripting (XSS) attacks by specifying which sources can load JavaScript, styles, images, and other resources. A strict CSP blocks inline scripts and restricts external script sources to whitelisted domains. Implementing CSP requires auditing all script sources in an application and updating legacy inline event handlers.

# Rails security header configuration
class ApplicationController < ActionController::Base
  before_action :set_security_headers
  
  private
  
  def set_security_headers
    response.headers['Content-Security-Policy'] = [
      "default-src 'self'",
      "script-src 'self' https://cdn.example.com",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' https://fonts.gstatic.com",
      "connect-src 'self' https://api.example.com",
      "frame-ancestors 'none'"
    ].join('; ')
    
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
  end
end

Strict-Transport-Security (HSTS) forces HTTPS connections for a specified duration, preventing downgrade attacks and cookie hijacking. Once a browser receives an HSTS header, it refuses to connect over HTTP for the specified period, even if the user types http:// in the address bar. The includeSubDomains directive extends this protection to all subdomains.

Cross-Origin Resource Sharing (CORS) controls which origins can access resources through JavaScript. Without proper CORS headers, browsers block cross-origin requests to prevent malicious sites from stealing data. Configuring CORS requires balancing security with legitimate cross-origin needs.

# CORS configuration in Rails
class ApplicationController < ActionController::Base
  before_action :set_cors_headers
  
  private
  
  def set_cors_headers
    allowed_origins = ['https://app.example.com', 'https://admin.example.com']
    origin = request.headers['Origin']
    
    if allowed_origins.include?(origin)
      response.headers['Access-Control-Allow-Origin'] = origin
      response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
      response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
      response.headers['Access-Control-Max-Age'] = '3600'
      response.headers['Access-Control-Allow-Credentials'] = 'true'
    end
  end
end

Authentication headers transmit credentials from client to server. The Authorization header carries tokens, API keys, or encoded username/password combinations. Different authentication schemes use different formats: Basic authentication encodes credentials in base64, Bearer tokens pass opaque tokens, and Digest authentication uses challenge-response mechanisms. Exposing credentials in headers requires HTTPS to prevent interception.

Cookie handling introduces multiple security concerns. The Secure flag restricts cookie transmission to HTTPS connections. HttpOnly prevents JavaScript access to cookies, mitigating XSS-based cookie theft. SameSite controls when browsers send cookies with cross-site requests, defending against CSRF attacks.

# Secure cookie configuration in Rails
Rails.application.config.session_store :cookie_store,
  key: '_app_session',
  secure: Rails.env.production?,
  httponly: true,
  same_site: :lax

# Setting cookies with security flags
cookies.signed[:user_id] = {
  value: user.id,
  expires: 1.day.from_now,
  secure: true,
  httponly: true,
  same_site: :strict
}

Header injection attacks occur when applications include unsanitized user input in headers. Attackers inject newline characters to add arbitrary headers or modify response behavior. Modern frameworks automatically sanitize header values, but manual header construction requires careful validation.

Common Patterns

Cache-Control patterns optimize performance by specifying how responses can be cached. The max-age directive sets cache lifetime in seconds. The public directive allows caching in shared caches like CDNs, while private restricts caching to client-specific caches. The no-store directive prevents all caching for sensitive data.

# Cache control patterns in Rails
class PublicController < ApplicationController
  def index
    # Cache for 1 hour in any cache
    expires_in 1.hour, public: true
    
    # Equivalent to:
    # response.cache_control[:public] = true
    # response.cache_control[:max_age] = 3600
    
    render json: public_data
  end
end

class PrivateController < ApplicationController
  def show
    # Cache in browser only, revalidate
    expires_in 5.minutes, public: false, must_revalidate: true
    
    render json: user_specific_data
  end
end

class SensitiveController < ApplicationController
  def show
    # Never cache
    response.cache_control[:no_store] = true
    response.cache_control[:no_cache] = true
    response.cache_control[:must_revalidate] = true
    
    render json: sensitive_data
  end
end

Content negotiation uses Accept headers to select appropriate response formats. Clients specify preferred media types with quality values indicating relative preference. Servers examine Accept headers to choose the best representation of a resource.

class ApiController < ApplicationController
  def show
    respond_to do |format|
      format.json { render json: resource }
      format.xml { render xml: resource }
      format.html { render :show }
    end
  end
  
  # Manual content negotiation
  def index
    accept = request.headers['Accept']
    
    if accept.include?('application/json')
      render json: resources
    elsif accept.include?('text/xml')
      render xml: resources
    else
      render html: resources
    end
  end
end

Custom headers communicate application-specific metadata. The X- prefix historically identified custom headers, though RFC 6648 deprecated this convention. Despite deprecation, X- headers remain common in practice. Custom headers enable feature flags, debugging information, rate limiting data, and request tracing.

# Custom header patterns
class ApplicationController < ActionController::Base
  before_action :add_custom_headers
  
  private
  
  def add_custom_headers
    response.headers['X-Request-ID'] = request.request_id
    response.headers['X-Runtime'] = Benchmark.realtime { yield }
    response.headers['X-Rate-Limit-Remaining'] = rate_limit_remaining
    response.headers['X-API-Version'] = API_VERSION
  end
end

# Request tracing with correlation IDs
class RequestTracer
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request_id = env['HTTP_X_REQUEST_ID'] || SecureRandom.uuid
    Thread.current[:request_id] = request_id
    
    status, headers, body = @app.call(env)
    headers['X-Request-ID'] = request_id
    
    [status, headers, body]
  ensure
    Thread.current[:request_id] = nil
  end
end

ETags enable efficient caching through conditional requests. Servers generate ETags as fingerprints of response content. Clients include ETags in If-None-Match headers on subsequent requests. Servers return 304 Not Modified when content remains unchanged, saving bandwidth.

# ETag implementation
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    if stale?(etag: @article, last_modified: @article.updated_at)
      render json: @article
    end
    # Rails automatically returns 304 if ETag matches
  end
  
  # Manual ETag handling
  def index
    @articles = Article.all
    etag = Digest::MD5.hexdigest(@articles.to_json)
    
    if request.headers['If-None-Match'] == etag
      head :not_modified
    else
      response.headers['ETag'] = etag
      render json: @articles
    end
  end
end

Practical Examples

API Authentication with Bearer Tokens

Modern APIs authenticate requests using tokens in Authorization headers. The Bearer scheme passes tokens without encoding, relying on HTTPS for confidentiality. Token validation occurs on every request, requiring efficient lookup mechanisms.

class ApiController < ApplicationController
  before_action :authenticate_with_token
  
  private
  
  def authenticate_with_token
    auth_header = request.headers['Authorization']
    
    unless auth_header&.start_with?('Bearer ')
      render json: { error: 'Missing authorization' }, status: :unauthorized
      return
    end
    
    token = auth_header.delete_prefix('Bearer ')
    @current_user = User.find_by(api_token: token)
    
    unless @current_user
      render json: { error: 'Invalid token' }, status: :unauthorized
    end
  end
end

Rate Limiting with Custom Headers

Rate limiting protects APIs from abuse by tracking request counts per client. Response headers communicate remaining quota and reset times, enabling clients to manage their request patterns.

class RateLimiter
  def initialize(app)
    @app = app
    @store = Redis.new
  end
  
  def call(env)
    request = Rack::Request.new(env)
    client_id = extract_client_id(request)
    
    limit = 100
    window = 3600
    key = "rate_limit:#{client_id}:#{Time.now.to_i / window}"
    
    current = @store.incr(key)
    @store.expire(key, window) if current == 1
    
    remaining = [limit - current, 0].max
    reset_time = ((Time.now.to_i / window) + 1) * window
    
    status, headers, body = @app.call(env)
    
    headers['X-RateLimit-Limit'] = limit.to_s
    headers['X-RateLimit-Remaining'] = remaining.to_s
    headers['X-RateLimit-Reset'] = reset_time.to_s
    
    if current > limit
      headers['Retry-After'] = (reset_time - Time.now.to_i).to_s
      return [429, headers, ['Rate limit exceeded']]
    end
    
    [status, headers, body]
  end
  
  private
  
  def extract_client_id(request)
    request.env['HTTP_X_API_KEY'] || request.ip
  end
end

Content Compression

Compressing response bodies reduces bandwidth and improves load times. The Accept-Encoding header indicates which compression algorithms the client supports. The Content-Encoding header identifies the compression applied to the response.

class CompressionMiddleware
  def initialize(app)
    @app = app
  end
  
  def call(env)
    status, headers, body = @app.call(env)
    
    accept_encoding = env['HTTP_ACCEPT_ENCODING'] || ''
    return [status, headers, body] unless accept_encoding.include?('gzip')
    
    # Skip compression for small or already compressed content
    content_type = headers['Content-Type']
    return [status, headers, body] if headers['Content-Encoding']
    return [status, headers, body] unless compressible?(content_type)
    
    compressed = compress_body(body)
    
    # Only use compression if it reduces size
    if compressed.bytesize < original_size(body)
      headers['Content-Encoding'] = 'gzip'
      headers['Content-Length'] = compressed.bytesize.to_s
      headers['Vary'] = 'Accept-Encoding'
      
      [status, headers, [compressed]]
    else
      [status, headers, body]
    end
  end
  
  private
  
  def compressible?(content_type)
    ['text/', 'application/json', 'application/javascript'].any? do |type|
      content_type&.start_with?(type)
    end
  end
  
  def compress_body(body)
    io = StringIO.new
    gz = Zlib::GzipWriter.new(io)
    body.each { |chunk| gz.write(chunk) }
    gz.close
    io.string
  end
  
  def original_size(body)
    body.sum { |chunk| chunk.bytesize }
  end
end

Common Pitfalls

Case Sensitivity Confusion

HTTP header names are case-insensitive per the specification, but different libraries handle this differently. Ruby's Net::HTTP normalizes header names to Title-Case, while Rack converts them to uppercase with underscores. Assuming a specific case in comparisons causes bugs.

# Wrong: case-sensitive comparison
request['content-type']  # Might not find 'Content-Type'

# Correct: case-insensitive access
request['Content-Type']
request['content-type']  # Works with proper library support

# In Rack, always use uppercase
env['HTTP_CONTENT_TYPE']  # Wrong
env['CONTENT_TYPE']       # Correct

Header Injection Vulnerabilities

Including unsanitized user input in headers enables attackers to inject malicious headers. Newline characters split headers, allowing header injection attacks. Always validate and sanitize user data before including it in headers.

# Vulnerable code
response.headers['X-Username'] = params[:username]
# Attacker sends: "admin\r\nSet-Cookie: session=stolen"

# Secure code
username = params[:username].gsub(/[\r\n]/, '')
response.headers['X-Username'] = username

# Or use framework helpers that sanitize automatically
response.headers['X-Username'] = sanitize_header_value(params[:username])

CORS Preflight Caching Issues

Browsers cache CORS preflight responses according to Access-Control-Max-Age headers. Setting this value too high causes problems when CORS configuration changes. Setting it too low increases preflight request overhead. Finding the right balance requires considering deployment frequency and performance needs.

# Too aggressive caching
response.headers['Access-Control-Max-Age'] = '86400'  # 24 hours

# Better: shorter cache for development
max_age = Rails.env.production? ? 3600 : 60
response.headers['Access-Control-Max-Age'] = max_age.to_s

Missing Vary Headers

When responses vary based on request headers, the Vary header must list those headers. Proxies and caches use Vary to store different versions of resources. Missing Vary headers cause caches to serve wrong content to clients with different capabilities.

# Wrong: content varies but Vary header missing
def show
  if request.headers['Accept'].include?('json')
    render json: data
  else
    render html: data
  end
  # Cache serves JSON to HTML clients or vice versa
end

# Correct: Vary header indicates content negotiation
def show
  response.headers['Vary'] = 'Accept'
  
  if request.headers['Accept'].include?('json')
    render json: data
  else
    render html: data
  end
end

Authorization Header Overwrite

Middleware that sets Authorization headers can overwrite existing values, breaking authentication. Defensive programming checks for existing headers before setting new values.

# Wrong: overwrites user's authentication
def call(env)
  env['HTTP_AUTHORIZATION'] = "Bearer #{default_token}"
  @app.call(env)
end

# Correct: preserves existing auth
def call(env)
  env['HTTP_AUTHORIZATION'] ||= "Bearer #{default_token}"
  @app.call(env)
end

Content-Length Mismatches

Manually setting Content-Length headers risks mismatches with actual body size. Frameworks calculate Content-Length automatically, but manual manipulation can desynchronize these values, causing truncated responses or hung connections.

# Wrong: manual Content-Length likely incorrect
response.headers['Content-Length'] = '100'
response.body = generate_body  # Actual size may differ

# Correct: let framework calculate
response.body = generate_body
# Content-Length set automatically

Reference

Standard Request Headers

Header Purpose Example Values
Accept Media types client can process application/json, text/html
Accept-Encoding Compression algorithms supported gzip, deflate, br
Accept-Language Preferred natural languages en-US, fr-FR;q=0.8
Authorization Authentication credentials Bearer token123, Basic dXNlcjpwYXNz
Cache-Control Caching directives for request no-cache, max-age=0
Content-Type Media type of request body application/json, multipart/form-data
Cookie Stored cookies sent to server session=abc; user=123
Host Target server hostname and port example.com:443
If-Modified-Since Conditional request timestamp Wed, 21 Oct 2015 07:28:00 GMT
If-None-Match Conditional request ETag "686897696a7c876b7e"
Origin Cross-origin request origin https://example.com
Referer URL of referring page https://example.com/page
User-Agent Client software identifier Mozilla/5.0 (Windows NT 10.0)

Standard Response Headers

Header Purpose Example Values
Access-Control-Allow-Origin CORS allowed origins https://example.com, *
Cache-Control Caching directives for response max-age=3600, no-store
Content-Encoding Compression applied to body gzip, br
Content-Length Body size in bytes 1234
Content-Type Media type of response body application/json, text/html
ETag Resource version identifier "686897696a7c876b7e"
Expires Response expiration timestamp Wed, 21 Oct 2025 07:28:00 GMT
Last-Modified Resource modification timestamp Wed, 21 Oct 2025 07:28:00 GMT
Location Redirect target URL https://example.com/new-location
Set-Cookie Cookie to store in client session=abc; Secure; HttpOnly
Strict-Transport-Security HTTPS enforcement policy max-age=31536000; includeSubDomains
Vary Headers affecting response content Accept-Encoding, Accept-Language

Security Headers

Header Protection Configuration
Content-Security-Policy XSS, injection attacks default-src 'self'; script-src 'self' https://cdn.example.com
Strict-Transport-Security Protocol downgrade max-age=31536000; includeSubDomains; preload
X-Content-Type-Options MIME sniffing attacks nosniff
X-Frame-Options Clickjacking DENY, SAMEORIGIN
X-XSS-Protection Reflected XSS 1; mode=block
Referrer-Policy Information leakage strict-origin-when-cross-origin
Permissions-Policy Feature access control geolocation=(), camera=()

Cache-Control Directives

Directive Effect Use Case
max-age=seconds Cache lifetime in seconds Cacheable content with known lifetime
no-cache Revalidate before use Content requiring freshness check
no-store Never cache Sensitive or personal data
public Allow shared cache storage Public resources safe to cache
private Client cache only User-specific content
must-revalidate Strict expiration enforcement Content requiring exact freshness
immutable Never revalidate Assets with versioned URLs

Ruby Header Access Patterns

Context Read Header Set Header
Net::HTTP Request N/A request['Header-Name'] = 'value'
Net::HTTP Response response['Header-Name'] N/A
Rack Environment env['HTTP_HEADER_NAME'] N/A
Rack Response N/A headers['Header-Name'] = 'value'
Rails Request request.headers['Header-Name'] N/A
Rails Response N/A response.headers['Header-Name'] = 'value'
Faraday Request N/A req.headers['Header-Name'] = 'value'
Faraday Response response.headers['Header-Name'] N/A

Common Content-Type Values

Media Type Usage File Extension
application/json JSON data interchange .json
application/xml XML documents .xml
application/pdf PDF documents .pdf
text/html HTML documents .html
text/plain Plain text .txt
text/css Stylesheets .css
application/javascript JavaScript code .js
image/jpeg JPEG images .jpg
image/png PNG images .png
multipart/form-data File uploads N/A
application/x-www-form-urlencoded Form submissions N/A