pax_global_header00006660000000000000000000000064140737374110014521gustar00rootroot0000000000000052 comment=fbebcded3a04758fc35f38c978071552551ee6c8 ruby-protocol-http-0.22.5/000077500000000000000000000000001407373741100154045ustar00rootroot00000000000000ruby-protocol-http-0.22.5/.editorconfig000066400000000000000000000000641407373741100200610ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-protocol-http-0.22.5/.github/000077500000000000000000000000001407373741100167445ustar00rootroot00000000000000ruby-protocol-http-0.22.5/.github/workflows/000077500000000000000000000000001407373741100210015ustar00rootroot00000000000000ruby-protocol-http-0.22.5/.github/workflows/development.yml000066400000000000000000000015561407373741100240550ustar00rootroot00000000000000name: Development on: [push, pull_request] jobs: test: runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - "2.6" - "2.7" - "3.0" experimental: [false] env: [""] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: ${{matrix.env}} bundle exec rspec ruby-protocol-http-0.22.5/.gitignore000066400000000000000000000002251407373741100173730ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /external/ # rspec failure tracking .rspec_status /gems.locked .covered.db ruby-protocol-http-0.22.5/.rspec000066400000000000000000000000671407373741100165240ustar00rootroot00000000000000--format documentation --warnings --require spec_helperruby-protocol-http-0.22.5/DESIGN.md000066400000000000000000000047021407373741100167020ustar00rootroot00000000000000 # Middleware Design `Body::Writable` is a queue of String chunks. ## Request Response Model ~~~ruby class Request attr :verb attr :target attr :body end class Response attr :status attr :headers attr :body end def call(request) return response end def call(request) return @app.call(request) end ~~~ ## Stream Model ~~~ruby class Stream attr :verb attr :target def respond(status, headers) = ... attr_accessor :input attr_accessor :output end class Response def initialize(verb, target) @input = Body::Writable.new @output = Body::Writable.new end def request(verb, target) # Create a request stream suitable for writing into the buffered response: Stream.new(verb, target, @input, @output) end def write(...) @input.write(...) end def read @output.read end end def call(stream) # nothing. maybe error end def call(stream) return @app.call(stream) end ~~~ # Client Design ## Request Response Model ~~~ruby request = Request.new("GET", url) response = call(request) response.headers response.read ~~~ ## Stream Model ~~~ruby response = Response.new call(response.request("GET", url)) response.headers response.read ~~~ ## Differences The request/response model has a symmetrical design which naturally uses the return value for the result of executing the request. The result encapsulates the behaviour of how to read the response status, headers and body. Because of that, streaming input and output becomes a function of the result object itself. As in: ~~~ruby def call(request) body = Body::Writable.new Fiber.schedule do while chunk = request.input.read body.write(chunk.reverse) end end return Response[200, [], body] end input = Body::Writable.new response = call(... body ...) input.write("Hello World") input.close response.read -> "dlroW olleH" ~~~ The streaming model does not have the same symmetry, and instead opts for a uni-directional flow of information. ~~~ruby def call(stream) Fiber.schedule do while chunk = stream.read stream.write(chunk.reverse) end end end input = Body::Writable.new response = Response.new(...input...) call(response.stream) input.write("Hello World") input.close response.read -> "dlroW olleH" ~~~ The value of this uni-directional flow is that it is natural for the stream to be taken out of the scope imposed by the nested `call(request)` model. However, the user must explicitly close the stream, since it's no longer scoped to the client and/or server. ruby-protocol-http-0.22.5/README.md000066400000000000000000000045471407373741100166750ustar00rootroot00000000000000# Protocol::HTTP Provides abstractions for working with the HTTP protocol. [![Development Status](https://github.com/socketry/protocol-http/workflows/Development/badge.svg)](https://github.com/socketry/protocol-http/actions?workflow=Development) ## Installation Add this line to your application's Gemfile: ``` ruby gem 'protocol-http' ``` And then execute: $ bundle Or install it yourself as: $ gem install protocol-http ## Usage ### Headers ``` ruby require 'protocol/http/headers' headers = Protocol::HTTP::Headers.new headers['Content-Type'] = "image/jpeg" headers['content-type'] # => "image/jpeg" ``` ### Reference ``` ruby require 'protocol/http/reference' reference = Protocol::HTTP::Reference.new("/search", q: 'kittens') reference.to_s # => "/search?q=kittens" ``` ### URL ``` ruby require 'protocol/http/url' reference = Protocol::HTTP::Reference.parse("/search?q=kittens") parameters = Protocol::HTTP::URL.decode(reference.query_string) # => {"q"=>"kittens"} ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-protocol-http-0.22.5/bake.rb000066400000000000000000000014551407373741100166400ustar00rootroot00000000000000# frozen_string_literal: true def external require 'bundler' Bundler.with_unbundled_env do clone_and_test("protocol-http1") clone_and_test("protocol-http2") clone_and_test("async-websocket") clone_and_test("async-http") clone_and_test("async-rest") clone_and_test("falcon") end end private def clone_and_test(name) path = "external/#{name}" unless File.exist?(path) system("git", "clone", "https://git@github.com/socketry/#{name}", path) end gemfile = [ File.join(path, "gems.rb"), File.join(path, "Gemfile"), ].find{|path| File.exist?(path)} system("git", "checkout", "-f", File.basename(gemfile), chdir: path) File.open(gemfile, "a") do |file| file.puts('', 'gem "protocol-http", path: "../../"') end system("bundle install && bundle exec rspec", chdir: path) end ruby-protocol-http-0.22.5/gems.rb000066400000000000000000000004151407373741100166640ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" # Specify your gem's dependencies in protocol-http.gemspec gemspec group :maintenance, optional: true do gem "bake-modernize" gem "bake-bundler" end group :test do gem 'async-io' gem 'async-rspec' end ruby-protocol-http-0.22.5/lib/000077500000000000000000000000001407373741100161525ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/000077500000000000000000000000001407373741100200135ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/http.rb000066400000000000000000000022541407373741100213220ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative "http/version" ruby-protocol-http-0.22.5/lib/protocol/http/000077500000000000000000000000001407373741100207725ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/http/accept_encoding.rb000066400000000000000000000042651407373741100244330ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'middleware' require_relative 'body/buffered' require_relative 'body/inflate' module Protocol module HTTP # Set a valid accept-encoding header and decode the response. class AcceptEncoding < Middleware ACCEPT_ENCODING = 'accept-encoding'.freeze CONTENT_ENCODING = 'content-encoding'.freeze DEFAULT_WRAPPERS = { 'gzip' => Body::Inflate.method(:for), 'identity' => ->(body){body}, } def initialize(app, wrappers = DEFAULT_WRAPPERS) super(app) @accept_encoding = wrappers.keys.join(', ') @wrappers = wrappers end def call(request) request.headers[ACCEPT_ENCODING] = @accept_encoding response = super if body = response.body and !body.empty? and content_encoding = response.headers.delete(CONTENT_ENCODING) # We want to unwrap all encodings content_encoding.reverse_each do |name| if wrapper = @wrappers[name] body = wrapper.call(body) end end response.body = body end return response end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/000077500000000000000000000000001407373741100217275ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/http/body/buffered.rb000066400000000000000000000051001407373741100240320ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'readable' module Protocol module HTTP module Body # A body which buffers all it's contents. class Buffered < Readable # Wraps an array into a buffered body. # @return [Readable, nil] the wrapped body or nil if nil was given. def self.wrap(body) if body.is_a?(Readable) return body elsif body.is_a?(Array) return self.new(body) elsif body.is_a?(String) return self.new([body]) elsif body return self.for(body) end end def self.for(body) chunks = [] body.each do |chunk| chunks << chunk end self.new(chunks) end def initialize(chunks = [], length = nil) @chunks = chunks @length = length @index = 0 @digest = nil end attr :chunks def finish self end def length @length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize} end def empty? @index >= @chunks.length end # A buffered response is always ready. def ready? true end def read if chunk = @chunks[@index] @index += 1 return chunk.dup end end def write(chunk) @digest&.update(chunk) @chunks << chunk end def rewind @index = 0 end def inspect "\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>" end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/completable.rb000066400000000000000000000036201407373741100245440ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'wrapper' module Protocol module HTTP module Body # Invokes a callback once the body has completed, either successfully or due to an error. class Completable < Wrapper def self.wrap(message, &block) if body = message&.body and !body.empty? message.body = self.new(message.body, block) else yield end end def initialize(body, callback) super(body) @callback = callback end def finish if @body result = super @callback.call @body = nil return result end end def close(error = nil) if @body super @callback.call(error) @body = nil end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/deflate.rb000066400000000000000000000056231407373741100236660ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'wrapper' require 'zlib' module Protocol module HTTP module Body class ZStream < Wrapper DEFAULT_LEVEL = 7 DEFLATE = -Zlib::MAX_WBITS GZIP = Zlib::MAX_WBITS | 16 ENCODINGS = { 'deflate' => DEFLATE, 'gzip' => GZIP, } def self.encoding_name(window_size) if window_size <= -8 return 'deflate' elsif window_size >= 16 return 'gzip' else return 'compress' end end def initialize(body, stream) super(body) @stream = stream @input_length = 0 @output_length = 0 end def close(error = nil) @stream.close unless @stream.closed? super end def length # We don't know the length of the output until after it's been compressed. nil end attr :input_length attr :output_length def ratio if @input_length != 0 @output_length.to_f / @input_length.to_f else 1.0 end end def inspect "#{super} | \#<#{self.class} #{(ratio*100).round(2)}%>" end end class Deflate < ZStream def self.for(body, window_size = GZIP, level = DEFAULT_LEVEL) self.new(body, Zlib::Deflate.new(level, window_size)) end def read return if @stream.finished? # The stream might have been closed while waiting for the chunk to come in. if chunk = super @input_length += chunk.bytesize chunk = @stream.deflate(chunk, Zlib::SYNC_FLUSH) @output_length += chunk.bytesize return chunk elsif !@stream.closed? chunk = @stream.finish @output_length += chunk.bytesize return chunk.empty? ? nil : chunk end end end end end endruby-protocol-http-0.22.5/lib/protocol/http/body/digestable.rb000066400000000000000000000036721407373741100243670ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'wrapper' require 'digest/sha2' module Protocol module HTTP module Body # Invokes a callback once the body has finished reading. class Digestable < Wrapper def self.wrap(message, digest = Digest::SHA256.new, &block) if body = message&.body and !body.empty? message.body = self.new(message.body, digest, block) end end def initialize(body, digest = Digest::SHA256.new, callback = nil) super(body) @digest = digest @callback = callback end def digest @digest end def etag @digest.hexdigest.dump end def read if chunk = super @digest.update(chunk) return chunk else @callback&.call(self) return nil end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/file.rb000066400000000000000000000051051407373741100231740ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'readable' require 'async/io/stream' module Protocol module HTTP module Body class File < Readable BLOCK_SIZE = Async::IO::BLOCK_SIZE MODE = ::File::RDONLY | ::File::BINARY def self.open(path, *arguments, **options) self.new(::File.open(path, MODE), *arguments, **options) end def initialize(file, range = nil, size: file.size, block_size: BLOCK_SIZE) @file = file @block_size = block_size if range @file.seek(range.min) @offset = range.min @length = @remaining = range.size else @offset = 0 @length = @remaining = size end end def close(error = nil) @file.close @remaining = 0 super end attr :file attr :offset attr :length def empty? @remaining == 0 end def ready? true end def rewind @file.seek(@offset) end def read if @remaining > 0 amount = [@remaining, @block_size].min if chunk = @file.read(amount) @remaining -= chunk.bytesize return chunk end end end def join return "" if @remaining == 0 buffer = @file.read(@remaining) @remaining = 0 return buffer end def inspect "\#<#{self.class} file=#{@file.inspect} offset=#{@offset} remaining=#{@remaining}>" end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/head.rb000066400000000000000000000030431407373741100231550ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'readable' module Protocol module HTTP module Body class Head < Readable def self.for(body) head = self.new(body.length) body.close return head end def initialize(length) @length = length end def empty? true end def ready? true end def length @length end end end end endruby-protocol-http-0.22.5/lib/protocol/http/body/inflate.rb000066400000000000000000000036511407373741100237030ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'zlib' require_relative 'deflate' module Protocol module HTTP module Body class Inflate < ZStream def self.for(body, encoding = GZIP) self.new(body, Zlib::Inflate.new(encoding)) end def read return if @stream.finished? # The stream might have been closed while waiting for the chunk to come in. if chunk = super @input_length += chunk.bytesize # It's possible this triggers the stream to finish. chunk = @stream.inflate(chunk) @output_length += chunk.bytesize elsif !@stream.closed? chunk = @stream.finish @output_length += chunk.bytesize end if chunk.empty? and @stream.finished? return nil end return chunk end end end end endruby-protocol-http-0.22.5/lib/protocol/http/body/readable.rb000066400000000000000000000061051407373741100240150ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'async/io/buffer' module Protocol module HTTP module Body # A generic base class for wrapping body instances. Typically you'd override `#read`. # The implementation assumes a sequential unbuffered stream of data. # def each -> yield(String | nil) # def read -> String | nil # def join -> String # def finish -> buffer the stream and close it. # def close(error = nil) -> close the stream immediately. # end class Readable # The consumer can call stop to signal that the stream output has terminated. def close(error = nil) end # Will read return any data? def empty? false end # Whether calling read will block. # We prefer pessimistic implementation, and thus default to `false`. # @return [Boolean] def ready? false end def length nil end # Read the next available chunk. def read nil end # Should the internal mechanism prefer to use {call}? # @returns [Boolean] def stream? false end # Write the body to the given stream. def call(stream) while chunk = self.read stream.write(chunk) end ensure stream.close($!) end # Read all remaining chunks into a buffered body and close the underlying input. def finish # Internally, this invokes `self.each` which then invokes `self.close`. Buffered.for(self) end # Enumerate all chunks until finished, then invoke `#close`. def each while chunk = self.read yield chunk end ensure self.close($!) end # Read all remaining chunks into a single binary string using `#each`. def join buffer = String.new.force_encoding(Encoding::BINARY) self.each do |chunk| buffer << chunk chunk.clear end if buffer.empty? return nil else return buffer end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/reader.rb000066400000000000000000000050311407373741100235150ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module Body # General operations for interacting with a request or response body. module Reader # Read chunks from the body. # @yield [String] read chunks from the body. def each(&block) if @body @body.each(&block) @body = nil end end # Reads the entire request/response body. # @return [String] the entire body as a string. def read if @body buffer = @body.join @body = nil return buffer end end # Gracefully finish reading the body. This will buffer the remainder of the body. # @return [Buffered] buffers the entire body. def finish if @body body = @body.finish @body = nil return body end end # Write the body of the response to the given file path. def save(path, mode = ::File::WRONLY|::File::CREAT, *args) if @body ::File.open(path, mode, *args) do |file| self.each do |chunk| file.write(chunk) end end end end # Close the connection as quickly as possible. Discards body. May close the underlying connection if necessary to terminate the stream. def close(error = nil) if @body @body.close(error) @body = nil end end # Whether there is a body? def body? @body and !@body.empty? end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/rewindable.rb000066400000000000000000000042411407373741100243710ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'wrapper' require_relative 'buffered' module Protocol module HTTP module Body # A body which buffers all it's contents as it is `#read`. class Rewindable < Wrapper def initialize(body) super(body) @chunks = [] @index = 0 end def empty? (@index >= @chunks.size) && super end def ready? (@index < @chunks.size) || super end # A rewindable body wraps some other body. Convert it to a buffered body def buffered Buffered.new(@chunks) end def read if @index < @chunks.size chunk = @chunks[@index] @index += 1 else if chunk = super @chunks << chunk @index += 1 end end # We dup them on the way out, so that if someone modifies the string, it won't modify the rewindability. return chunk&.dup end def rewind @index = 0 end def inspect "\#<#{self.class} #{@index}/#{@chunks.size} chunks read>" end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/stream.rb000066400000000000000000000110431407373741100235460ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module Body # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind. class Stream def initialize(input, output) @input = input @output = output # Will hold remaining data in `#read`. @buffer = nil @closed = false end attr :input attr :output # rack.hijack_io must respond to: # read, write, read_nonblock, write_nonblock, flush, close, close_read, close_write, closed? # read behaves like IO#read. Its signature is read([length, [buffer]]). If given, length must be a non-negative Integer (>= 0) or nil, and buffer must be a String and may not be nil. If length is given and not nil, then this method reads at most length bytes from the input stream. If length is not given or nil, then this method reads all data until EOF. When EOF is reached, this method returns nil if length is given and not nil, or “” if length is not given or is nil. If buffer is given, then the read data will be placed into buffer instead of a newly created String object. # @param length [Integer] the amount of data to read # @param buffer [String] the buffer which will receive the data # @return a buffer containing the data def read(length = nil, buffer = nil) buffer ||= Async::IO::Buffer.new buffer.clear until buffer.bytesize == length @buffer = read_next if @buffer.nil? break if @buffer.nil? remaining_length = length - buffer.bytesize if length if remaining_length && remaining_length < @buffer.bytesize # We know that we are not going to reuse the original buffer. # But byteslice will generate a hidden copy. So let's freeze it first: @buffer.freeze buffer << @buffer.byteslice(0, remaining_length) @buffer = @buffer.byteslice(remaining_length, @buffer.bytesize) else buffer << @buffer @buffer = nil end end return nil if buffer.empty? && length && length > 0 return buffer end def read_nonblock(length, buffer = nil) @buffer ||= read_next chunk = nil if @buffer.bytesize > length chunk = @buffer.byteslice(0, length) @buffer = @buffer.byteslice(length, @buffer.bytesize) else chunk = @buffer @buffer = nil end if buffer buffer.replace(chunk) else buffer = chunk end return buffer end def write(buffer) @output.write(buffer) end alias write_nonblock write def flush end def close_read @input&.close end # close must never be called on the input stream. huh? def close_write @output&.close end # Close the input and output bodies. def close self.close_read self.close_write ensure @closed = true end # Whether the stream has been closed. def closed? @closed end # Whether there are any output chunks remaining? def empty? @output.empty? end private def read_next if chunk = @input&.read return chunk else @input = nil return nil end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/body/wrapper.rb000066400000000000000000000040501407373741100237330ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'readable' module Protocol module HTTP module Body # Wrapping body instance. Typically you'd override `#read`. class Wrapper < Readable def self.wrap(message) if body = message.body message.body = self.new(body) end end def initialize(body) @body = body end # The wrapped body. attr :body # Buffer any remaining body. def finish @body.finish end def close(error = nil) @body.close(error) super end def empty? @body.empty? end def ready? @body.ready? end def length @body.length end # Read the next available chunk. def read @body.read end def inspect @body.inspect end def stream? @body.stream? end def call(stream) @body.call(stream) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/content_encoding.rb000066400000000000000000000054311407373741100246420ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'middleware' require_relative 'body/buffered' require_relative 'body/deflate' module Protocol module HTTP # Encode a response according the the request's acceptable encodings. class ContentEncoding < Middleware DEFAULT_WRAPPERS = { 'gzip' => Body::Deflate.method(:for) } DEFAULT_CONTENT_TYPES = %r{^(text/.*?)|(.*?/json)|(.*?/javascript)$} def initialize(app, content_types = DEFAULT_CONTENT_TYPES, wrappers = DEFAULT_WRAPPERS) super(app) @content_types = content_types @wrappers = wrappers end def call(request) response = super # Early exit if the response has already specified a content-encoding. return response if response.headers['content-encoding'] # This is a very tricky issue, so we avoid it entirely. # https://lists.w3.org/Archives/Public/ietf-http-wg/2014JanMar/1179.html return response if response.partial? # Ensure that caches are aware we are varying the response based on the accept-encoding request header: response.headers.add('vary', 'accept-encoding') # TODO use http-accept and sort by priority if !response.body.empty? and accept_encoding = request.headers['accept-encoding'] if content_type = response.headers['content-type'] and @content_types =~ content_type body = response.body accept_encoding.each do |name| if wrapper = @wrappers[name] response.headers['content-encoding'] = name body = wrapper.call(body) break end end response.body = body end end return response end end end end ruby-protocol-http-0.22.5/lib/protocol/http/cookie.rb000066400000000000000000000044711407373741100225760ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'url' module Protocol module HTTP # Represents an individual cookie key-value pair. class Cookie def initialize(name, value, directives) @name = name @value = value @directives = directives end attr :name attr :value attr :directives def encoded_name URL.escape(@name) end def encoded_value URL.escape(@value) end def to_s buffer = String.new.b buffer << encoded_name << '=' << encoded_value if @directives @directives.collect do |key, value| buffer << ';' case value when String buffer << key << '=' << value when TrueClass buffer << key end end end return buffer end def self.parse(string) head, *directives = string.split(/\s*;\s*/) key, value = head.split('=') directives = self.parse_directives(directives) self.new( URL.unescape(key), URL.unescape(value), directives, ) end def self.parse_directives(strings) strings.collect do |string| key, value = string.split('=', 2) [key, value || true] end.to_h end end end end ruby-protocol-http-0.22.5/lib/protocol/http/error.rb000066400000000000000000000023721407373741100224540ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP # A generic, HTTP protocol error. class Error < StandardError end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/000077500000000000000000000000001407373741100222225ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/http/header/authorization.rb000066400000000000000000000032701407373741100254510ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'base64' module Protocol module HTTP module Header # Used for basic authorization. # # ~~~ ruby # headers.add('authorization', Authorization.basic("my_username", "my_password")) # ~~~ class Authorization < String # Splits the header and # @return [Tuple(String, String)] def credentials self.split(/\s+/, 2) end def self.basic(username, password) encoded = "#{username}:#{password}" self.new( "Basic #{Base64.strict_encode64(encoded)}" ) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/cache_control.rb000066400000000000000000000042151407373741100253540ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'split' module Protocol module HTTP module Header class CacheControl < Split PRIVATE = 'private' PUBLIC = 'public' NO_CACHE = 'no-cache' NO_STORE = 'no-store' MAX_AGE = 'max-age' STATIC = 'static' DYNAMIC = 'dynamic' STREAMING = 'streaming' def initialize(value) super(value.downcase) end def << value super(value.downcase) end def static? self.include?(STATIC) end def dynamic? self.include?(DYNAMIC) end def streaming? self.include?(STREAMING) end def private? self.include?(PRIVATE) end def public? self.include?(PUBLIC) end def no_cache? self.include?(NO_CACHE) end def no_store? self.include?(NO_STORE) end def max_age if value = self.find{|value| value.start_with?(MAX_AGE)} _, age = value.split('=', 2) return Integer(age) end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/connection.rb000066400000000000000000000031721407373741100247110ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'split' module Protocol module HTTP module Header class Connection < Split KEEP_ALIVE = 'keep-alive' CLOSE = 'close' UPGRADE = 'upgrade' def initialize(value) super(value.downcase) end def << value super(value.downcase) end def keep_alive? self.include?(KEEP_ALIVE) end def close? self.include?(CLOSE) end def upgrade? self.include?(UPGRADE) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/cookie.rb000066400000000000000000000033031407373741100240170ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'multiple' require_relative '../cookie' module Protocol module HTTP module Header # The Cookie HTTP request header contains stored HTTP cookies previously sent by the server with the Set-Cookie header. class Cookie < Multiple def to_h cookies = self.collect do |string| HTTP::Cookie.parse(string) end cookies.map{|cookie| [cookie.name, cookie]}.to_h end end # The Set-Cookie HTTP response header sends cookies from the server to the user agent. class SetCookie < Cookie end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/etag.rb000066400000000000000000000025111407373741100234660ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module Header class ETag < String def << value replace(value) end def weak? self.start_with('\W') end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/etags.rb000066400000000000000000000027221407373741100236550ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'split' module Protocol module HTTP module Header # This implementation is not strictly correct according to the RFC-specified format. class ETags < Split def wildcard? self.include?('*') end def match?(etag) wildcard? || self.include?(etag) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/multiple.rb000066400000000000000000000026411407373741100244050ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module Header # Header value which is split by newline charaters (e.g. cookies). class Multiple < Array def initialize(value) super() self << value end def to_s join("\n") end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/split.rb000066400000000000000000000027321407373741100237060ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module Header # Header value which is split by commas. class Split < Array COMMA = /\s*,\s*/ def initialize(value) super(value.split(COMMA)) end def << value self.push(*value.split(COMMA)) end def to_s join(", ") end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/header/vary.rb000066400000000000000000000025651407373741100235400ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'split' module Protocol module HTTP module Header class Vary < Split def initialize(value) super(value.downcase) end def << value super(value.downcase) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/headers.rb000066400000000000000000000177301407373741100227420ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'header/split' require_relative 'header/multiple' require_relative 'header/cookie' require_relative 'header/connection' require_relative 'header/cache_control' require_relative 'header/etag' require_relative 'header/etags' require_relative 'header/vary' require_relative 'header/authorization' module Protocol module HTTP # Headers are an array of key-value pairs. Some header keys represent multiple values. class Headers Split = Header::Split Multiple = Header::Multiple TRAILER = 'trailer' # Construct an instance from a headers Array or Hash. No-op if already an instance of `Headers`. If the underlying array is frozen, it will be duped. # @return [Headers] an instance of headers. def self.[] headers if headers.nil? return self.new end if headers.is_a?(self) if headers.frozen? return headers.dup else return headers end end fields = headers.to_a if fields.frozen? fields = fields.dup end return self.new(fields) end def initialize(fields = [], indexed = nil) @fields = fields @indexed = indexed # Marks where trailer start in the @fields array. @tail = nil end def initialize_dup(other) super @fields = @fields.dup @indexed = @indexed.dup end def clear @fields.clear @indexed = nil @tail = nil end # Flatten trailer into the headers. def flatten! if @tail self.delete(TRAILER) @tail = nil end return self end def flatten self.dup.flatten! end # An array of `[key, value]` pairs. attr :fields # @return the trailer if there are any. def trailer? @tail != nil end # Record the current headers, and prepare to receive trailer. def trailer!(&block) return nil unless self.include?(TRAILER) @tail ||= @fields.size return to_enum(:trailer!) unless block_given? if @tail @fields.drop(@tail).each(&block) end end # Enumerate all headers in the trailer, if there are any. def trailer(&block) return to_enum(:trailer) unless block_given? if @tail @fields.drop(@tail).each(&block) end end def freeze return if frozen? # Ensure @indexed is generated: self.to_h @fields.freeze @indexed.freeze super end def empty? @fields.empty? end def each(&block) @fields.each(&block) end def include? key self[key] != nil end def keys self.to_h.keys end def extract(keys) deleted, @fields = @fields.partition do |field| keys.include?(field.first.downcase) end if @indexed keys.each do |key| @indexed.delete(key) end end return deleted end # Add the specified header key value pair. # # @param key [String] the header key. # @param value [String] the header value to assign. def add(key, value) self[key] = value end # Set the specified header key to the specified value, replacing any existing header keys with the same name. # @param key [String] the header key to replace. # @param value [String] the header value to assign. def set(key, value) # TODO This could be a bit more efficient: self.delete(key) self.add(key, value) end def merge!(headers) headers.each do |key, value| self[key] = value end return self end def merge(headers) self.dup.merge!(headers) end # Append the value to the given key. Some values can be appended multiple times, others can only be set once. # @param key [String] The header key. # @param value The header value. def []= key, value if @indexed merge_into(@indexed, key.downcase, value) end @fields << [key, value] end POLICY = { # Headers which may only be specified once. 'content-type' => false, 'content-disposition' => false, 'content-length' => false, 'user-agent' => false, 'referer' => false, 'host' => false, 'if-modified-since' => false, 'if-unmodified-since' => false, 'from' => false, 'location' => false, 'max-forwards' => false, # Custom headers: 'connection' => Header::Connection, 'cache-control' => Header::CacheControl, 'vary' => Header::Vary, # Headers specifically for proxies: 'via' => Split, 'x-forwarded-for' => Split, # Authorization headers: 'authorization' => Header::Authorization, 'proxy-authorization' => Header::Authorization, # Cache validations: 'etag' => Header::ETag, 'if-match' => Header::ETags, 'if-none-match' => Header::ETags, # Headers which may be specified multiple times, but which can't be concatenated: 'www-authenticate' => Multiple, 'proxy-authenticate' => Multiple, # Custom headers: 'set-cookie' => Header::SetCookie, 'cookie' => Header::Cookie, }.tap{|hash| hash.default = Split} # Delete all headers with the given key, and return the merged value. def delete(key) deleted, @fields = @fields.partition do |field| field.first.downcase == key end if deleted.empty? return nil end if @indexed return @indexed.delete(key) elsif policy = POLICY[key] (key, value), *tail = deleted merged = policy.new(value) tail.each{|k,v| merged << v} return merged else key, value = deleted.last return value end end protected def merge_into(hash, key, value) if policy = POLICY[key] if current_value = hash[key] current_value << value else hash[key] = policy.new(value) end else # We can't merge these, we only expose the last one set. hash[key] = value end end def [] key to_h[key] end # A hash table of `{key, policy[key].map(values)}` def to_h @indexed ||= @fields.inject({}) do |hash, (key, value)| merge_into(hash, key.downcase, value) hash end end def inspect "#<#{self.class} #{@fields.inspect}>" end def == other case other when Hash to_h == other when Headers @fields == other.fields else @fields == other end end # Used for merging objects into a sequential list of headers. Normalizes header keys and values. class Merged include Enumerable def initialize(*all) @all = all end def clear @all.clear end def << headers @all << headers return self end # @yield [String, String] header key (lower case) and value (as string). def each(&block) @all.each do |headers| headers.each do |key, value| yield key.downcase, value.to_s end end end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/methods.rb000066400000000000000000000037051407373741100227670ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP # All supported HTTP methods class Methods GET = 'GET' POST = 'POST' PUT = 'PUT' PATCH = 'PATCH' DELETE = 'DELETE' HEAD = 'HEAD' OPTIONS = 'OPTIONS' LINK = 'LINK' UNLINK = 'UNLINK' TRACE = 'TRACE' CONNECT = 'CONNECT' def self.valid?(name) const_defined?(name) rescue NameError # Ruby will raise an exception if the name is not valid for a constant. return false end def self.each constants.each do |name| yield name, const_get(name) end end # Use Methods.constants to get all constants. self.each do |name, value| define_method(name.downcase) do |location, headers = nil, body = nil| self.call( Request[value, location.to_s, Headers[headers], body] ) end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/middleware.rb000066400000000000000000000036641407373741100234450ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'methods' require_relative 'headers' require_relative 'request' require_relative 'response' module Protocol module HTTP class Middleware < Methods # Convert a block to a middleware delegate. def self.for(&block) def block.close end return self.new(block) end def initialize(delegate) @delegate = delegate end attr :delegate def close @delegate.close end def call(request) @delegate.call(request) end module Okay def self.close end def self.call(request) Response[200, {}, []] end end module HelloWorld def self.close end def self.call(request) Response[200, Headers['content-type' => 'text/plain'], ["Hello World!"]] end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/middleware/000077500000000000000000000000001407373741100231075ustar00rootroot00000000000000ruby-protocol-http-0.22.5/lib/protocol/http/middleware/builder.rb000066400000000000000000000036471407373741100250740ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative '../headers' module Protocol module HTTP class Middleware module NotFound def self.close end def self.call(request) Response[404, Headers[], []] end end class Builder def initialize(default_app = NotFound) @use = [] @app = default_app end def use(middleware, *arguments, &block) @use << proc {|app| middleware.new(app, *arguments, &block)} end ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true) def run(app) @app = app end def to_app @use.reverse.inject(@app) {|app, use| use.call(app)} end end def self.build(&block) builder = Builder.new builder.instance_eval(&block) return builder.to_app end end end end ruby-protocol-http-0.22.5/lib/protocol/http/reference.rb000066400000000000000000000126301407373741100232570ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'url' module Protocol module HTTP # A relative reference, excluding any authority. The path part of an HTTP request. class Reference include Comparable # Generate a reference from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`. def self.parse(path = '/', parameters = nil) base, fragment = path.split('#', 2) path, query_string = base.split('?', 2) self.new(path, query_string, fragment, parameters) end def initialize(path = '/', query_string = nil, fragment = nil, parameters = nil) @path = path @query_string = query_string @fragment = fragment @parameters = parameters end # The path component, e.g. /foo/bar/index.html attr_accessor :path # The un-parsed query string, e.g. 'x=10&y=20' attr_accessor :query_string # A fragment, the part after the '#' attr_accessor :fragment # User supplied parameters that will be appended to the query part. attr_accessor :parameters def freeze return self if frozen? @path.freeze @query_string.freeze @fragment.freeze @parameters.freeze super end def to_ary [@path, @query_string, @fragment, @parameters] end def <=> other to_ary <=> other.to_ary end def self.[] reference if reference.is_a? self return reference else return self.parse(reference) end end def parameters? @parameters and !@parameters.empty? end def query_string? @query_string and !@query_string.empty? end def fragment? @fragment and !@fragment.empty? end def append(buffer) if query_string? buffer << URL.escape_path(@path) << '?' << @query_string buffer << '&' << URL.encode(@parameters) if parameters? else buffer << URL.escape_path(@path) buffer << '?' << URL.encode(@parameters) if parameters? end if fragment? buffer << '#' << URL.escape(@fragment) end return buffer end def to_s append(String.new) end # Merges two references as specified by RFC2396, similar to `URI.join`. def + other other = self.class[other] self.class.new( expand_path(self.path, other.path, true), other.query_string, other.fragment, other.parameters, ) end # Just the base path, without any query string, parameters or fragment. def base self.class.new(@path, nil, nil, nil) end # @option path [String] Append the string to this reference similar to `File.join`. # @option parameters [Hash] Append the parameters to this reference. # @option fragment [String] Set the fragment to this value. def with(path: nil, parameters: nil, fragment: @fragment) if @parameters if parameters parameters = @parameters.merge(parameters) else parameters = @parameters end end if path path = expand_path(@path, path, false) else path = @path end self.class.new(path, @query_string, fragment, parameters) end # The arguments to this function are legacy, prefer to use `with`. def dup(path = nil, parameters = nil, merge_parameters = true) if merge_parameters with(path: path, parameters: parameters) else self.base.with(path: path, parameters: parameters) end end private def split(path) if path.empty? [path] else path.split('/', -1) end end # @param pop [Boolean] whether to remove the last path component of the base path, to conform to URI merging behaviour, as defined by RFC2396. def expand_path(base, relative, pop = true) if relative.start_with? '/' return relative else path = split(base) # RFC2396 Section 5.2: # 6) a) All but the last segment of the base URI's path component is # copied to the buffer. In other words, any characters after the # last (right-most) slash character, if any, are excluded. path.pop if pop or path.last == '' parts = split(relative) parts.each do |part| if part == '..' path.pop elsif part == '.' # Do nothing. else path << part end end return path.join('/') end end end end end ruby-protocol-http-0.22.5/lib/protocol/http/request.rb000066400000000000000000000046211407373741100230120ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'body/buffered' require_relative 'body/reader' module Protocol module HTTP class Request prepend Body::Reader def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil) @scheme = scheme @authority = authority @method = method @path = path @version = version @headers = headers @body = body @protocol = protocol end attr_accessor :scheme attr_accessor :authority attr_accessor :method attr_accessor :path attr_accessor :version attr_accessor :headers attr_accessor :body attr_accessor :protocol # Send the request to the given connection. def call(connection) connection.call(self) end def head? @method == Methods::HEAD end def connect? @method == Methods::CONNECT end def self.[](method, path, headers, body) body = Body::Buffered.wrap(body) headers = ::Protocol::HTTP::Headers[headers] self.new(nil, nil, method, path, nil, headers, body) end def idempotent? @method != Methods::POST && (@body.nil? || @body.empty?) end def to_s "#{@scheme}://#{@authority}: #{@method} #{@path} #{@version}" end end end end ruby-protocol-http-0.22.5/lib/protocol/http/response.rb000066400000000000000000000052541407373741100231630ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'body/buffered' require_relative 'body/reader' module Protocol module HTTP class Response prepend Body::Reader def initialize(version = nil, status = 200, headers = Headers.new, body = nil, protocol = nil) @version = version @status = status @headers = headers @body = body @protocol = protocol end attr_accessor :version attr_accessor :status attr_accessor :headers attr_accessor :body attr_accessor :protocol def hijack? false end def continue? @status == 100 end def success? @status and @status >= 200 && @status < 300 end def partial? @status == 206 end def redirection? @status and @status >= 300 && @status < 400 end def not_modified? @status == 304 end def preserve_method? @status == 307 || @status == 308 end def failure? @status and @status >= 400 && @status < 600 end def bad_request? @status == 400 end def server_failure? @status == 500 end def self.[](status, headers = nil, body = nil, protocol = nil) body = Body::Buffered.wrap(body) headers = ::Protocol::HTTP::Headers[headers] self.new(nil, status, headers, body, protocol) end def self.for_exception(exception) Response[500, Headers['content-type' => 'text/plain'], ["#{exception.class}: #{exception.message}"]] end def to_s "#{@status} #{@version}" end def to_ary return @status, @headers, @body end end end end ruby-protocol-http-0.22.5/lib/protocol/http/url.rb000066400000000000000000000073421407373741100221270ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP module URL # Escapes a generic string, using percent encoding. def self.escape(string, encoding = string.encoding) string.b.gsub(/([^a-zA-Z0-9_.\-]+)/) do |m| '%' + m.unpack('H2' * m.bytesize).join('%').upcase end.force_encoding(encoding) end def self.unescape(string, encoding = string.encoding) string.b.gsub(/%(\h\h)/) do |hex| Integer(hex, 16).chr end.force_encoding(encoding) end # According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar. NON_PCHAR = /([^a-zA-Z0-9_\-\.~!$&'()*+,;=:@\/]+)/.freeze # Escapes a path def self.escape_path(path) encoding = path.encoding path.b.gsub(NON_PCHAR) do |m| '%' + m.unpack('H2' * m.bytesize).join('%').upcase end.force_encoding(encoding) end # Encodes a hash or array into a query string def self.encode(value, prefix = nil) case value when Array return value.map {|v| self.encode(v, "#{prefix}[]") }.join("&") when Hash return value.map {|k, v| self.encode(v, prefix ? "#{prefix}[#{escape(k.to_s)}]" : escape(k.to_s)) }.reject(&:empty?).join('&') when nil return prefix else raise ArgumentError, "value must be a Hash" if prefix.nil? return "#{prefix}=#{escape(value.to_s)}" end end def self.scan(string) # TODO Ruby 2.6 doesn't require `.each` string.split('&').each do |assignment| key, value = assignment.split('=', 2) yield unescape(key), unescape(value) end end def self.split(name) name.scan(/([^\[]+)|(?:\[(.*?)\])/).flatten!.compact! end def self.assign(keys, value, parent) top, *middle = keys middle.each_with_index do |key, index| if key.nil? or key.empty? parent = (parent[top] ||= Array.new) top = parent.size if nested = middle[index+1] and last = parent.last top -= 1 unless last.include?(nested) end else parent = (parent[top] ||= Hash.new) top = key end end parent[top] = value end # TODO use native C extension from `Trenni::Reference`. def self.decode(string, maximum = 8, symbolize_keys: false) parameters = {} self.scan(string) do |name, value| keys = self.split(name) if keys.size > maximum raise ArgumentError, "Key length exceeded limit!" end if symbolize_keys keys.collect!{|key| key.empty? ? nil : key.to_sym} end self.assign(keys, value, parameters) end return parameters end end end end ruby-protocol-http-0.22.5/lib/protocol/http/version.rb000066400000000000000000000023071407373741100230060ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Protocol module HTTP VERSION = "0.22.5" end end ruby-protocol-http-0.22.5/protocol-http.gemspec000066400000000000000000000011121407373741100215620ustar00rootroot00000000000000 require_relative "lib/protocol/http/version" Gem::Specification.new do |spec| spec.name = "protocol-http" spec.version = Protocol::HTTP::VERSION spec.summary = "Provides abstractions to handle HTTP protocols." spec.authors = ["Samuel Williams"] spec.license = "MIT" spec.homepage = "https://github.com/socketry/protocol-http" spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" spec.add_development_dependency "rspec" end ruby-protocol-http-0.22.5/spec/000077500000000000000000000000001407373741100163365ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/000077500000000000000000000000001407373741100201775ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/000077500000000000000000000000001407373741100211565ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/body/000077500000000000000000000000001407373741100221135ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/body/buffered_spec.rb000066400000000000000000000071611407373741100252410ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/buffered' RSpec.describe Protocol::HTTP::Body::Buffered do include_context RSpec::Memory let(:body) {["Hello", "World"]} subject! {described_class.wrap(body)} describe ".wrap" do context "when body is a Body::Readable" do let(:body) {Protocol::HTTP::Body::Readable.new} it "returns the body" do expect(subject).to be == body end end context "when body is an Array" do let(:body) {["Hello", "World"]} it "returns instance initialized with the array" do expect(subject).to be_an_instance_of(described_class) end end context "when body responds to #each" do let(:body) {["Hello", "World"].each} it "buffers the content into an array before initializing" do expect(subject).to be_an_instance_of(described_class) allow(body).to receive(:each).and_raise(StopIteration) expect(subject.read).to be == "Hello" expect(subject.read).to be == "World" end end context "when body is a String" do let(:body) {"Hello World"} it "returns instance initialized with the array" do expect(subject).to be_an_instance_of(described_class) end end end describe "#length" do it "returns sum of chunks' bytesize" do expect(subject.length).to be == 10 end end describe "#empty?" do it "returns false when there are chunks left" do expect(subject.empty?).to be == false subject.read expect(subject.empty?).to be == false end it "returns true when there are no chunks left" do subject.read subject.read expect(subject.empty?).to be == true end it "returns false when rewinded" do subject.read subject.read subject.rewind expect(subject.empty?).to be == false end end describe '#ready?' do it {is_expected.to be_ready} end describe "#finish" do it "returns self" do expect(subject.finish).to be == subject end end describe "#read" do it "retrieves chunks of content" do expect(subject.read).to be == "Hello" expect(subject.read).to be == "World" expect(subject.read).to be == nil end context "with large content" do let(:content) {Array.new(5) {|i| "#{i}" * (1*1024*1024)}} it "allocates expected amount of memory" do expect do subject.read until subject.empty? end.to limit_allocations(size: 0) end end end describe "#rewind" do it "positions the cursor to the beginning" do expect(subject.read).to be == "Hello" subject.rewind expect(subject.read).to be == "Hello" end end end ruby-protocol-http-0.22.5/spec/protocol/http/body/completable_spec.rb000066400000000000000000000030021407373741100257340ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/completable' RSpec.describe Protocol::HTTP::Body::Completable do let(:source) {Protocol::HTTP::Body::Buffered.new} let(:callback) {double} subject {described_class.new(source, callback)} it "can trigger callback when finished reading" do expect(callback).to receive(:call) expect(subject.read).to be_nil subject.close end end ruby-protocol-http-0.22.5/spec/protocol/http/body/deflate_spec.rb000066400000000000000000000041331407373741100250570ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # Copyright, 2012, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/buffered' require 'protocol/http/body/deflate' require 'protocol/http/body/inflate' RSpec.describe Protocol::HTTP::Body::Deflate do let(:body) {Protocol::HTTP::Body::Buffered.new} let(:compressed_body) {Protocol::HTTP::Body::Deflate.for(body)} let(:decompressed_body) {Protocol::HTTP::Body::Inflate.for(compressed_body)} it "should round-trip data" do body.write("Hello World!") body.close expect(decompressed_body.join).to be == "Hello World!" end it "should read chunks" do body.write("Hello ") body.write("World!") body.close expect(body.read).to be == "Hello " expect(body.read).to be == "World!" expect(body.read).to be == nil end it "should round-trip chunks" do body.write("Hello ") body.write("World!") body.close expect(decompressed_body.read).to be == "Hello " expect(decompressed_body.read).to be == "World!" expect(decompressed_body.read).to be == nil end end ruby-protocol-http-0.22.5/spec/protocol/http/body/digestable_spec.rb000066400000000000000000000036321407373741100255610ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/digestable' RSpec.describe Protocol::HTTP::Body::Digestable do let(:source) {Protocol::HTTP::Body::Buffered.new} subject {described_class.new(source)} describe '#digest' do before do source.write "Hello" source.write "World" end it "can compute digest" do 2.times {subject.read} expect(subject.digest).to be == "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4" end it "can recompute digest" do expect(subject.read).to be == "Hello" expect(subject.digest).to be == "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969" expect(subject.read).to be == "World" expect(subject.digest).to be == "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4" end end end ruby-protocol-http-0.22.5/spec/protocol/http/body/file_spec.rb000066400000000000000000000036051407373741100243750ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/file' RSpec.describe Protocol::HTTP::Body::File do let(:path) {File.expand_path('file_spec.txt', __dir__)} context 'entire file' do subject {described_class.open(path)} it "should read entire file" do expect(subject.read).to be == "Hello World" end it "should use binary encoding" do expect(::File).to receive(:open).with(path, ::File::RDONLY | ::File::BINARY).and_call_original chunk = subject.read expect(chunk.encoding).to be == Encoding::BINARY end describe '#ready?' do it {is_expected.to be_ready} end end context 'partial file' do subject {described_class.open(path, 2...4)} it "should read specified range" do expect(subject.read).to be == "ll" end end end ruby-protocol-http-0.22.5/spec/protocol/http/body/file_spec.txt000066400000000000000000000000131407373741100245770ustar00rootroot00000000000000Hello Worldruby-protocol-http-0.22.5/spec/protocol/http/body/head_spec.rb000066400000000000000000000037741407373741100243660ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/head' RSpec.describe Protocol::HTTP::Body::Head do context "with zero length" do subject(:body) {described_class.new(0)} it {is_expected.to be_empty} describe '#join' do subject {body.join} it {is_expected.to be_nil} end end context "with non-zero length" do subject(:body) {described_class.new(1)} it {is_expected.to be_empty} describe '#read' do subject {body.read} it {is_expected.to be_nil} end describe '#join' do subject {body.join} it {is_expected.to be_nil} end end describe '.for' do let(:body) {double} subject {described_class.for(body)} it "captures length and closes existing body" do expect(body).to receive(:length).and_return(1) expect(body).to receive(:close) expect(subject).to have_attributes(length: 1) subject.close end end end ruby-protocol-http-0.22.5/spec/protocol/http/body/rewindable_spec.rb000066400000000000000000000056001407373741100255670ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/body/rewindable' RSpec.describe Protocol::HTTP::Body::Rewindable do let(:source) {Protocol::HTTP::Body::Buffered.new} subject {described_class.new(source)} it "doesn't get affected by clearing chunks" do source.write("Hello World!") 2.times do chunk = subject.read expect(chunk).to be == "Hello World!" chunk.clear subject.rewind end end it "can write and read data" do 3.times do |i| source.write("Hello World #{i}") expect(subject.read).to be == "Hello World #{i}" end end it "can write and read data multiple times" do 3.times do |i| source.write("Hello World #{i}") end 3.times do subject.rewind expect(subject.read).to be == "Hello World 0" end end it "can buffer data in order" do 3.times do |i| source.write("Hello World #{i}") end 2.times do subject.rewind 3.times do |i| expect(subject.read).to be == "Hello World #{i}" end end end describe '#empty?' do it {is_expected.to be_empty} context "with unread chunk" do before {source.write("Hello World")} it {is_expected.to_not be_empty} end context "with read chunk" do before do source.write("Hello World") expect(subject.read).to be == "Hello World" end it {is_expected.to be_empty} end context "with rewound chunk" do before do source.write("Hello World") expect(subject.read).to be == "Hello World" subject.rewind end it {is_expected.to_not be_empty} end context "with rewound chunk" do before do source.write("Hello World") expect(subject.read).to be == "Hello World" subject.rewind expect(subject.read).to be == "Hello World" end it {is_expected.to be_empty} end end end ruby-protocol-http-0.22.5/spec/protocol/http/content_encoding_spec.rb000066400000000000000000000063701407373741100260430ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/accept_encoding' require 'protocol/http/content_encoding' RSpec.describe Protocol::HTTP::ContentEncoding do context 'with complete text/plain response' do subject do described_class.new(Protocol::HTTP::Middleware::HelloWorld) end it "can request resource with compression" do compressor = Protocol::HTTP::AcceptEncoding.new(subject) response = compressor.get("/index", {'accept-encoding' => 'gzip'}) expect(response).to be_success expect(response.headers['vary']).to include('accept-encoding') expect(response.body).to be_kind_of Protocol::HTTP::Body::Inflate expect(response.read).to be == "Hello World!" end it "can request resource without compression" do response = subject.get("/index") expect(response).to be_success expect(response.headers).to_not include('content-encoding') expect(response.headers['vary']).to include('accept-encoding') expect(response.read).to be == "Hello World!" end end context 'with partial response' do let(:app) do app = ->(request){ Protocol::HTTP::Response[206, Protocol::HTTP::Headers['content-type' => 'text/plain'], ["Hello World!"]] } end subject do described_class.new(app) end it "can request resource with compression" do response = subject.get("/index", {'accept-encoding' => 'gzip'}) expect(response).to be_success expect(response.headers).to_not include('content-encoding') expect(response.read).to be == "Hello World!" end end context 'with existing content encoding' do let(:app) do app = ->(request){ Protocol::HTTP::Response[200, Protocol::HTTP::Headers['content-type' => 'text/plain', 'content-encoding' => 'identity'], ["Hello World!"]] } end subject do described_class.new(app) end it "does not compress response" do response = subject.get("/index", {'accept-encoding' => 'gzip'}) expect(response).to be_success expect(response.headers).to include('content-encoding') expect(response.headers['content-encoding']).to be == ['identity'] expect(response.read).to be == "Hello World!" end end end ruby-protocol-http-0.22.5/spec/protocol/http/header/000077500000000000000000000000001407373741100224065ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/header/authorization_spec.rb000066400000000000000000000032201407373741100266420ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/header/authorization' require 'protocol/http/headers' RSpec.describe Protocol::HTTP::Header::Authorization do context 'with basic username/password' do subject {described_class.basic("samuel", "password")} it "should generate correct authorization header" do expect(subject).to be == "Basic c2FtdWVsOnBhc3N3b3Jk" end describe '#credentials' do it "can split credentials" do expect(subject.credentials).to be == ["Basic", "c2FtdWVsOnBhc3N3b3Jk"] end end end end ruby-protocol-http-0.22.5/spec/protocol/http/header/cache_control_spec.rb000066400000000000000000000031761407373741100265570ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/header/cache_control' RSpec.describe Protocol::HTTP::Header::CacheControl do subject {described_class.new(description)} context "max-age=60, public" do it {is_expected.to have_attributes(public?: true)} it {is_expected.to have_attributes(private?: false)} it {is_expected.to have_attributes(max_age: 60)} end context "no-cache, no-store" do it {is_expected.to have_attributes(no_cache?: true)} it {is_expected.to have_attributes(no_store?: true)} end end ruby-protocol-http-0.22.5/spec/protocol/http/header/cookie_spec.rb000066400000000000000000000031471407373741100252230ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2021, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/header/cookie' RSpec.describe Protocol::HTTP::Header::Cookie do subject {described_class.new(description)} let(:cookies) {subject.to_h} context "session=123; secure" do it "has named cookie" do expect(cookies).to include('session') session = cookies['session'] expect(session).to have_attributes(name: 'session') expect(session).to have_attributes(value: '123') expect(session.directives).to include('secure') end end end ruby-protocol-http-0.22.5/spec/protocol/http/header/etags_spec.rb000066400000000000000000000030141407373741100250460ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/header/etags' RSpec.describe Protocol::HTTP::Header::ETags do subject {described_class.new(description)} context "*" do it {is_expected.to be_wildcard} it {is_expected.to be_match('whatever')} end context "abcd" do it {is_expected.to_not be_wildcard} it {is_expected.to_not be_match('whatever')} it {is_expected.to be_match('abcd')} end end ruby-protocol-http-0.22.5/spec/protocol/http/header/vary_spec.rb000066400000000000000000000030621407373741100247270ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/header/cache_control' RSpec.describe Protocol::HTTP::Header::CacheControl do subject {described_class.new(description)} context "accept-language" do it {is_expected.to include('accept-language')} it {is_expected.to_not include('user-agent')} end context "Accept-Language" do it {is_expected.to include('accept-language')} it {is_expected.to_not include('Accept-Language')} end end ruby-protocol-http-0.22.5/spec/protocol/http/headers/000077500000000000000000000000001407373741100225715ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/headers/connection_spec.rb000066400000000000000000000037661407373741100263030ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/headers' require 'protocol/http/cookie' RSpec.describe Protocol::HTTP::Header::Connection do context "connection: close" do subject {described_class.new("close")} it "should indiciate connection will be closed" do expect(subject).to be_close end it "should indiciate connection will not be keep-alive" do expect(subject).to_not be_keep_alive end end context "connection: keep-alive" do subject {described_class.new("keep-alive")} it "should indiciate connection will not be closed" do expect(subject).to_not be_close end it "should indiciate connection is not keep-alive" do expect(subject).to be_keep_alive end end context "connection: upgrade" do subject {described_class.new("upgrade")} it "should indiciate connection can be upgraded" do expect(subject).to be_upgrade end end end ruby-protocol-http-0.22.5/spec/protocol/http/headers/merged_spec.rb000066400000000000000000000036231407373741100253770ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/headers' RSpec.describe Protocol::HTTP::Headers::Merged do let(:fields) do [ ['Content-Type', 'text/html'], ['Set-Cookie', 'hello=world'], ['Accept', '*/*'], ['content-length', 10], ] end subject{described_class.new(fields)} let(:headers) {Protocol::HTTP::Headers.new(subject)} describe '#each' do it 'should yield keys as lower case' do subject.each do |key, value| expect(key).to be == key.downcase end end it 'should yield values as strings' do subject.each do |key, value| expect(value).to be_kind_of String end end end describe '#<<' do it "can append fields" do subject << [["Accept", "image/jpeg"]] expect(headers['accept']).to be == ['*/*', 'image/jpeg'] end end end ruby-protocol-http-0.22.5/spec/protocol/http/headers_spec.rb000066400000000000000000000133441407373741100241350ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/headers' require 'protocol/http/cookie' RSpec.describe Protocol::HTTP::Headers do let(:fields) do [ ['Content-Type', 'text/html'], ['Set-Cookie', 'hello=world'], ['Accept', '*/*'], ['Set-Cookie', 'foo=bar'], ['Connection', 'Keep-Alive'] ] end before(:each) do fields&.each do |name, value| subject.add(name, value) end end describe '#freeze' do it "can't modify frozen headers" do subject.freeze expect(subject.fields).to be == fields expect(subject.fields).to be_frozen expect(subject.to_h).to be_frozen end end describe '#dup' do it "should not modify source object" do headers = subject.dup headers['field'] = 'value' expect(subject).to_not include('field') end end describe '#empty?' do it "shouldn't be empty" do expect(subject).to_not be_empty end end describe '#fields' do it 'should add fields in order' do expect(subject.fields).to be == fields end it 'can enumerate fields' do subject.each.with_index do |field, index| expect(field).to be == fields[index] end end end describe '#to_h' do it 'should generate array values for duplicate keys' do expect(subject.to_h['set-cookie']).to be == ['hello=world', 'foo=bar'] end end describe '#[]' do it 'can lookup fields' do expect(subject['content-type']).to be == 'text/html' end end describe '#[]=' do it 'can add field' do subject['Content-Length'] = 1 expect(subject.fields.last).to be == ['Content-Length', 1] expect(subject['content-length']).to be == 1 end it 'can add field with indexed hash' do expect(subject.to_h).to_not be_empty subject['Content-Length'] = 1 expect(subject['content-length']).to be == 1 end end describe '#add' do it 'can add field' do subject.add('Content-Length', 1) expect(subject.fields.last).to be == ['Content-Length', 1] expect(subject['content-length']).to be == 1 end end describe '#set' do it 'can replace an existing field' do subject.add('accept-encoding', 'gzip,deflate') subject.set('accept-encoding', 'gzip') expect(subject['accept-encoding']).to be == ['gzip'] end end describe '#extract' do it "can extract key's that don't exist" do expect(subject.extract('foo')).to be_empty end it 'can extract single key' do expect(subject.extract('content-type')).to be == [['Content-Type', 'text/html']] end end describe '#==' do it "can compare with array" do expect(subject).to be == fields end it "can compare with itself" do expect(subject).to be == subject end it "can compare with hash" do expect(subject).to_not be == {} end end describe '#delete' do it 'can delete case insensitive fields' do expect(subject.delete('content-type')).to be == 'text/html' expect(subject.fields).to be == fields[1..-1] end it 'can delete non-existant fields' do expect(subject.delete('transfer-encoding')).to be_nil end end describe '#merge' do it "can merge content-length" do subject.merge!('content-length' => 2) expect(subject['content-length']).to be == 2 end end describe '#trailer!' do it "can add trailer" do subject.add('trailer', 'etag') trailer = subject.trailer! subject.add('etag', 'abcd') expect(trailer.to_h).to be == {'etag' => 'abcd'} end end describe '#trailer' do it "can enumerate trailer" do subject.add('trailer', 'etag') subject.trailer! subject.add('etag', 'abcd') expect(subject.trailer.to_h).to be == {'etag' => 'abcd'} end end describe '#flatten!' do it "can flatten trailer" do subject.add('trailer', 'etag') trailer = subject.trailer! subject.add('etag', 'abcd') subject.flatten! expect(subject).to_not include('trailer') expect(subject).to include('etag') end end describe '#flatten' do it "can flatten trailer" do subject.add('trailer', 'etag') trailer = subject.trailer! subject.add('etag', 'abcd') copy = subject.flatten expect(subject).to include('trailer') expect(subject).to include('etag') expect(copy).to_not include('trailer') expect(copy).to include('etag') end end describe 'set-cookie' do it "can extract parsed cookies" do expect(subject['set-cookie']).to be_kind_of(Protocol::HTTP::Header::Cookie) end end describe 'connection' do it "can extract connection options" do expect(subject['connection']).to be_kind_of(Protocol::HTTP::Header::Connection) end it "should normalize to lower case" do expect(subject['connection']).to be == ['keep-alive'] end end end ruby-protocol-http-0.22.5/spec/protocol/http/methods_spec.rb000066400000000000000000000051051407373741100241610ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/methods' RSpec.describe Protocol::HTTP::Methods do it "defines several methods" do expect(described_class.constants).to_not be_empty end shared_examples_for Protocol::HTTP::Methods do |name| it "defines #{name} method" do expect(described_class.constants).to include(name.to_sym) end it "has correct value" do expect(described_class.const_get(name)).to be == name.to_s end it "is a valid method" do expect(described_class).to be_valid(name) end end it_behaves_like Protocol::HTTP::Methods, "GET" it_behaves_like Protocol::HTTP::Methods, "POST" it_behaves_like Protocol::HTTP::Methods, "PUT" it_behaves_like Protocol::HTTP::Methods, "PATCH" it_behaves_like Protocol::HTTP::Methods, "DELETE" it_behaves_like Protocol::HTTP::Methods, "HEAD" it_behaves_like Protocol::HTTP::Methods, "OPTIONS" it_behaves_like Protocol::HTTP::Methods, "LINK" it_behaves_like Protocol::HTTP::Methods, "UNLINK" it_behaves_like Protocol::HTTP::Methods, "TRACE" it_behaves_like Protocol::HTTP::Methods, "CONNECT" it "defines exactly 11 methods" do expect(described_class.constants.length).to be == 11 end describe '.valid?' do subject {described_class} describe "FOOBAR" do it {is_expected.to_not be_valid(description)} end describe "GETEMALL" do it {is_expected.to_not be_valid(description)} end describe "Accept:" do it {is_expected.to_not be_valid(description)} end end end ruby-protocol-http-0.22.5/spec/protocol/http/middleware/000077500000000000000000000000001407373741100232735ustar00rootroot00000000000000ruby-protocol-http-0.22.5/spec/protocol/http/middleware/builder_spec.rb000066400000000000000000000034361407373741100262660ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/middleware' require 'protocol/http/middleware/builder' RSpec.describe Protocol::HTTP::Middleware::Builder do it "can make an app" do app = Protocol::HTTP::Middleware.build do run Protocol::HTTP::Middleware::HelloWorld end expect(app).to be Protocol::HTTP::Middleware::HelloWorld end it "defaults to not found" do app = Protocol::HTTP::Middleware.build do end expect(app).to be Protocol::HTTP::Middleware::NotFound end it "can instantiate middleware" do app = Protocol::HTTP::Middleware.build do use Protocol::HTTP::Middleware end expect(app).to be_kind_of Protocol::HTTP::Middleware end end ruby-protocol-http-0.22.5/spec/protocol/http/middleware_spec.rb000066400000000000000000000032471407373741100246400ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2021, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/middleware' RSpec.describe Protocol::HTTP::Middleware do it "can invoke delegate" do request = :request delegate = instance_double(described_class) expect(delegate).to receive(:call) do |request| expect(request).to be request end middleware = described_class.new(delegate) middleware.call(request) end it "can close delegate" do delegate = instance_double(described_class) expect(delegate).to receive(:close) middleware = described_class.new(delegate) middleware.close end end ruby-protocol-http-0.22.5/spec/protocol/http/reference_spec.rb000066400000000000000000000122211407373741100244510ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2012, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/reference' RSpec.describe Protocol::HTTP::Reference do describe '#+' do let(:absolute) {described_class['/foo/bar']} let(:relative) {described_class['foo/bar']} let(:up) {described_class['../baz']} it 'can add a relative path' do expect(subject + relative).to be == absolute end it 'can add an absolute path' do expect(subject + absolute).to be == absolute end it 'can add an absolute path' do expect(relative + absolute).to be == absolute end it 'can remove relative parts' do expect(absolute + up).to be == described_class['/baz'] end end describe '#freeze' do it "can freeze reference" do expect(subject.freeze).to be subject expect(subject).to be_frozen end end describe '#dup' do let(:parameters) {{x: 10}} let(:path) {"foo/bar.html"} it "can add parameters" do copy = subject.dup(nil, parameters) expect(copy.parameters).to be == parameters end it "can update path" do copy = subject.dup(path) expect(copy.path).to be == "/foo/bar.html" end it "can append path components" do copy = subject.dup("foo/").dup("bar/") expect(copy.path).to be == "/foo/bar/" end it "can append empty path components" do copy = subject.dup("") expect(copy.path).to be == subject.path end it "can delete last path component" do copy = subject.dup("hello").dup("") expect(copy.path).to be == "/hello/" end it "can merge parameters" do subject.parameters = {y: 20} copy = subject.dup(nil, parameters, true) expect(copy.parameters).to be == {x: 10, y: 20} end it "can replace parameters" do subject.parameters = {y: 20} copy = subject.dup(nil, parameters, false) expect(copy.parameters).to be == parameters end it "can nest path with absolute base" do copy = subject.with(path: "foo").with(path: "bar") expect(copy.path).to be == "/foo/bar" end it "can nest path with relative base" do copy = subject.with(path: "foo").with(path: "bar") expect(copy.path).to be == "/foo/bar" end end context 'empty query string' do subject {described_class.new('/', '', nil, {})} it 'it should not append query string' do expect(subject.to_s).to_not include('?') end it 'can add a relative path' do result = subject + described_class['foo/bar'] expect(result.to_s).to be == '/foo/bar' end end context 'empty fragment' do subject {described_class.new('/', nil, '', nil)} it 'it should not append query string' do expect(subject.to_s).to_not include('#') end end context Protocol::HTTP::Reference.parse('path with spaces/image.jpg') do it "encodes whitespace" do expect(subject.to_s).to be == "path%20with%20spaces/image.jpg" end end context Protocol::HTTP::Reference.parse('path', array: [1, 2, 3]) do it "encodes array" do expect(subject.to_s).to be == "path?array[]=1&array[]=2&array[]=3" end end context Protocol::HTTP::Reference.parse('path_with_underscores/image.jpg') do it "doesn't touch underscores" do expect(subject.to_s).to be == "path_with_underscores/image.jpg" end end context Protocol::HTTP::Reference.parse('index', 'my name' => 'Bob Dole') do it "encodes query" do expect(subject.to_s).to be == "index?my%20name=Bob%20Dole" end end context Protocol::HTTP::Reference.parse('index#All Your Base') do it "encodes fragment" do expect(subject.to_s).to be == "index\#All%20Your%20Base" end end context Protocol::HTTP::Reference.parse('I/❤️/UNICODE', face: '😀') do it "encodes unicode" do expect(subject.to_s).to be == "I/%E2%9D%A4%EF%B8%8F/UNICODE?face=%F0%9F%98%80" end end context Protocol::HTTP::Reference.parse("foo?bar=10&baz=20", yes: 'no') do it "can use existing query parameters" do expect(subject.to_s).to be == "foo?bar=10&baz=20&yes=no" end end context Protocol::HTTP::Reference.parse('foo#frag') do it "can use existing fragment" do expect(subject.fragment).to be == "frag" expect(subject.to_s).to be == 'foo#frag' end end end ruby-protocol-http-0.22.5/spec/protocol/http/request_spec.rb000066400000000000000000000035151407373741100242110ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/request' RSpec.describe Protocol::HTTP::Request do let(:headers) {Protocol::HTTP::Headers.new} let(:body) {nil} context "simple GET request" do subject {described_class.new("http", "localhost", "GET", "/index.html", "HTTP/1.0", headers, body)} it {is_expected.to have_attributes( scheme: "http", authority: "localhost", method: "GET", path: "/index.html", version: "HTTP/1.0", headers: headers, body: body, protocol: nil )} it {is_expected.to_not be_head} it {is_expected.to_not be_connect} it {is_expected.to be_idempotent} it {is_expected.to have_attributes( to_s: "http://localhost: GET /index.html HTTP/1.0" )} end end ruby-protocol-http-0.22.5/spec/protocol/http/response_spec.rb000066400000000000000000000037551407373741100243650ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/request' RSpec.describe Protocol::HTTP::Response do let(:headers) {Protocol::HTTP::Headers.new} let(:body) {nil} context "GET response" do subject {described_class.new("HTTP/1.0", 200, headers, body)} it {is_expected.to have_attributes( version: "HTTP/1.0", status: 200, headers: headers, body: body, protocol: nil )} it {is_expected.to_not be_hijack} it {is_expected.to_not be_continue} it {is_expected.to be_success} it {is_expected.to have_attributes( to_ary: [200, headers, body] )} it {is_expected.to have_attributes( to_s: "200 HTTP/1.0" )} end context "unmodified cached response" do subject {described_class.new("HTTP/1.1", 304, headers, body)} it {is_expected.to_not be_success} it {is_expected.to be_redirection} it {is_expected.to be_not_modified} end end ruby-protocol-http-0.22.5/spec/protocol/http/url_spec.rb000066400000000000000000000053401407373741100233210ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http/url' RSpec.shared_examples_for "valid parameters" do |parameters, query_string = nil| let(:encoded) {Protocol::HTTP::URL.encode(parameters)} if query_string it "can encode #{parameters.inspect}" do expect(encoded).to be == query_string end end let(:decoded) {Protocol::HTTP::URL.decode(encoded)} it "can round-trip #{parameters.inspect}" do expect(decoded).to be == parameters end end RSpec.describe Protocol::HTTP::URL do it_behaves_like "valid parameters", {'foo' => 'bar'}, "foo=bar" it_behaves_like "valid parameters", {'foo' => ["1", "2", "3"]}, "foo[]=1&foo[]=2&foo[]=3" it_behaves_like "valid parameters", {'foo' => {'bar' => 'baz'}}, "foo[bar]=baz" it_behaves_like "valid parameters", {'foo' => [{'bar' => 'baz'}]}, "foo[][bar]=baz" it_behaves_like "valid parameters", {'foo' => [{'bar' => 'baz'}, {'bar' => 'bob'}]} let(:encoded) {Protocol::HTTP::URL.encode(parameters)} context "basic parameters" do let(:parameters) {{x: "10", y: "20"}} let(:decoded) {Protocol::HTTP::URL.decode(encoded, symbolize_keys: true)} it "can symbolize keys" do expect(decoded).to be == parameters end end context "nested parameters" do let(:parameters) {{things: [{x: "10"}, {x: "20"}]}} let(:decoded) {Protocol::HTTP::URL.decode(encoded, symbolize_keys: true)} it "can symbolize keys" do expect(decoded).to be == parameters end end describe '.decode' do it "fails on deeply nested parameters" do expect do Protocol::HTTP::URL.decode("a[b][c][d][e][f][g][h][i]=10") end.to raise_error(/Key length exceeded/) end end end ruby-protocol-http-0.22.5/spec/protocol/http_spec.rb000066400000000000000000000024351407373741100225210ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'protocol/http' RSpec.describe Protocol::HTTP do it "has a version number" do expect(Protocol::HTTP::VERSION).not_to be nil end end ruby-protocol-http-0.22.5/spec/spec_helper.rb000066400000000000000000000032521407373741100211560ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'async/rspec' require 'covered/rspec' RSpec.shared_context 'docstring as description' do let(:description) {self.class.metadata.fetch(:description_args).first} end RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.include_context 'docstring as description' config.expect_with :rspec do |c| c.syntax = :expect end end