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.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- 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
|
data/lib/iodine/http/request.rb
DELETED
@@ -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
|