iodine 0.1.21 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
@@ -1,108 +0,0 @@
1
- module Iodine
2
-
3
- module Http
4
- # This (will be) a Rack handler for the Iodine HTTP server.
5
- module Rack
6
- module_function
7
- def run(app, options = {})
8
- @app = app
9
- Iodine.threads ||= 18
10
- Iodine.port = options[:Port]
11
- RACK_DICTIONARY['rack.multiprocess'.freeze] = Iodine.processes.to_i > 1
12
- Iodine.protocol ||= Iodine::Http::Http1
13
- @pre_rack_handler = Iodine::Http.on_http unless Iodine::Http.on_http == Iodine::Http::NOT_IMPLEMENTED
14
- Iodine::Http.on_http self
15
- true
16
- end
17
- def call request, response
18
- if @pre_rack_handler
19
- tmp = @pre_rack_handler.call(request, response)
20
- return tmp if tmp
21
- end
22
- # response.quite!
23
- res = @app.call rack_env(request)
24
- raise "Rack app returned an unexpected value: #{res.to_s}" unless res && res.is_a?(Array)
25
- response.status = res[0]
26
- response.headers.clear
27
- res[1].each {|k, v| response.headers[k.to_s.downcase] = v }
28
- response.body = res[2]
29
- response.raw_cookies.clear
30
- response.headers['set-cookie'.freeze] = response.headers.delete('set-cookie'.freeze).split("\n".freeze).join("\r\nset-cookie: ".freeze) if request[:io].is_a?(Iodine::Http::Http1) && response.headers['set-cookie'.freeze]
31
- response.request[:no_log] = true
32
- true
33
- end
34
-
35
- protected
36
-
37
-
38
- def self.rack_env request
39
- env = RACK_DICTIONARY.dup
40
- # env['pl.request'] = @request
41
- # env.each {|k, v| env[k] = @request[v] if v.is_a?(Symbol)}
42
- RACK_ADDON.each {|k, v| env[k] = (request[v].is_a?(String) ? ( request[v].frozen? ? request[v].dup.force_encoding('ASCII-8BIT') : request[v].force_encoding('ASCII-8BIT') ): request[v])}
43
- request.each {|k, v| env["HTTP_#{k.upcase.tr('-', '_')}"] = v if k.is_a?(String) }
44
- env['rack.input'.freeze] ||= request[:body] || StringIO.new(''.force_encoding('ASCII-8BIT'.freeze).freeze)
45
- env['CONTENT_LENGTH'.freeze] = env.delete 'HTTP_CONTENT_LENGTH'.freeze if env['HTTP_CONTENT_LENGTH'.freeze]
46
- env['CONTENT_TYPE'.freeze] = env.delete 'HTTP_CONTENT_TYPE'.freeze if env['HTTP_CONTENT_TYPE'.freeze]
47
- env['HTTP_VERSION'.freeze] = "HTTP/#{request[:version].to_s}".freeze
48
- env['QUERY_STRING'.freeze] ||= ''.freeze
49
- env['rack.errors'.freeze] = StringIO.new(''.freeze)
50
- # should unchain cookies from Array to String
51
- env['HTTP_COOKIE'] = env['HTTP_COOKIE'].join '; ' if env['HTTP_COOKIE'].is_a?(Array)
52
- env
53
- end
54
-
55
- RACK_ADDON = {
56
- 'PATH_INFO' => :original_path,
57
- 'REQUEST_PATH' => :path,
58
- 'QUERY_STRING' => :quary_params,
59
- 'SERVER_NAME' => :host_name,
60
- 'REQUEST_URI' => :query,
61
- 'SERVER_PORT' => :port,
62
- 'REMOTE_ADDR' => :client_ip,
63
- # 'gr.params' => :params,
64
- # 'gr.cookies' => :cookies,
65
- 'REQUEST_METHOD' => :method,
66
- 'rack.url_scheme' => :scheme,
67
- 'rack.input' => :body
68
- }
69
-
70
- RACK_DICTIONARY = {
71
- "GATEWAY_INTERFACE" =>"CGI/1.2".freeze,
72
- 'SERVER_SOFTWARE' => "Iodine v. #{Iodine::VERSION}".freeze,
73
- 'SCRIPT_NAME' => ''.force_encoding('ASCII-8BIT'.freeze).freeze,
74
- 'rack.logger' => Iodine,
75
- 'rack.multithread' => true,
76
- 'rack.multiprocess' => false,
77
- # 'rack.hijack?' => false,
78
- # 'rack.hijack_io' => nil,
79
- 'rack.run_once' => false
80
- }
81
- RACK_DICTIONARY['rack.version'.freeze] = ::Rack.version.split('.'.freeze) if defined?(::Rack)
82
- HASH_SYM_PROC = Proc.new {|h,k| k = (Symbol === k ? k.to_s : k.to_s.to_sym); h.has_key?(k) ? h[k] : (h["iodine.#{k.to_s}"] if h.has_key?("iodine.#{k.to_s}") ) }
83
- end
84
- end
85
- end
86
-
87
- # ENV["RACK_HANDLER"] = 'iodine'
88
-
89
- # make Iodine the default fallback position for Rack.
90
- begin
91
- require 'rack/handler'
92
- Rack::Handler::WEBrick = Rack::Handler.get(:iodine)
93
- rescue Exception
94
-
95
- end
96
- ::Rack::Handler.register( 'iodine', 'Iodine::Http::Rack') if defined?(::Rack)
97
-
98
- ######
99
- ## example requests
100
-
101
- # GET /stream HTTP/1.1
102
- # Host: localhost:3000
103
- # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
104
- # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
105
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
106
- # Accept-Language: en-us
107
- # Accept-Encoding: gzip, deflate
108
- # Connection: keep-alive
@@ -1,462 +0,0 @@
1
- module Iodine
2
-
3
- module Http
4
-
5
- # This class is the part of the Iodine server.
6
- # The request object is a Hash and the Request provides
7
- # simple shortcuts and access to the request's Hash data.
8
- #
9
- #
10
- # An Http Request
11
- class Request < Hash
12
-
13
- # Sets magic cookies - NOT part of the API.
14
- #
15
- # magic cookies keep track of both incoming and outgoing cookies, setting the response's cookies as well as the combined cookie respetory (held by the request object).
16
- #
17
- # use only the []= for magic cookies. merge and update might not set the response cookies.
18
- class Cookies < ::Hash
19
- # sets the Magic Cookie's controller object (which holds the response object and it's `set_cookie` method).
20
- def set_response response
21
- @response = response
22
- end
23
- # overrides the []= method to set the cookie for the response (by encoding it and preparing it to be sent), as well as to save the cookie in the combined cookie jar (unencoded and available).
24
- def []= key, val
25
- return super unless instance_variable_defined?(:@response) && @response
26
- if key.is_a?(Symbol) && self.has_key?( key.to_s)
27
- key = key.to_s
28
- elsif self.has_key?( key.to_s.to_sym)
29
- key = key.to_s.to_sym
30
- end
31
- @response.set_cookie key, (val.nil? ? nil : val)
32
- super
33
- end
34
- # overrides th [] method to allow Symbols and Strings to mix and match
35
- def [] key
36
- if key.is_a?(Symbol) && self.has_key?( key.to_s)
37
- key = key.to_s
38
- elsif self.has_key?( key.to_s.to_sym)
39
- key = key.to_s.to_sym
40
- end
41
- super
42
- end
43
- end
44
-
45
- def initialize io = nil
46
- super()
47
- self[:io] = io if io
48
- self[:cookies] = Cookies.new
49
- self[:params] = {}
50
- @parsed = false
51
- end
52
-
53
- public
54
-
55
- # the request's headers
56
- def headers
57
- self.select {|k,v| k.is_a? String }
58
- end
59
- # the request's method (GET, POST... etc').
60
- def request_method
61
- self[:method]
62
- end
63
- # set request's method (GET, POST... etc').
64
- def request_method= value
65
- self[:method] = value
66
- end
67
- # the parameters sent by the client.
68
- def params
69
- self[:params]
70
- end
71
- # the cookies sent by the client.
72
- def cookies
73
- self[:cookies]
74
- end
75
-
76
- # the query string
77
- def query
78
- self[:query]
79
- end
80
-
81
- # the original (frozen) path (resource requested).
82
- def original_path
83
- self[:original_path]
84
- end
85
-
86
- # the requested path (rewritable).
87
- def path
88
- self[:path]
89
- end
90
- def path=(new_path)
91
- self[:path] = new_path
92
- end
93
-
94
- # The HTTP version for this request
95
- def version
96
- self[:version]
97
- end
98
-
99
- # the base url ([http/https]://host[:port])
100
- def base_url switch_scheme = nil
101
- "#{switch_scheme || self[:scheme]}://#{self[:host_name]}#{self[:port]? ":#{self[:port]}" : ''.freeze}".freeze
102
- end
103
-
104
- # the request's url, without any GET parameters ([http/https]://host[:port]/path)
105
- def request_url switch_scheme = nil
106
- "#{base_url switch_scheme}#{self[:original_path]}".freeze
107
- end
108
-
109
- # the protocol's scheme (http/https/ws/wss) managing this request
110
- def scheme
111
- self[:scheme]
112
- end
113
-
114
- # the host's name (without the port)
115
- def host_name
116
- self[:host_name]
117
- end
118
-
119
- # @return [true, false] returns true if the requested was an SSL protocol (true also if the connection is clear-text behind an SSL Proxy, such as with some PaaS providers).
120
- def ssl?
121
- self[:io].ssl? || self[:scheme] == 'https'.freeze || self[:scheme] == 'wss'.freeze
122
- end
123
- alias :secure? :ssl?
124
-
125
- # @return [Iodine::Http, Iodine::Http2, Iodine::Websockets] the Protocol used for the request.
126
- def io
127
- self[:io]
128
- end
129
-
130
- # @return [Hash like storage] Returns the session storage object IF a session was already initialized (use the response to initialize a session).
131
- def session
132
- self[:session]
133
- end
134
-
135
- # method recognition
136
-
137
- HTTP_GET = 'GET'.freeze
138
- # returns true of the method == GET
139
- def get?
140
- self[:method] == HTTP_GET
141
- end
142
-
143
- HTTP_HEAD = 'HEAD'.freeze
144
- # returns true of the method == HEAD
145
- def head?
146
- self[:method] == HTTP_HEAD
147
- end
148
- HTTP_POST = 'POST'.freeze
149
- # returns true of the method == POST
150
- def post?
151
- self[:method] == HTTP_POST
152
- end
153
- HTTP_PUT = 'PUT'.freeze
154
- # returns true of the method == PUT
155
- def put?
156
- self[:method] == HTTP_PUT
157
- end
158
- HTTP_DELETE = 'DELETE'.freeze
159
- # returns true of the method == DELETE
160
- def delete?
161
- self[:method] == HTTP_DELETE
162
- end
163
- HTTP_TRACE = 'TRACE'.freeze
164
- # returns true of the method == TRACE
165
- def trace?
166
- self[:method] == HTTP_TRACE
167
- end
168
- HTTP_OPTIONS = 'OPTIONS'.freeze
169
- # returns true of the method == OPTIONS
170
- def options?
171
- self[:method] == HTTP_OPTIONS
172
- end
173
- HTTP_CONNECT = 'CONNECT'.freeze
174
- # returns true of the method == CONNECT
175
- def connect?
176
- self[:method] == HTTP_CONNECT
177
- end
178
- HTTP_PATCH = 'PATCH'.freeze
179
- # returns true of the method == PATCH
180
- def patch?
181
- self[:method] == HTTP_PATCH
182
- end
183
- HTTP_CTYPE = 'content-type'.freeze; HTTP_JSON = /application\/json/.freeze
184
- # returns true if the request is of type JSON.
185
- def json?
186
- self[HTTP_CTYPE] =~ HTTP_JSON
187
- end
188
- HTTP_XML = /text\/xml/.freeze
189
- # returns true if the request is of type XML.
190
- def xml?
191
- self[HTTP_CTYPE].match HTTP_XML
192
- end
193
- # returns true if this is a websocket upgrade request
194
- def websocket?
195
- @is_websocket ||= (self['upgrade'.freeze] && self['upgrade'.freeze].to_s =~ /websocket/i.freeze && self['connection'.freeze].to_s =~ /upg/i.freeze && true)
196
- end
197
- alias :upgrade? :websocket?
198
-
199
- # parses an HTTP request (quary, body data etc')
200
- def self.parse request
201
- # if m = request[:query].match /(([a-z0-9A-Z]+):\/\/)?(([^\/\:]+))?(:([0-9]+))?([^\?\#]*)(\?([^\#]*))?/
202
- # request[:requested_protocol] = m[1] || request['x-forwarded-proto'] || ( request[:io].ssl? ? 'https' : 'http')
203
- # request[:host_name] = m[4] || (request['host'] ? request['host'].match(/^[^:]*/).to_s : nil)
204
- # request[:port] = m[6] || (request['host'] ? request['host'].match(/:([0-9]*)/).to_a[1] : nil)
205
- # request[:original_path] = HTTP.decode(m[7], :uri) || '/'
206
- # request['host'] ||= "#{request[:host_name]}:#{request[:port]}"
207
-
208
- # # parse query for params - m[9] is the data part of the query
209
- # if m[9]
210
- # extract_params m[9].split(/[&;]/), request[:params]
211
- # end
212
- # end
213
- return request if request[:client_ip]
214
-
215
- request.delete :headers_size
216
-
217
- # 2014 RFC 7239 Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43
218
- if tmp = request['forwarded'.freeze]
219
- tmp = tmp.join(';'.freeze) if tmp.is_a?(Array)
220
- tmp.match(/proto=([^\s;]+)/i.freeze).tap {|m| request[:scheme] = m[1].downcase if m } unless request[:scheme]
221
- tmp.match(/for=[\[]?[\"]?([^\s;\",]+)/i.freeze).tap {|m| request[:client_ip] = m[1] if m } unless request[:client_ip]
222
- end
223
-
224
- request[:client_ip] ||= (request['x-forwarded-for'.freeze].to_s.match(/[^\s\[\"\,]+/.freeze) || request[:io].io.to_io.remote_address.ip_address).to_s rescue 'unknown'.freeze
225
- request[:version] ||= '1'.freeze
226
-
227
- request[:scheme] ||= request['x-forwarded-proto'.freeze] ? request['x-forwarded-proto'.freeze].downcase : ( request[:io].ssl? ? 'https'.freeze : 'http'.freeze)
228
- tmp = (request['host'.freeze] || request[:authority] || ''.freeze).split(':'.freeze)
229
- request[:host_name] = tmp[0]
230
- request[:port] = tmp[1]
231
-
232
- tmp = (request[:query] ||= request[:path] ).split('?'.freeze, 2)
233
- request[:path] = tmp[0].chomp('/'.freeze)
234
- request[:original_path] = tmp[0].freeze
235
- request[:quary_params] = tmp[1]
236
- extract_params tmp[1].split(/[&;]/.freeze), (request[:params] ||= {}) if tmp[1]
237
-
238
- if request['cookie'.freeze]
239
- if request['cookie'.freeze].is_a?(Array)
240
- tmp = []
241
- request['cookie'.freeze].each {|s| s.split(/[;,][\s]?/.freeze).each { |c| tmp << c } }
242
- request['cookie'.freeze] = tmp
243
- extract_header tmp, request.cookies
244
- else
245
- extract_header request['cookie'.freeze].split(/[;,][\s]?/.freeze), request.cookies
246
- end
247
- elsif request['set-cookie'.freeze]
248
- request['set-cookie'.freeze] = [ request['set-cookie'.freeze] ] unless request['set-cookie'.freeze].is_a?(Array)
249
- tmp = []
250
- request['set-cookie'.freeze].each {|s| tmp << s.split(/[;][\s]?/.freeze)[0] }
251
- request['set-cookie'.freeze] = tmp
252
- extract_header tmp, request.cookies
253
- end
254
-
255
- read_body request if request[:body]
256
-
257
- request
258
- end
259
-
260
- # re-encodes a string into UTF-8
261
- def self.make_utf8!(string, encoding= ::Encoding::UTF_8)
262
- return false unless string
263
- string.force_encoding(::Encoding::ASCII_8BIT).encode!(encoding, ::Encoding::ASCII_8BIT, invalid: :replace, undef: :replace, replace: ''.freeze) unless string.force_encoding(encoding).valid_encoding?
264
- string
265
- end
266
-
267
- # re-encodes a string into UTF-8
268
- def self.try_utf8!(string, encoding= ::Encoding::UTF_8)
269
- return false unless string
270
- string.force_encoding(::Encoding::ASCII_8BIT) unless string.force_encoding(encoding).valid_encoding?
271
- string
272
- end
273
-
274
- # encodes URL data.
275
- def self.encode_url str
276
- (str.to_s.gsub(/[^a-z0-9\*\.\_\-]/i.freeze) {|m| '%%%02x'.freeze % m.ord }).force_encoding(::Encoding::ASCII_8BIT)
277
- end
278
-
279
- # Adds paramaters to a Hash object, according to the Iodine's server conventions.
280
- def self.add_param_to_hash name, value, target, &block
281
- begin
282
- c = target
283
- val = rubyfy! value
284
- a = name.chomp('[]'.freeze).split('['.freeze)
285
-
286
- a[0...-1].inject(target) do |h, n|
287
- n.chomp!(']'.freeze);
288
- n.strip!;
289
- raise "malformed parameter name for #{name}" if n.empty?
290
- n = (n.to_i.to_s == n) ? n.to_i : n.to_sym
291
- c = (h[n] ||= {})
292
- end
293
- n = a.last
294
- n.chomp!(']'.freeze); n.strip!;
295
- n = n.empty? ? nil : ( (n.to_i.to_s == n) ? n.to_i : n.to_sym )
296
- if n
297
- if c[n]
298
- c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
299
- else
300
- c[n] = val
301
- end
302
- c.default_proc = block if block
303
- else
304
- if c[n]
305
- c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
306
- else
307
- c[n] = [val]
308
- end
309
- end
310
- val
311
- rescue => e
312
- Iodine.error e
313
- Iodine.error "(Silent): parameters parse error for #{name} ... maybe conflicts with a different set?"
314
- target[name] = val
315
- end
316
- end
317
-
318
- # extracts parameters from the query
319
- def self.extract_params data, target_hash
320
- data.each do |set|
321
- list = set.split('='.freeze, 2)
322
- list.each {|s| uri_decode!(s) if s}
323
- add_param_to_hash list.shift, list.shift, target_hash
324
- end
325
- end
326
- # decode form / uri data (including the '+' sign as a space (%20) replacement).
327
- def self.uri_decode! s
328
- s.gsub!('+'.freeze, '%20'.freeze); s.gsub!(/\%[0-9a-f]{2}/i.freeze) {|m| m[1..2].to_i(16).chr}; s.gsub!(/&#[0-9]{4};/i.freeze) {|m| [m[2..5].to_i].pack 'U'.freeze }; s
329
- end
330
- # extracts parameters from header data
331
- def self.extract_header data, target_hash
332
- data.each do |set|
333
- list = set.split('='.freeze, 2)
334
- list.each {|s| form_decode!(s) if s}
335
- add_param_to_hash list.shift, list.shift, target_hash
336
- end
337
- end
338
- # decode percent-encoded data (excluding the '+' sign for encoding).
339
- def self.form_decode! s
340
- s.gsub!(/\%[0-9a-f]{2}/i.freeze) {|m| m[1..2].to_i(16).chr}; s.gsub!(/&#[0-9]{4};/i.freeze) {|m| [m[2..5].to_i].pack 'U'.freeze }; s
341
- end
342
- # Changes String to a Ruby Object, if it's a special string...
343
- def self.rubyfy!(string)
344
- return string unless string.is_a?(String)
345
- try_utf8! string
346
- if string == 'true'.freeze
347
- string = true
348
- elsif string == 'false'.freeze
349
- string = false
350
- elsif string.to_i.to_s == string
351
- string = string.to_i
352
- end
353
- string
354
- end
355
-
356
- # read the body's data and parse any incoming data.
357
- def self.read_body request
358
- # save body for Rack, if applicable
359
- # request[:rack_input] = request[:body] if ::Iodine::Http.on_http == ::Iodine::Http::Rack
360
- # parse content
361
- request[:body].rewind
362
- case request['content-type'.freeze].to_s
363
- when /x-www-form-urlencoded/.freeze
364
- extract_params request[:body].read.split(/[&;]/.freeze), request[:params] #, :form # :uri
365
- when /multipart\/form-data/.freeze
366
- read_multipart request, request
367
- when /text\/xml/.freeze
368
- # to-do support xml?
369
- # request[:xml] = make_utf8! request[:body].read
370
- nil
371
- when /application\/json/.freeze
372
- JSON.parse(make_utf8! request[:body].read).each {|k, v| add_param_to_hash k, v, request[:params]} rescue true
373
- end
374
- request[:body].rewind if request[:body]
375
- end
376
-
377
- # parse a mime/multipart body or part.
378
- def self.read_multipart request, headers = {}, boundary = [], name_prefix = String.new
379
- body = request[:body]
380
- return unless headers['content-type'.freeze].to_s =~ /multipart/i.freeze
381
- part_headers = {}
382
- extract_header headers['content-type'.freeze].split(/[;,][\s]?/.freeze), part_headers
383
- boundary << part_headers[:boundary]
384
- if part_headers[:name]
385
- if name_prefix.empty?
386
- name_prefix << part_headers[:name]
387
- else
388
- name_prefix << "[#{part_headers[:name]}]".freeze
389
- end
390
- end
391
- part_headers.delete :name
392
- part_headers.clear
393
- line = nil
394
- boundary_length = nil
395
- true until ( (line = body.gets) ) && line =~ /\A--(#{boundary.join '|'})(--)?[\r]?\n/
396
- until body.eof?
397
- return if line =~ /--[\r]?\n/.freeze
398
- return boundary.pop if boundary.count > 1 && line.match(/--(#{boundary.join '|'})/)[1] != boundary.last
399
- boundary_length = line.bytesize
400
- line = body.gets until line.nil? || line =~ /\:/.freeze
401
- until line.nil? || line =~ /^[\r]?\n/.freeze
402
- tmp = line.strip.split ':'.freeze, 2
403
- return Iodine.error "Http multipart parsing error (multipart header data malformed): #{line}" unless tmp && tmp.count == 2
404
- tmp[0].strip!; tmp[0].downcase!; tmp[1].strip!;
405
- part_headers[tmp[0]] = tmp[1]
406
- line = body.gets
407
- end
408
- return if line.nil?
409
- if !part_headers['content-disposition'.freeze]
410
- Iodine.error "Wrong multipart format with headers: #{part_headers}"
411
- return
412
- end
413
- extract_header part_headers['content-disposition'.freeze].split(/[;,][\s]?/.freeze), part_headers
414
- if name_prefix.empty?
415
- name = part_headers[:name][1..-2]
416
- else
417
- name = "#{name_prefix}[part_headers[:name][1..-2]}]"
418
- end
419
- part_headers.delete :name
420
-
421
- start_part_pos = body.pos
422
- tmp = /\A--(#{boundary.join '|'})(--)?[\r]?\n/
423
- line.clear until ( (line = body.gets) && line =~ tmp)
424
- end_part_pos = (body.pos - line.bytesize) - 2
425
- new_part_pos = body.pos
426
- body.pos = end_part_pos
427
- end_part_pos += 1 unless body.getc =~ /[\r\n]/.freeze
428
- end_part_pos += 1 unless body.getc =~ /[\r\n\-]/.freeze
429
- if part_headers['content-type'.freeze]
430
- if part_headers['content-type'.freeze] =~ /multipart/i.freeze
431
- body.pos = start_part_pos
432
- read_multipart request, part_headers, boundary, name_prefix
433
- else
434
- part_headers.delete 'content-disposition'.freeze
435
- add_param_to_hash "#{name}[type]", make_utf8!(part_headers['content-type'.freeze]), request[:params]
436
- part_headers.each {|k,v| add_param_to_hash "#{name}[#{k.to_s}]", make_utf8!(v[0] == '"' ? v[1..-2].to_s : v), request[:params] if v}
437
-
438
- tmp = Tempfile.new 'upload'.freeze, encoding: 'binary'.freeze
439
- body.pos = start_part_pos
440
- ((end_part_pos - start_part_pos)/65_536).to_i.times {tmp << body.read(65_536)}
441
- tmp << body.read(end_part_pos - body.pos)
442
- add_param_to_hash "#{name}[size]", tmp.size, request[:params]
443
- add_param_to_hash "#{name}[file]", tmp, request[:params] do |hash, key|
444
- if key == :data || key == "data".freeze && hash.has_key?(:file) && hash[:file].is_a?(::Tempfile)
445
- hash[:file].rewind
446
- (hash[:data] = hash[:file].read)
447
- end
448
- end
449
- tmp.rewind
450
- end
451
- else
452
- body.pos = start_part_pos
453
- add_param_to_hash name, form_decode!( body.read(end_part_pos - start_part_pos) ), request[:params]
454
- end
455
- body.pos = new_part_pos
456
- end
457
-
458
- end
459
-
460
- end
461
- end
462
- end