diff --git a/lib/protocol/multipart.rb b/lib/protocol/multipart.rb index a1fb747..e5623dd 100644 --- a/lib/protocol/multipart.rb +++ b/lib/protocol/multipart.rb @@ -4,6 +4,7 @@ # Copyright, 2025, by Samuel Williams. require_relative "multipart/version" +require_relative "multipart/error" require_relative "multipart/byte_limit" require_relative "multipart/headers" require_relative "multipart/parser" diff --git a/lib/protocol/multipart/byte_limit.rb b/lib/protocol/multipart/byte_limit.rb index 7ec2987..f6820f8 100644 --- a/lib/protocol/multipart/byte_limit.rb +++ b/lib/protocol/multipart/byte_limit.rb @@ -3,6 +3,8 @@ # Released under the MIT License. # Copyright, 2026, by Samuel Williams. +require_relative "error" + module Protocol module Multipart # Tracks consumed bytes against an optional maximum. @@ -30,7 +32,7 @@ def consume(size) @size += size if @maximum and @size > @maximum - raise RangeError, "Multipart #{@name} exceeded limit of #{@maximum}!" + raise LimitError, "Multipart #{@name} exceeded limit of #{@maximum}!" end return @size diff --git a/lib/protocol/multipart/error.rb b/lib/protocol/multipart/error.rb new file mode 100644 index 0000000..2979569 --- /dev/null +++ b/lib/protocol/multipart/error.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +module Protocol + module Multipart + # Raised when multipart processing exceeds a configured limit. + class LimitError < StandardError + end + end +end diff --git a/lib/protocol/multipart/parser.rb b/lib/protocol/multipart/parser.rb index 2587438..4d80ea0 100644 --- a/lib/protocol/multipart/parser.rb +++ b/lib/protocol/multipart/parser.rb @@ -4,6 +4,7 @@ # Copyright, 2025, by Samuel Williams. require "io/stream" +require_relative "error" require_relative "headers" module Protocol @@ -245,7 +246,7 @@ def read_line(size, limit, allowance: 0, chomp:) def check_limit(name, value, limit) if limit and value > limit - raise RangeError, "Multipart #{name} exceeded limit of #{limit}!" + raise LimitError, "Multipart #{name} exceeded limit of #{limit}!" end end diff --git a/protocol-multipart.gemspec b/protocol-multipart.gemspec index 534cced..3f3f0ff 100644 --- a/protocol-multipart.gemspec +++ b/protocol-multipart.gemspec @@ -26,5 +26,5 @@ Gem::Specification.new do |spec| spec.add_dependency "io-stream", "~> 0.8" spec.add_dependency "protocol-http", "~> 0.67" - spec.add_dependency "protocol-url", "~> 0.5" + spec.add_dependency "protocol-url", "~> 0.9" end diff --git a/releases.md b/releases.md index 05a258b..f567ba1 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Add `Protocol::Multipart::LimitError` for configured processing limits. + ## v0.4.0 - Use consistent limit naming for multipart parser constraints. diff --git a/test/protocol/multipart/byte_limit.rb b/test/protocol/multipart/byte_limit.rb index 3df1ba3..e8a92c4 100644 --- a/test/protocol/multipart/byte_limit.rb +++ b/test/protocol/multipart/byte_limit.rb @@ -16,7 +16,7 @@ it "raises when the maximum is exceeded" do limit = subject.new(1, name: :field_size) - expect{limit.consume(2)}.to raise_exception(RangeError, message: be =~ /field_size exceeded limit of 1/) + expect{limit.consume(2)}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /field_size exceeded limit of 1/) end it "can be unlimited" do diff --git a/test/protocol/multipart/form_data.rb b/test/protocol/multipart/form_data.rb index d42226d..ac0e996 100644 --- a/test/protocol/multipart/form_data.rb +++ b/test/protocol/multipart/form_data.rb @@ -204,7 +204,7 @@ def to_h expect do parse_field("12345", field_size_limit: 4) - end.to raise_exception(RangeError, message: be =~ /field_size exceeded limit of 4/) + end.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /field_size exceeded limit of 4/) end it "applies the upload size limit at its boundary" do @@ -213,7 +213,7 @@ def to_h expect do parse_upload("12345", upload_size_limit: 4) - end.to raise_exception(RangeError, message: be =~ /upload_size exceeded limit of 4/) + end.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /upload_size exceeded limit of 4/) end it "applies the upload size limit while discarding unread content" do @@ -224,7 +224,7 @@ def to_h expect do subject::Parser.new(upload_size_limit: 3).each(StringIO.new(serialize(form_data)), boundary: form_data.boundary).to_a - end.to raise_exception(RangeError, message: be =~ /upload_size exceeded/) + end.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /upload_size exceeded/) end it "applies the total size limit at its boundary" do @@ -233,7 +233,7 @@ def to_h expect do parse_fields({"first" => "12", "second" => "345"}, total_size_limit: 4) - end.to raise_exception(RangeError, message: be =~ /total_size exceeded limit of 4/) + end.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /total_size exceeded limit of 4/) end it "allows content limits to be disabled" do @@ -267,7 +267,7 @@ def to_h expect do parse_field("1", name: "a[b][c]", depth_limit: 2) - end.to raise_exception(RangeError, message: be =~ /depth exceeded limit of 2/) + end.to raise_exception(Protocol::URL::LimitError, message: be =~ /depth exceeded limit of 2/) end with "invalid form metadata" do diff --git a/test/protocol/multipart/parser.rb b/test/protocol/multipart/parser.rb index 164903d..cbe12eb 100644 --- a/test/protocol/multipart/parser.rb +++ b/test/protocol/multipart/parser.rb @@ -56,13 +56,13 @@ data = "preamble\r\n--#{boundary}\r\n\r\n--#{boundary}--\r\n" parser = Protocol::Multipart::Parser.new(StringIO.new(data), boundary, preamble_size_limit: 4) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /preamble_size exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /preamble_size exceeded/) end it "limits an unterminated preamble" do parser = Protocol::Multipart::Parser.new(StringIO.new("x" * 1024), boundary, preamble_size_limit: 16) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /preamble_size exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /preamble_size exceeded/) end it "allows a boundary after the maximum preamble size" do @@ -76,14 +76,14 @@ data = "--#{boundary}\r\nContent-Type: text/plain\r\n\r\nvalue\r\n--#{boundary}--\r\n" parser = Protocol::Multipart::Parser.new(StringIO.new(data), boundary, header_size_limit: 16) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /header_size exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /header_size exceeded/) end it "limits an unterminated header" do data = "--#{boundary}\r\nX-Test: #{'x' * 1024}" parser = Protocol::Multipart::Parser.new(StringIO.new(data), boundary, header_size_limit: 16) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /header_size exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /header_size exceeded/) end it "allows the header terminator after the maximum header size" do @@ -97,14 +97,14 @@ data = "--#{boundary}\r\nContent-Type: text/plain\r\nX-Test: true\r\n\r\nvalue\r\n--#{boundary}--\r\n" parser = Protocol::Multipart::Parser.new(StringIO.new(data), boundary, header_count_limit: 1) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /header_count exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /header_count exceeded/) end it "limits the part count" do data = "--#{boundary}\r\n\r\none\r\n--#{boundary}\r\n\r\ntwo\r\n--#{boundary}--\r\n" parser = Protocol::Multipart::Parser.new(StringIO.new(data), boundary, part_count_limit: 1) - expect{parser.each.to_a}.to raise_exception(RangeError, message: be =~ /part_count exceeded/) + expect{parser.each.to_a}.to raise_exception(Protocol::Multipart::LimitError, message: be =~ /part_count exceeded/) end it "allows limits to be disabled" do