protocol-http 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 13445d901a8d8f1b18193aaf50e6db7f44bbc4a211a731737d85de1aebb47f2f
4
+ data.tar.gz: bddfef52d76c90c55443ce2de2e3f21586e430c48f0b0abe3c0a50ee3df10d42
5
+ SHA512:
6
+ metadata.gz: 9f1550f22ece5045d1dfb20414baea631ae14f4ca62fd8936e5d2fc4c440ec6dc880ec961ae75c7edf9497101d04856c3145274276af2e3e453fd0fdb7eccc64
7
+ data.tar.gz: 1c004df3985a25c449736652b23ac11221fdded57515e8ac716f9630f409e43b345901873ecf3a19c7b1a48d33bc86afd2dd307f83a91252a5adf843d47033a2
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
6
+
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ dist: xenial
3
+ cache: bundler
4
+
5
+ matrix:
6
+ include:
7
+ - rvm: 2.4
8
+ - rvm: 2.5
9
+ - rvm: 2.6
10
+ - rvm: 2.6
11
+ env: COVERAGE=PartialSummary,Coveralls
12
+ - rvm: truffleruby
13
+ - rvm: jruby-head
14
+ env: JRUBY_OPTS="--debug -X+O"
15
+ - rvm: ruby-head
16
+ allow_failures:
17
+ - rvm: truffleruby
18
+ - rvm: ruby-head
19
+ - rvm: jruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in protocol-http.gemspec
4
+ gemspec
@@ -0,0 +1,91 @@
1
+ # Protocol::HTTP
2
+
3
+ Provides abstractions for working with the HTTP protocol with a focus on on HTTP/2.
4
+
5
+ [![Build Status](https://secure.travis-ci.com/socketry/protocol-http.svg)](http://travis-ci.com/socketry/protocol-http)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'protocol-http'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install protocol-http
22
+
23
+ ## Usage
24
+
25
+ ### Headers
26
+
27
+ ```ruby
28
+ require 'protocol/http/headers'
29
+
30
+ headers = Protocol::HTTP::Headers.new
31
+
32
+ headers['Content-Type'] = "image/jpeg"
33
+
34
+ headers['content-type']
35
+ # => "image/jpeg"
36
+ ```
37
+
38
+ ### Reference
39
+
40
+ ```ruby
41
+ require 'protocol/http/reference'
42
+
43
+ reference = Protocol::HTTP::Reference.new("/search", q: 'kittens')
44
+
45
+ reference.to_s
46
+ # => "/search?q=kittens"
47
+ ```
48
+
49
+ ### URL
50
+
51
+ ```ruby
52
+ require 'protocol/http/url'
53
+
54
+ reference = Protocol::HTTP::Reference.parse("/search?q=kittens")
55
+
56
+ parameters = Protocol::HTTP::URL.decode(reference.query_string)
57
+ # => {"q"=>"kittens"}
58
+ ```
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it
63
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
64
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
65
+ 4. Push to the branch (`git push origin my-new-feature`)
66
+ 5. Create new Pull Request
67
+
68
+ ## License
69
+
70
+ Released under the MIT license.
71
+
72
+ Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
73
+ Copyright, 2013, by Ilya Grigorik.
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining a copy
76
+ of this software and associated documentation files (the "Software"), to deal
77
+ in the Software without restriction, including without limitation the rights
78
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
79
+ copies of the Software, and to permit persons to whom the Software is
80
+ furnished to do so, subject to the following conditions:
81
+
82
+ The above copyright notice and this permission notice shall be included in
83
+ all copies or substantial portions of the Software.
84
+
85
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
86
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
87
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
88
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
89
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
90
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
91
+ THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative "http/version"
@@ -0,0 +1,44 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Protocol
22
+ module HTTP
23
+ # CGI keys (https://tools.ietf.org/html/rfc3875#section-4.1)
24
+ module CGI
25
+ AUTH_TYPE = "AUTH_TYPE".freeze
26
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
27
+ CONTENT_TYPE = "CONTENT_TYPE".freeze
28
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
29
+ PATH_INFO = "PATH_INFO".freeze
30
+ PATH_TRANSLATED = "PATH_TRANSLATED".freeze
31
+ QUERY_STRING = "QUERY_STRING".freeze
32
+ REMOTE_ADDR = "REMOTE_ADDR".freeze
33
+ REMOTE_HOST = "REMOTE_HOST".freeze
34
+ REMOTE_IDENT = "REMOTE_IDENT".freeze
35
+ REMOTE_USER = "REMOTE_USER".freeze
36
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
37
+ SCRIPT_NAME = "SCRIPT_NAME".freeze
38
+ SERVER_NAME = "SERVER_NAME".freeze
39
+ SERVER_PORT = "SERVER_PORT".freeze
40
+ SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
41
+ SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # Copyright, 2013, by Ilya Grigorik.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ module Protocol
23
+ module HTTP
24
+ class Error < StandardError
25
+ end
26
+
27
+ # The request was invalid/malformed in some way.
28
+ class BadRequest < Error
29
+ end
30
+
31
+ # Raised if connection header is missing or invalid indicating that
32
+ # this is an invalid HTTP 2.0 request - no frames are emitted and the
33
+ # connection must be aborted.
34
+ class HandshakeError < Error
35
+ end
36
+
37
+ # Raised by stream or connection handlers, results in GOAWAY frame
38
+ # which signals termination of the current connection. You *cannot*
39
+ # recover from this exception, or any exceptions subclassed from it.
40
+ class ProtocolError < Error
41
+ def initialize(message, code = nil)
42
+ super(message)
43
+
44
+ @code = code
45
+ end
46
+
47
+ attr :code
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,230 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Protocol
22
+ module HTTP
23
+ class Headers
24
+ class Split < Array
25
+ COMMA = /\s*,\s*/
26
+
27
+ def initialize(value)
28
+ super(value.split(COMMA))
29
+ end
30
+
31
+ def << value
32
+ super value.split(COMMA)
33
+ end
34
+
35
+ def to_s
36
+ join(", ")
37
+ end
38
+ end
39
+
40
+ class Multiple < Array
41
+ def initialize(value)
42
+ super()
43
+
44
+ self << value
45
+ end
46
+
47
+ def to_s
48
+ join("\n")
49
+ end
50
+ end
51
+
52
+ def self.[] hash
53
+ self.new(hash.to_a)
54
+ end
55
+
56
+ def initialize(fields = nil, indexed = nil)
57
+ if fields
58
+ @fields = fields.dup
59
+ else
60
+ @fields = []
61
+ end
62
+
63
+ if indexed
64
+ @indexed = indexed.dup
65
+ else
66
+ @indexed = self.to_h
67
+ end
68
+ end
69
+
70
+ def dup
71
+ self.class.new(@fields, @indexed)
72
+ end
73
+
74
+ attr :fields
75
+
76
+ def freeze
77
+ return if frozen?
78
+
79
+ @indexed = to_h
80
+
81
+ super
82
+ end
83
+
84
+ def empty?
85
+ @fields.empty?
86
+ end
87
+
88
+ def each(&block)
89
+ @fields.each(&block)
90
+ end
91
+
92
+ def include? key
93
+ self[key] != nil
94
+ end
95
+
96
+ def slice!(keys)
97
+ _, @fields = @fields.partition do |field|
98
+ keys.include?(field.first.downcase)
99
+ end
100
+
101
+ keys.each do |key|
102
+ @indexed.delete(key)
103
+ end
104
+
105
+ return self
106
+ end
107
+
108
+ def slice(keys)
109
+ self.dup.slice!(keys)
110
+ end
111
+
112
+ def add(key, value)
113
+ self[key] = value
114
+ end
115
+
116
+ def merge!(headers)
117
+ headers.each do |key, value|
118
+ self[key] = value
119
+ end
120
+
121
+ return self
122
+ end
123
+
124
+ def merge(headers)
125
+ self.dup.merge!(headers)
126
+ end
127
+
128
+ # Append the value to the given key. Some values can be appended multiple times, others can only be set once.
129
+ # @param key [String] The header key.
130
+ # @param value The header value.
131
+ def []= key, value
132
+ merge_into(@indexed, key.downcase, value)
133
+
134
+ @fields << [key, value]
135
+ end
136
+
137
+ MERGE_POLICY = {
138
+ # Headers which may only be specified once.
139
+ 'content-type' => false,
140
+ 'content-disposition' => false,
141
+ 'content-length' => false,
142
+ 'user-agent' => false,
143
+ 'referer' => false,
144
+ 'host' => false,
145
+ 'authorization' => false,
146
+ 'proxy-authorization' => false,
147
+ 'if-modified-since' => false,
148
+ 'if-unmodified-since' => false,
149
+ 'from' => false,
150
+ 'location' => false,
151
+ 'max-forwards' => false,
152
+
153
+ 'connection' => Split,
154
+
155
+ # Headers specifically for proxies:
156
+ 'via' => Split,
157
+ 'x-forwarded-for' => Split,
158
+
159
+ # Headers which may be specified multiple times, but which can't be concatenated.
160
+ 'set-cookie' => Multiple,
161
+ 'www-authenticate' => Multiple,
162
+ 'proxy-authenticate' => Multiple
163
+ }.tap{|hash| hash.default = Split}
164
+
165
+ # Delete all headers with the given key, and return the merged value.
166
+ def delete(key)
167
+ _, @fields = @fields.partition do |field|
168
+ field.first.downcase == key
169
+ end
170
+
171
+ return @indexed.delete(key)
172
+ end
173
+
174
+ protected def merge_into(hash, key, value)
175
+ if policy = MERGE_POLICY[key]
176
+ if current_value = hash[key]
177
+ current_value << value
178
+ else
179
+ hash[key] = policy.new(value)
180
+ end
181
+ else
182
+ raise ArgumentError, "Header #{key} can only be set once!" if hash.include?(key)
183
+
184
+ # We can't merge these, we only expose the last one set.
185
+ hash[key] = value
186
+ end
187
+ end
188
+
189
+ def [] key
190
+ @indexed[key]
191
+ end
192
+
193
+ def to_h
194
+ @fields.inject({}) do |hash, (key, value)|
195
+ merge_into(hash, key.downcase, value)
196
+
197
+ hash
198
+ end
199
+ end
200
+
201
+ def == other
202
+ if other.is_a? Hash
203
+ to_h == other
204
+ else
205
+ @fields == other.fields
206
+ end
207
+ end
208
+
209
+ # Used for merging objects into a sequential list of headers. Normalizes header keys and values.
210
+ class Merged
211
+ def initialize(*all)
212
+ @all = all
213
+ end
214
+
215
+ def << headers
216
+ @all << headers
217
+ end
218
+
219
+ # @yield [String, String] header key (lower case) and value (as string).
220
+ def each(&block)
221
+ @all.each do |headers|
222
+ headers.each do |key, value|
223
+ yield key.downcase, value.to_s
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Protocol
22
+ module HTTP
23
+ # HTTP method verbs
24
+ module Methods
25
+ GET = 'GET'.freeze
26
+ POST = 'POST'.freeze
27
+ PUT = 'PUT'.freeze
28
+ PATCH = 'PATCH'.freeze
29
+ DELETE = 'DELETE'.freeze
30
+ HEAD = 'HEAD'.freeze
31
+ OPTIONS = 'OPTIONS'.freeze
32
+ LINK = 'LINK'.freeze
33
+ UNLINK = 'UNLINK'.freeze
34
+ TRACE = 'TRACE'.freeze
35
+
36
+ # Use Methods.constants to get all constants.
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,151 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'url'
22
+
23
+ module Protocol
24
+ module HTTP
25
+ # A relative reference, excluding any authority.
26
+ class Reference
27
+ # Generate a reference from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`.
28
+ def self.parse(path = '/', parameters = nil)
29
+ base, fragment = path.split('#', 2)
30
+ path, query_string = base.split('?', 2)
31
+
32
+ self.new(path, query_string, fragment, parameters)
33
+ end
34
+
35
+ def initialize(path, query_string = nil, fragment = nil, parameters = nil)
36
+ @path = path
37
+ @query_string = query_string
38
+ @fragment = fragment
39
+ @parameters = parameters
40
+ end
41
+
42
+ def self.[] reference
43
+ if reference.is_a? self
44
+ return reference
45
+ else
46
+ return self.parse(reference)
47
+ end
48
+ end
49
+
50
+ # The path component, e.g. /foo/bar/index.html
51
+ attr :path
52
+
53
+ # The un-parsed query string, e.g. 'x=10&y=20'
54
+ attr :query_string
55
+
56
+ # A fragment, the part after the '#'
57
+ attr :fragment
58
+
59
+ # User supplied parameters that will be appended to the query part.
60
+ attr :parameters
61
+
62
+ def parameters?
63
+ @parameters and !@parameters.empty?
64
+ end
65
+
66
+ def query_string?
67
+ @query_string and !@query_string.empty?
68
+ end
69
+
70
+ def fragment?
71
+ @fragment and !@fragment.empty?
72
+ end
73
+
74
+ def append(buffer)
75
+ if query_string?
76
+ buffer << URL.escape_path(@path) << '?' << @query_string
77
+ buffer << '&' << URL.encode(@parameters) if parameters?
78
+ else
79
+ buffer << URL.escape_path(@path)
80
+ buffer << '?' << URL.encode(@parameters) if parameters?
81
+ end
82
+
83
+ if fragment?
84
+ buffer << '#' << URL.escape(@fragment)
85
+ end
86
+
87
+ return buffer
88
+ end
89
+
90
+ def to_str
91
+ append(String.new)
92
+ end
93
+
94
+ alias to_s to_str
95
+
96
+ def + other
97
+ other = self.class[other]
98
+
99
+ self.class.new(
100
+ expand_path(self.path, other.path),
101
+ other.query_string,
102
+ other.fragment,
103
+ other.parameters,
104
+ )
105
+ end
106
+
107
+ def [] parameters
108
+ self.dup(nil, parameters)
109
+ end
110
+
111
+ def dup(path = nil, parameters = nil, merge = true)
112
+ if @parameters and merge
113
+ if parameters
114
+ parameters = @parameters.merge(parameters)
115
+ else
116
+ parameters = @parameters
117
+ end
118
+ end
119
+
120
+ if path
121
+ path = expand_path(@path, path)
122
+ else
123
+ path = @path
124
+ end
125
+
126
+ self.class.new(path, @query_string, @fragment, parameters)
127
+ end
128
+
129
+ private
130
+
131
+ def expand_path(base, relative)
132
+ if relative.start_with? '/'
133
+ return relative
134
+ else
135
+ path = base.split('/')
136
+ parts = relative.split('/')
137
+
138
+ parts.each do |part|
139
+ if part == '..'
140
+ path.pop
141
+ else
142
+ path << part
143
+ end
144
+ end
145
+
146
+ return path.join('/')
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,122 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Protocol
22
+ module HTTP
23
+ module URL
24
+ # Escapes a generic string, using percent encoding.
25
+ def self.escape(string, encoding = string.encoding)
26
+ string.b.gsub(/([^a-zA-Z0-9_.\-]+)/) do |m|
27
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
28
+ end.force_encoding(encoding)
29
+ end
30
+
31
+ def self.unescape(string, encoding = string.encoding)
32
+ string.b.gsub(/%(\h\h)/) do |hex|
33
+ Integer(hex, 16).chr
34
+ end.force_encoding(encoding)
35
+ end
36
+
37
+ # According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar.
38
+ NON_PCHAR = /([^a-zA-Z0-9_\-\.~!$&'()*+,;=:@\/]+)/.freeze
39
+
40
+ # Escapes a path
41
+ def self.escape_path(path)
42
+ encoding = path.encoding
43
+ path.b.gsub(NON_PCHAR) do |m|
44
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
45
+ end.force_encoding(encoding)
46
+ end
47
+
48
+ # Encodes a hash or array into a query string
49
+ def self.encode(value, prefix = nil)
50
+ case value
51
+ when Array
52
+ return value.map {|v|
53
+ self.encode(v, "#{prefix}[]")
54
+ }.join("&")
55
+ when Hash
56
+ return value.map {|k, v|
57
+ self.encode(v, prefix ? "#{prefix}[#{escape(k.to_s)}]" : escape(k.to_s))
58
+ }.reject(&:empty?).join('&')
59
+ when nil
60
+ return prefix
61
+ else
62
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
63
+
64
+ return "#{prefix}=#{escape(value.to_s)}"
65
+ end
66
+ end
67
+
68
+ def self.scan(string)
69
+ # TODO Ruby 2.6 doesn't require `.each`
70
+ string.split('&').each do |assignment|
71
+ key, value = assignment.split('=', 2)
72
+
73
+ yield unescape(key), unescape(value)
74
+ end
75
+ end
76
+
77
+ def self.split(name)
78
+ name.scan(/([^\[]+)|(?:\[(.*?)\])/).flatten!.compact!
79
+ end
80
+
81
+ def self.assign(keys, value, parent)
82
+ top, *middle = keys
83
+
84
+ middle.each_with_index do |key, index|
85
+ if key.nil? or key.empty?
86
+ parent = (parent[top] ||= Array.new)
87
+ top = parent.size
88
+
89
+ if nested = middle[index+1] and last = parent.last
90
+ top -= 1 unless last.include?(nested)
91
+ end
92
+ else
93
+ parent = (parent[top] ||= Hash.new)
94
+ top = key
95
+ end
96
+ end
97
+
98
+ parent[top] = value
99
+ end
100
+
101
+ def self.decode(string, maximum = 8, symbolize_keys: false)
102
+ parameters = {}
103
+
104
+ self.scan(string) do |name, value|
105
+ keys = self.split(name)
106
+
107
+ if keys.count > maximum
108
+ raise ArgumentError, "Key length exceeded limit!"
109
+ end
110
+
111
+ if symbolize_keys
112
+ keys.collect!{|key| key.empty? ? nil : key.to_sym}
113
+ end
114
+
115
+ self.assign(keys, value, parameters)
116
+ end
117
+
118
+ return parameters
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Protocol
22
+ module HTTP
23
+ VERSION = "0.1.0"
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+
2
+ require_relative "lib/protocol/http/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "protocol-http"
6
+ spec.version = Protocol::HTTP::VERSION
7
+ spec.authors = ["Samuel Williams"]
8
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
+
10
+ spec.summary = "Provides abstractions to handle HTTP protocols."
11
+ spec.homepage = "https://github.com/socketry/protocol-http"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
+ f.match(%r{^(test|spec|features)/})
15
+ end
16
+
17
+ spec.required_ruby_version = '>= 2.4.0'
18
+
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "covered"
22
+ spec.add_development_dependency "bundler", ">= 1.16"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protocol-http
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: covered
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - samuel.williams@oriontransfer.co.nz
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".editorconfig"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - lib/protocol/http.rb
84
+ - lib/protocol/http/cgi.rb
85
+ - lib/protocol/http/error.rb
86
+ - lib/protocol/http/headers.rb
87
+ - lib/protocol/http/methods.rb
88
+ - lib/protocol/http/reference.rb
89
+ - lib/protocol/http/url.rb
90
+ - lib/protocol/http/version.rb
91
+ - protocol-http.gemspec
92
+ homepage: https://github.com/socketry/protocol-http
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 2.4.0
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.0.2
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Provides abstractions to handle HTTP protocols.
114
+ test_files: []