class ObjectFramework::HTTP::Parser
Constants
- TOKEN
Attributes
Public Class Methods
Source
# File lib/object_framework/http/protocol.rb, line 45 def initialize(line_limit: 8 * 1024, header_limit: 32 * 1024, body_limit: 1024 * 1024) @line_limit, @header_limit, @body_limit = line_limit, header_limit, body_limit raise ConfigurationError, "HTTP limits must be positive integers" unless [line_limit, header_limit, body_limit].all? { |limit| limit.is_a?(Integer) && limit.positive? } end
Public Instance Methods
Source
# File lib/object_framework/http/protocol.rb, line 91 def parse_field(line) name, value = line.split(":", 2) raise BadRequest unless name && TOKEN.match?(name) && value && !value.match?(/[\x00-\x08\x0a-\x1f\x7f]/) [name.downcase, value.strip] end
Source
# File lib/object_framework/http/protocol.rb, line 50 def parse_head(raw) raise BadRequest unless raw.end_with?("\r\n\r\n") lines = raw.b.split("\r\n", -1) request_line = lines.shift raise URITooLong if request_line.bytesize > line_limit raise HeadersTooLarge if raw.bytesize - request_line.bytesize - 2 > header_limit method, target, version = request_line.split(" ", -1) raise BadRequest unless method && TOKEN.match?(method) && target && version && request_line == "#{method} #{target} #{version}" raise VersionNotSupported unless version == "HTTP/1.1" raise BadRequest if target.match?(/[\x00-\x20\x7f-\xff#]/n) headers = {} lines[0...-2].each do |line| name, value = parse_field(line) if headers.key?(name) raise BadRequest if %w[host content-length transfer-encoding].include?(name) headers[name] = "#{headers[name]}, #{value}" else headers[name] = value end end host = headers["host"] raise BadRequest unless host && /\A(?:\[[0-9a-fA-F:.]+\]|[A-Za-z0-9.-]+)(?::\d{1,5})?\z/.match?(host) raise BadRequest if headers.key?("content-length") && headers.key?("transfer-encoding") length = 0 if headers.key?("content-length") raise BadRequest unless /\A[0-9]+\z/.match?(headers["content-length"]) length = Integer(headers["content-length"], 10) raise PayloadTooLarge if length > body_limit end encoding = headers["transfer-encoding"]&.downcase if encoding && encoding != "chunked" raise BadRequest if encoding.split(",").map(&:strip).count("chunked") != 1 || !encoding.end_with?("chunked") raise NotImplemented end expect = headers["expect"] raise ExpectationFailed if expect && expect.downcase != "100-continue" path, query = parse_target(method, target) Head.new(method:, path:, query:, headers: Values.detach(headers), length:, chunked: encoding == "chunked", keep_alive: !headers.fetch("connection", "").downcase.split(",").map(&:strip).include?("close")) end
Source
# File lib/object_framework/http/protocol.rb, line 97 def read_body(reader, head, deadline:) return reader.read_exact(head.length, deadline:) unless head.chunked body, trailer_size = +"".b, 0 loop do line = reader.read_line(limit: line_limit, deadline:) size, extension = line.split(";", 2) raise BadRequest unless /\A[0-9a-fA-F]+\z/.match?(size) if extension && !/\A(?:[!#$%&'*+.^_`|~0-9A-Za-z-]+(?:=(?:[!#$%&'*+.^_`|~0-9A-Za-z-]+|"(?:[^"\\\r\n]|\\[\x20-\x7e])*"))?)(?:;[!#$%&'*+.^_`|~0-9A-Za-z-]+(?:=(?:[!#$%&'*+.^_`|~0-9A-Za-z-]+|"(?:[^"\\\r\n]|\\[\x20-\x7e])*"))?)*\z/.match?(extension) raise BadRequest end count = Integer(size, 16) raise PayloadTooLarge if body.bytesize + count > body_limit if count.zero? loop do trailer = reader.read_line(limit: header_limit, deadline:) trailer_size += trailer.bytesize + 2 raise HeadersTooLarge if trailer_size > header_limit break if trailer.empty? name, = parse_field(trailer) raise BadRequest if %w[content-length transfer-encoding host connection content-type authorization].include?(name) end return body end body << reader.read_exact(count, deadline:) raise BadRequest unless reader.read_exact(2, deadline:) == "\r\n" end rescue EOFError raise BadRequest end