pax_global_header00006660000000000000000000000064143035625110014512gustar00rootroot0000000000000052 comment=70dace58f7adf11109dc32e998e59098937eb858 ruby-protocol-http-0.23.12/000077500000000000000000000000001430356251100154545ustar00rootroot00000000000000ruby-protocol-http-0.23.12/.editorconfig000066400000000000000000000000641430356251100201310ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-protocol-http-0.23.12/.github/000077500000000000000000000000001430356251100170145ustar00rootroot00000000000000ruby-protocol-http-0.23.12/.github/workflows/000077500000000000000000000000001430356251100210515ustar00rootroot00000000000000ruby-protocol-http-0.23.12/.github/workflows/coverage.yaml000066400000000000000000000021141430356251100235260ustar00rootroot00000000000000name: Coverage on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm COVERAGE: PartialSummary jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu - macos ruby: - "3.1" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: bundle exec rspec - uses: actions/upload-artifact@v2 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db validate: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" bundler-cache: true - uses: actions/download-artifact@v3 - name: Validate coverage timeout-minutes: 5 run: bundle exec bake covered:validate --paths */.covered.db \; ruby-protocol-http-0.23.12/.github/workflows/documentation.yaml000066400000000000000000000014131430356251100246050ustar00rootroot00000000000000name: Documentation permissions: contents: write on: push: branches: - main permissions: contents: write env: CONSOLE_OUTPUT: XTerm BUNDLE_WITH: maintenance jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" bundler-cache: true - name: Installing packages run: sudo apt-get install wget - name: Prepare GitHub Pages run: bundle exec bake github:pages:prepare --directory docs - name: Generate documentation timeout-minutes: 5 run: bundle exec bake utopia:project:static --force no - name: Deploy GitHub Pages run: bundle exec bake github:pages:commit --directory docs ruby-protocol-http-0.23.12/.github/workflows/test-external.yaml000066400000000000000000000011241430356251100245320ustar00rootroot00000000000000name: Test External on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu - macos ruby: - "3.0" - "3.1" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test:external ruby-protocol-http-0.23.12/.github/workflows/test.yaml000066400000000000000000000016641430356251100227230ustar00rootroot00000000000000name: Test on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - "2.7" - "3.0" - "3.1" experimental: [false] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test ruby-protocol-http-0.23.12/.gitignore000066400000000000000000000002251430356251100174430ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /external/ # rspec failure tracking .rspec_status /gems.locked .covered.db ruby-protocol-http-0.23.12/.rspec000066400000000000000000000000671430356251100165740ustar00rootroot00000000000000--format documentation --warnings --require spec_helperruby-protocol-http-0.23.12/bake.rb000066400000000000000000000014551430356251100167100ustar00rootroot00000000000000# 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.23.12/benchmark/000077500000000000000000000000001430356251100174065ustar00rootroot00000000000000ruby-protocol-http-0.23.12/benchmark/string.rb000066400000000000000000000007641430356251100212500ustar00rootroot00000000000000 def generator 100000.times do |i| yield "foo #{i}" end end def consumer_without_clear buffer = String.new generator do |chunk| buffer << chunk end return nil end def consumer_with_clear buffer = String.new generator do |chunk| buffer << chunk chunk.clear end return nil end require 'benchmark' Benchmark.bm do |x| x.report("consumer_with_clear") do consumer_with_clear GC.start end x.report("consumer_without_clear") do consumer_without_clear GC.start end end ruby-protocol-http-0.23.12/config/000077500000000000000000000000001430356251100167215ustar00rootroot00000000000000ruby-protocol-http-0.23.12/config/external.yaml000066400000000000000000000011001430356251100214170ustar00rootroot00000000000000protocol-http1: url: https://github.com/socketry/protocol-http1.git command: bundle exec rspec protocol-http2: url: https://github.com/socketry/protocol-http2.git command: bundle exec rspec async-http: url: https://github.com/socketry/async-http.git command: bundle exec rspec protocol-websocket: url: https://github.com/socketry/protocol-websocket.git command: bundle exec sus async-websocket: url: https://github.com/socketry/async-websocket.git command: bundle exec sus falcon: url: https://github.com/socketry/falcon.git command: bundle exec rspec ruby-protocol-http-0.23.12/design.md000066400000000000000000000055601430356251100172550ustar00rootroot00000000000000# 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) @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. ## Connection Upgrade ### HTTP/1 ``` GET /path/to/websocket HTTP/1.1 connection: upgrade upgrade: websocket ``` Request.new(GET, ..., protocol = websocket) -> Response.new(101, ..., protocol = websocket) ``` 101 Switching Protocols upgrade: websocket ``` ### HTTP/2 ``` :method CONNECT :path /path/to/websocket :protocol websocket ``` Request.new(CONNECT, ..., protocol = websocket) -> Response.new(200, ..., protocol = websocket) ruby-protocol-http-0.23.12/gems.rb000066400000000000000000000005311430356251100167330ustar00rootroot00000000000000# 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-gem" gem "utopia-project", "~> 0.18" end group :test do gem "bake-test" gem "bake-test-external" gem 'async-io' gem 'async-rspec' end ruby-protocol-http-0.23.12/guides/000077500000000000000000000000001430356251100167345ustar00rootroot00000000000000ruby-protocol-http-0.23.12/guides/design-overview/000077500000000000000000000000001430356251100220515ustar00rootroot00000000000000ruby-protocol-http-0.23.12/guides/design-overview/README.md000066400000000000000000000116201430356251100233300ustar00rootroot00000000000000# Design Overview The interfaces provided by {ruby Protocol::HTTP} underpin all downstream implementations. Therefore, we provide some justification for the design choices. ## Request/Response Model The main model we support is the request/response model. A client sends a request to a server which return response. The protocol is responsible for serializing the request and response objects. ```mermaid sequenceDiagram participant CA as Application participant Client participant Server participant SA as Application CA->>+Client: Request Client->>+Server: Request Server->>+SA: Request SA->>+Server: Response Server->>+Client: Response Client->>+CA: Response ``` We provide an interface for request and response objects. This provides performance, predictability and robustness. This model has proven itself over several years, handling a variety of different use cases. ~~~ ruby class Request attr :verb attr :target attr :headers attr :body end class Response attr :status attr :headers attr :body end ~~~ One other advantage is that it's symmetrical between clients and servers with a clear mapping, i.e. the protocol is responsible for transiting requests from the client to the server, and responses from the server back to the client. This helps us separate and define request/response interfaces independently from protocol implementation. ### Client Design A request/response model implies that you create a request and receive a response back. This maps to a normal function call where the request is the argument and the response is the returned value. ~~~ ruby request = Request.new("GET", url) response = client.call(request) response.headers response.read ~~~ ## Stream Model An alternative model is the stream model. This model is more suitable for WebSockets and other persistent bi-directional channels. ```mermaid sequenceDiagram participant CA as Application participant Client participant Server participant SA as Application CA->>+Client: Stream Client->>+Server: Stream Server->>+SA: Stream ``` The interfaces for streaming can be implemented a bit differently, since a response is not returned but rather assigned to the stream, and the streaming occurs in the same execution context as the client or server handling the request. ~~~ ruby class Stream # Request details. attr :verb attr :target attr :headers attr :response # Write the response and start streaming the output body. def respond(status, headers) response.status = status response.headers = headers end # Request body. attr_accessor :input # Response body. attr_accessor :output # Write to the response body. def write(...) @output.write(...) end # Read from the request body. def read @input.read end end class Response def initialize(verb, target) @input = Body::Writable.new @output = Body::Writable.new end attr_accessor :status attr_accessor :headers # Prepare a stream for making a request. def request(verb, target, headers) # Create a request stream suitable for writing into the buffered response: Stream.new(verb, target, headers, self, @input, @output) end # Write to the request body. def write(...) @input.write(...) end # Read from the response body. def read @output.read end end ~~~ ### Client Design A stream model implies that you create a stream which contains both the request and response bodies. This maps to a normal function call where the argument is the stream and the returned value is ignored. ~~~ ruby response = Response.new stream = response.request("GET", url) client.call(stream) 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, headers, 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) stream.respond(200, headers) 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.23.12/guides/getting-started/000077500000000000000000000000001430356251100220415ustar00rootroot00000000000000ruby-protocol-http-0.23.12/guides/getting-started/README.md000066400000000000000000000043211430356251100233200ustar00rootroot00000000000000# Getting Started This guide explains how to use `protocol-http` for building abstract HTTP interfaces. ## Installation Add the gem to your project: ~~~ bash $ bundle add protocol-http ~~~ ## Core Concepts `protocol-http` has several core concepts: - A {ruby Protocol::HTTP::Request} instance which represents an abstract HTTP request. Specific versions of HTTP may subclass this to track additional state. - A {ruby Protocol::HTTP::Response} instance which represents an abstract HTTP response. Specific versions of HTTP may subclass this to track additional state. - A {ruby Protocol::HTTP::Middleware} interface for building HTTP applications. - A {ruby Protocol::HTTP::Headers} interface for storing HTTP headers with semantics based on documented specifications (RFCs, etc). - A set of {ruby Protocol::HTTP::Body} classes which handle the internal request and response bodies, including bi-directional streaming. ## Integration This gem does not provide any specific client or server implementation, rather it's used by several other gems. - [Protocol::HTTP1] & [Protocol::HTTP2] which provide client and server implementations. - [Async::HTTP] which provides connection pooling and concurrency. ## Usage ### Headers {ruby Protocol::HTTP::Headers} provides semantically meaningful interpretation of header values implements case-normalising keys. ``` ruby require 'protocol/http/headers' headers = Protocol::HTTP::Headers.new headers['Content-Type'] = "image/jpeg" headers['content-type'] # => "image/jpeg" ``` ### Reference {ruby Protocol::HTTP::Reference} is used to construct "hypertext references" which consist of a path and URL-encoded key/value pairs. ``` ruby require 'protocol/http/reference' reference = Protocol::HTTP::Reference.new("/search", q: 'kittens') reference.to_s # => "/search?q=kittens" ``` ### URL {ruby Protocol::HTTP::URL} is used to parse incoming URLs to extract the query string and other relevant details. ``` ruby require 'protocol/http/url' reference = Protocol::HTTP::Reference.parse("/search?q=kittens") parameters = Protocol::HTTP::URL.decode(reference.query_string) # => {"q"=>"kittens"} ``` This implemenation may be merged with {ruby Protocol::HTTP::Reference} or removed in the future. ruby-protocol-http-0.23.12/guides/links.yaml000066400000000000000000000000701430356251100207350ustar00rootroot00000000000000getting-started: order: 1 design-overview: order: 2 ruby-protocol-http-0.23.12/lib/000077500000000000000000000000001430356251100162225ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/000077500000000000000000000000001430356251100200635ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/http.rb000066400000000000000000000022541430356251100213720ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/000077500000000000000000000000001430356251100210425ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/http/accept_encoding.rb000066400000000000000000000042651430356251100245030ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/body/000077500000000000000000000000001430356251100217775ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/http/body/buffered.rb000066400000000000000000000050211430356251100241040ustar00rootroot00000000000000# 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 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) @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.23.12/lib/protocol/http/body/completable.rb000066400000000000000000000036201430356251100246140ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/body/deflate.rb000066400000000000000000000062311430356251100237320ustar00rootroot00000000000000# 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 stream? # We might want to revisit this design choice. # We could wrap the streaming body in a Deflate stream, but that would require an extra stream wrapper which we don't have right now. See also `Digestable#stream?`. false 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.23.12/lib/protocol/http/body/digestable.rb000066400000000000000000000037421430356251100244350ustar00rootroot00000000000000# 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 stream? false 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.23.12/lib/protocol/http/body/file.rb000066400000000000000000000051551430356251100232510ustar00rootroot00000000000000# 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 stream? false 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.23.12/lib/protocol/http/body/head.rb000066400000000000000000000030431430356251100232250ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/body/inflate.rb000066400000000000000000000037211430356251100237510ustar00rootroot00000000000000# 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 stream? false 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.23.12/lib/protocol/http/body/readable.rb000066400000000000000000000060571430356251100240730ustar00rootroot00000000000000# 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 end if buffer.empty? return nil else return buffer end end end end end end ruby-protocol-http-0.23.12/lib/protocol/http/body/reader.rb000066400000000000000000000050311430356251100235650ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/body/rewindable.rb000066400000000000000000000043051430356251100244420ustar00rootroot00000000000000# 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 stream? false 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 end def rewind @index = 0 end def inspect "\#<#{self.class} #{@index}/#{@chunks.size} chunks read>" end end end end end ruby-protocol-http-0.23.12/lib/protocol/http/body/stream.rb000066400000000000000000000137231430356251100236250ustar00rootroot00000000000000# 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_relative 'buffered' 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 = Buffered.new) @input = input @output = output raise ArgumentError, "Non-writable output!" unless output.respond_to?(:write) # Will hold remaining data in `#read`. @buffer = nil @closed = false end attr :input attr :output # This provides a read-only interface for data, which is surprisingly tricky to implement correctly. module Reader # 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) return '' if length == 0 buffer ||= Async::IO::Buffer.new # Take any previously buffered data and replace it into the given buffer. if @buffer buffer.replace(@buffer) @buffer = nil else buffer.clear end if length while buffer.bytesize < length and chunk = read_next buffer << chunk end # This ensures the subsequent `slice!` works correctly. buffer.force_encoding(Encoding::BINARY) # This will be at least one copy: @buffer = buffer.byteslice(length, buffer.bytesize) # This should be zero-copy: buffer.slice!(length, buffer.bytesize) if buffer.empty? return nil else return buffer end else while chunk = read_next buffer << chunk end return buffer end end # Read at most `length` bytes from the stream. Will avoid reading from the underlying stream if possible. def read_partial(length = nil) if @buffer buffer = @buffer @buffer = nil else buffer = read_next end if buffer and length if buffer.bytesize > length # This ensures the subsequent `slice!` works correctly. buffer.force_encoding(Encoding::BINARY) @buffer = buffer.byteslice(length, buffer.bytesize) buffer.slice!(length, buffer.bytesize) end end return buffer end def read_nonblock(length, buffer = nil) @buffer ||= read_next chunk = nil unless @buffer buffer&.clear return end 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 end include Reader def write(buffer) if @output @output.write(buffer) return buffer.bytesize else raise IOError, "Stream is not writable, output has been closed!" end end def write_nonblock(buffer) write(buffer) end def <<(buffer) write(buffer) end def flush end def close_read @input&.close @input = nil end # close must never be called on the input stream. huh? def close_write @output&.close @output = nil end # Close the input and output bodies. def close(error = nil) self.close_read self.close_write return nil 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 @input return @input.read else @input = nil raise IOError, "Stream is not readable, input has been closed!" end end end end end end ruby-protocol-http-0.23.12/lib/protocol/http/body/wrapper.rb000066400000000000000000000040501430356251100240030ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/content_encoding.rb000066400000000000000000000054311430356251100247120ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/cookie.rb000066400000000000000000000044741430356251100226510ustar00rootroot00000000000000# 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('=', 2) 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.23.12/lib/protocol/http/error.rb000066400000000000000000000023721430356251100225240ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/000077500000000000000000000000001430356251100222725ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/http/header/authorization.rb000066400000000000000000000032701430356251100255210ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/cache_control.rb000066400000000000000000000042151430356251100254240ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/connection.rb000066400000000000000000000031721430356251100247610ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/cookie.rb000066400000000000000000000033031430356251100240670ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/etag.rb000066400000000000000000000025111430356251100235360ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/etags.rb000066400000000000000000000027221430356251100237250ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/multiple.rb000066400000000000000000000026411430356251100244550ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/split.rb000066400000000000000000000027321430356251100237560ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/header/vary.rb000066400000000000000000000025651430356251100236100ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/headers.rb000066400000000000000000000207171430356251100230110ustar00rootroot00000000000000# 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 # @returns Whether there are any trailers. def trailer? @tail != nil end # Record the current headers, and prepare to add trailers. # # This method is typically used after headers are sent to capture any # additional headers which should then be sent as trailers. # # A sender that intends to generate one or more trailer fields in a # message should generate a trailer header field in the header section of # that message to indicate which fields might be present in the trailers. # # @parameter names [Array] The trailer header names which will be added later. # @yields block {|name, value| ...} The trailer headers if any. # @returns An enumerator which is suitable for iterating over trailers. def trailer!(&block) @tail ||= @fields.size return trailer(&block) 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 alias key? include? 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 # @yields [String, String] header key (lower case string) and value (as string). def each(&block) @all.each do |headers| headers.each do |key, value| yield key.to_s.downcase, value.to_s end end end end end end end ruby-protocol-http-0.23.12/lib/protocol/http/methods.rb000066400000000000000000000037051430356251100230370ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/middleware.rb000066400000000000000000000040401430356251100235020ustar00rootroot00000000000000# 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 NotFound def self.close end def self.call(request) Response[404] 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.23.12/lib/protocol/http/middleware/000077500000000000000000000000001430356251100231575ustar00rootroot00000000000000ruby-protocol-http-0.23.12/lib/protocol/http/middleware/builder.rb000066400000000000000000000034471430356251100251420ustar00rootroot00000000000000# 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' module Protocol module HTTP class Middleware 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.23.12/lib/protocol/http/reference.rb000066400000000000000000000126301430356251100233270ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/request.rb000066400000000000000000000064741430356251100230720ustar00rootroot00000000000000# 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' require_relative 'headers' require_relative 'methods' 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 # The request scheme, usually one of "http" or "https". attr_accessor :scheme # The request authority, usually a hostname and port number. attr_accessor :authority # The request method, usually one of "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT" or "OPTIONS". attr_accessor :method # The request path, usually a path and query string. attr_accessor :path # The request version, usually "http/1.0", "http/1.1", "h2", or "h3". attr_accessor :version # The request headers, contains metadata associated with the request such as the user agent, accept (content type), accept-language, etc. attr_accessor :headers # The request body, an instance of Protocol::HTTP::Body::Readable or similar. attr_accessor :body # The request protocol, usually empty, but occasionally "websocket" or "webtransport", can be either single value `String` or multi-value `Array` of `String` instances. In HTTP/1, it is used to request a connection upgrade, and in HTTP/2 it is used to indicate a specfic protocol for the stream. 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.23.12/lib/protocol/http/response.rb000066400000000000000000000052541430356251100232330ustar00rootroot00000000000000# 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.23.12/lib/protocol/http/url.rb000066400000000000000000000076561430356251100222070ustar00rootroot00000000000000# 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 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 # Unescapes a percent encoded string. def self.unescape(string, encoding = string.encoding) string.b.gsub(/%(\h\h)/) do |hex| Integer($1, 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 non-path characters using percent encoding. 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 # Scan a string for URL-encoded key/value pairs. # @yields {|key, value| ...} # @parameter key [String] The unescaped key. # @parameter value [String] The unescaped key. def self.scan(string) string.split('&') 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.23.12/lib/protocol/http/version.rb000066400000000000000000000023101430356251100230500ustar00rootroot00000000000000# 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.23.12" end end ruby-protocol-http-0.23.12/protocol-http.gemspec000066400000000000000000000014071430356251100216410ustar00rootroot00000000000000# frozen_string_literal: true 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", "Bruno Sutic", "Bryan Powell", "Olle Jonsson", "Yuta Iwama"] spec.license = "MIT" spec.cert_chain = ['release.cert'] spec.signing_key = File.expand_path('~/.gem/release.pem') 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.23.12/readme.md000066400000000000000000000046601430356251100172410ustar00rootroot00000000000000# Protocol::HTTP Provides abstractions for working with the HTTP protocol. [![Development Status](https://github.com/socketry/protocol-http/workflows/Test/badge.svg)](https://github.com/socketry/protocol-http/actions?workflow=Test) ## Features - General abstractions for HTTP requests and responses. - Symmetrical interfaces for client and server. - Light-weight middlewar model for building applications. ## Usage Please see the [project documentation](https://socketry.github.io/protocol-http). ## Contributing We welcome contributions to this project. 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. ## See Also - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this interface. - [protocol-http2](https://github.com/socketry/protocol-http2) — HTTP/2 client/server implementation using this interface. - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client and server, supporting multiple HTTP protocols & TLS. - [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server WebSockets. ## 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.23.12/release.cert000066400000000000000000000033141430356251100177540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11 ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8 voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg= -----END CERTIFICATE----- ruby-protocol-http-0.23.12/spec/000077500000000000000000000000001430356251100164065ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/000077500000000000000000000000001430356251100202475ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/000077500000000000000000000000001430356251100212265ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/body/000077500000000000000000000000001430356251100221635ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/body/buffered_spec.rb000066400000000000000000000073541430356251100253150ustar00rootroot00000000000000# 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 describe '#inspect' do it "can be inspected" do expect(subject.inspect).to be =~ /\d+ chunks, \d+ bytes/ end end end ruby-protocol-http-0.23.12/spec/protocol/http/body/completable_spec.rb000066400000000000000000000030021430356251100260040ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/body/deflate_spec.rb000066400000000000000000000041331430356251100251270ustar00rootroot00000000000000#!/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.23.12/spec/protocol/http/body/digestable_spec.rb000066400000000000000000000036321430356251100256310ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/body/file_spec.rb000066400000000000000000000036051430356251100244450ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/body/file_spec.txt000066400000000000000000000000131430356251100246470ustar00rootroot00000000000000Hello Worldruby-protocol-http-0.23.12/spec/protocol/http/body/head_spec.rb000066400000000000000000000037741430356251100244360ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/body/rewindable_spec.rb000066400000000000000000000052551430356251100256450ustar00rootroot00000000000000# 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 "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.23.12/spec/protocol/http/body/stream_spec.rb000066400000000000000000000114231430356251100250160ustar00rootroot00000000000000# 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/stream' require 'protocol/http/body/buffered' RSpec.describe Protocol::HTTP::Body::Stream do let(:input) {Protocol::HTTP::Body::Buffered.new(["Hello", "World"])} let(:output) {Protocol::HTTP::Body::Buffered.new} subject {described_class.new(input, output)} describe "#read" do it "should read from the input" do expect(subject.read(5)).to be == "Hello" end it "can handle zero-length read" do expect(subject.read(0)).to be == "" end it "can read the entire input" do expect(subject.read).to be == "HelloWorld" end it "should read from the input into the given buffer" do buffer = String.new expect(subject.read(5, buffer)).to be == "Hello" expect(buffer).to be == "Hello" expect(subject.read(5, buffer)).to be == "World" expect(buffer).to be == "World" expect(subject.read(5, buffer)).to be nil expect(buffer).to be == "" end it "can read partial input" do expect(subject.read(2)).to be == "He" expect(subject.read(2)).to be == "ll" expect(subject.read(2)).to be == "oW" expect(subject.read(2)).to be == "or" expect(subject.read(2)).to be == "ld" expect(subject.read(2)).to be == nil end it "can read partial input into the given buffer" do buffer = String.new expect(subject.read(100, buffer)).to be == "HelloWorld" expect(buffer).to be == "HelloWorld" expect(subject.read(2, buffer)).to be == nil expect(buffer).to be == "" end end describe "#read_nonblock" do it "should read from the input" do expect(subject.read_nonblock(5)).to be == "Hello" expect(subject.read_nonblock(5)).to be == "World" expect(subject.read_nonblock(5)).to be == nil end it "should read from the input into the given buffer" do buffer = String.new expect(subject.read_nonblock(5, buffer)).to be == "Hello" expect(buffer).to be == "Hello" expect(subject.read_nonblock(5, buffer)).to be == "World" expect(buffer).to be == "World" expect(subject.read_nonblock(5, buffer)).to be nil expect(buffer).to be == "" end it "can read partial input into the given buffer" do buffer = String.new expect(subject.read_nonblock(100, buffer)).to be == "Hello" expect(buffer).to be == "Hello" expect(subject.read_nonblock(100, buffer)).to be == "World" expect(buffer).to be == "World" expect(subject.read_nonblock(2, buffer)).to be == nil expect(buffer).to be == "" end end describe '#close_read' do it "should close the input" do subject.close_read expect{subject.read(5)}.to raise_error(IOError) end end describe "#write" do it "should write to the output" do expect(subject.write("Hello")).to be == 5 expect(subject.write("World")).to be == 5 expect(output.chunks).to be == ["Hello", "World"] end end describe '#<<' do it "should write to the output" do subject << "Hello" subject << "World" expect(output.chunks).to be == ["Hello", "World"] end end describe "#write_nonblock" do it "should write to the output" do subject.write_nonblock("Hello") subject.write_nonblock("World") expect(output.chunks).to be == ["Hello", "World"] end end describe '#close_write' do it "should close the input" do subject.close_write expect{subject.write("X")}.to raise_error(IOError) end end describe '#flush' do it "can be flushed" do # For streams, this is a no-op since buffering is handled by the output body. subject.flush end end describe '#close' do it "can can be closed" do subject.close expect(subject).to be_closed end it "can be closed multiple times" do subject.close subject.close expect(subject).to be_closed end end end ruby-protocol-http-0.23.12/spec/protocol/http/content_encoding_spec.rb000066400000000000000000000063701430356251100261130ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/header/000077500000000000000000000000001430356251100224565ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/header/authorization_spec.rb000066400000000000000000000032201430356251100267120ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/header/cache_control_spec.rb000066400000000000000000000031761430356251100266270ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/header/cookie_spec.rb000066400000000000000000000036341430356251100252740ustar00rootroot00000000000000# 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 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.23.12/spec/protocol/http/header/etags_spec.rb000066400000000000000000000030141430356251100251160ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/header/vary_spec.rb000066400000000000000000000030621430356251100247770ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/headers/000077500000000000000000000000001430356251100226415ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/headers/connection_spec.rb000066400000000000000000000037661430356251100263530ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/headers/merged_spec.rb000066400000000000000000000036231430356251100254470ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/headers_spec.rb000066400000000000000000000142041430356251100242010ustar00rootroot00000000000000# 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 '#include?' do it "should include? named fields" do expect(subject).to be_include('set-cookie') end end describe '#key?' do it "should key? named fields" do expect(subject).to be_key('set-cookie') 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 it "can add trailer without explicit header" do 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.23.12/spec/protocol/http/methods_spec.rb000066400000000000000000000051051430356251100242310ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/middleware/000077500000000000000000000000001430356251100233435ustar00rootroot00000000000000ruby-protocol-http-0.23.12/spec/protocol/http/middleware/builder_spec.rb000066400000000000000000000034361430356251100263360ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/middleware_spec.rb000066400000000000000000000032471430356251100247100ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/reference_spec.rb000066400000000000000000000122211430356251100245210ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/request_spec.rb000066400000000000000000000035151430356251100242610ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/response_spec.rb000066400000000000000000000037551430356251100244350ustar00rootroot00000000000000# 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.23.12/spec/protocol/http/url_spec.rb000066400000000000000000000055471430356251100234020ustar00rootroot00000000000000# 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 describe '.unescape' do it "succeds with hex characters" do expect(Protocol::HTTP::URL.unescape("%3A")).to be == ":" end end end ruby-protocol-http-0.23.12/spec/protocol/http_spec.rb000066400000000000000000000024351430356251100225710ustar00rootroot00000000000000# 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.23.12/spec/spec_helper.rb000066400000000000000000000032521430356251100212260ustar00rootroot00000000000000# 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