protocol-http 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []