solutious-stella 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGES.txt +36 -0
  2. data/README.textile +162 -0
  3. data/Rakefile +88 -0
  4. data/bin/stella +12 -0
  5. data/bin/stella.bat +12 -0
  6. data/lib/daemonize.rb +56 -0
  7. data/lib/pcaplet.rb +180 -0
  8. data/lib/stella.rb +101 -0
  9. data/lib/stella/adapter/ab.rb +337 -0
  10. data/lib/stella/adapter/base.rb +106 -0
  11. data/lib/stella/adapter/httperf.rb +305 -0
  12. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  13. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  14. data/lib/stella/adapter/siege.rb +341 -0
  15. data/lib/stella/cli.rb +258 -0
  16. data/lib/stella/cli/agents.rb +73 -0
  17. data/lib/stella/cli/base.rb +55 -0
  18. data/lib/stella/cli/language.rb +18 -0
  19. data/lib/stella/cli/localtest.rb +78 -0
  20. data/lib/stella/cli/sysinfo.rb +16 -0
  21. data/lib/stella/cli/watch.rb +278 -0
  22. data/lib/stella/command/base.rb +40 -0
  23. data/lib/stella/command/localtest.rb +358 -0
  24. data/lib/stella/data/domain.rb +82 -0
  25. data/lib/stella/data/http.rb +131 -0
  26. data/lib/stella/logger.rb +84 -0
  27. data/lib/stella/response.rb +85 -0
  28. data/lib/stella/storable.rb +201 -0
  29. data/lib/stella/support.rb +276 -0
  30. data/lib/stella/sysinfo.rb +257 -0
  31. data/lib/stella/test/definition.rb +79 -0
  32. data/lib/stella/test/run/summary.rb +70 -0
  33. data/lib/stella/test/stats.rb +114 -0
  34. data/lib/stella/text.rb +64 -0
  35. data/lib/stella/text/resource.rb +38 -0
  36. data/lib/utils/crypto-key.rb +84 -0
  37. data/lib/utils/domainutil.rb +47 -0
  38. data/lib/utils/escape.rb +302 -0
  39. data/lib/utils/fileutil.rb +78 -0
  40. data/lib/utils/httputil.rb +266 -0
  41. data/lib/utils/mathutil.rb +15 -0
  42. data/lib/utils/stats.rb +88 -0
  43. data/lib/utils/textgraph.rb +267 -0
  44. data/lib/utils/timerutil.rb +58 -0
  45. data/lib/win32/Console.rb +970 -0
  46. data/lib/win32/Console/ANSI.rb +305 -0
  47. data/support/kvm.h +91 -0
  48. data/support/ruby-pcap-takuma-notes.txt +19 -0
  49. data/support/ruby-pcap-takuma-patch.txt +30 -0
  50. data/support/text/en.yaml +80 -0
  51. data/support/text/nl.yaml +7 -0
  52. data/support/useragents.txt +75 -0
  53. data/tests/01-util_test.rb +0 -0
  54. data/tests/02-stella-util_test.rb +42 -0
  55. data/tests/10-stella_test.rb +104 -0
  56. data/tests/11-stella-storable_test.rb +68 -0
  57. data/tests/60-stella-command_test.rb +248 -0
  58. data/tests/80-stella-cli_test.rb +45 -0
  59. data/tests/spec-helper.rb +31 -0
  60. metadata +165 -0
@@ -0,0 +1,47 @@
1
+
2
+ require 'net/dns/packet'
3
+
4
+ module DomainUtil
5
+
6
+ def DomainUtil.parse_domain_request(data=[])
7
+ return unless data && !data.empty?
8
+ data = data.split(/\r?\n/) unless data.kind_of? Array
9
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
10
+
11
+ dns_data = Net::DNS::Packet.parse( data.join($/) )
12
+ return unless dns_data.header.query?
13
+ domain_name = dns_data.question[0].qName
14
+ return dns_data, domain_name, dns_data.header
15
+ end
16
+
17
+ def DomainUtil.parse_domain_response(data=[])
18
+ return unless data && !data.empty?
19
+ data = data.split(/\r?\n/) unless data.kind_of? Array
20
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
21
+
22
+ # This is the heavy lifting.
23
+ dns_data = Net::DNS::Packet.parse( data.join($/) )
24
+
25
+ # We don't want queries or empty answers
26
+ return if dns_data.header.query? || dns_data.answer.nil? || dns_data.answer.empty?
27
+
28
+ domain_name = dns_data.answer[0].name
29
+
30
+ # Empty the lists if they are already populated
31
+ addresses = []
32
+ cnames = []
33
+
34
+ # Store the CNAMEs associated to this domain. Can be empty.
35
+ dns_data.each_cname do |cname|
36
+ cnames << cname.to_s
37
+ end
38
+
39
+ # Store the IP address for this domain. If empty, the lookup was unsuccessful.
40
+ dns_data.each_address do |ip|
41
+ addresses << ip.to_s
42
+ end
43
+
44
+ return dns_data, domain_name, dns_data.header, addresses, cnames
45
+ end
46
+
47
+ end
@@ -0,0 +1,302 @@
1
+ # escape.rb - escape/unescape library for several formats
2
+ #
3
+ # Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ # Escape module provides several escape functions.
28
+ # * URI
29
+ # * HTML
30
+ # * shell command
31
+ module EscapeUtil
32
+ module_function
33
+
34
+ class StringWrapper
35
+ class << self
36
+ alias new_no_dup new
37
+ def new(str)
38
+ new_no_dup(str.dup)
39
+ end
40
+ end
41
+
42
+ def initialize(str)
43
+ @str = str
44
+ end
45
+
46
+ def to_s
47
+ @str.dup
48
+ end
49
+
50
+ def inspect
51
+ "\#<#{self.class}: #{@str}>"
52
+ end
53
+
54
+ def ==(other)
55
+ other.class == self.class && @str == other.instance_variable_get(:@str)
56
+ end
57
+ alias eql? ==
58
+
59
+ def hash
60
+ @str.hash
61
+ end
62
+ end
63
+
64
+ class ShellEscaped < StringWrapper
65
+ end
66
+
67
+ # Escape.shell_command composes
68
+ # a sequence of words to
69
+ # a single shell command line.
70
+ # All shell meta characters are quoted and
71
+ # the words are concatenated with interleaving space.
72
+ # It returns an instance of ShellEscaped.
73
+ #
74
+ # Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
75
+ # Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
76
+ #
77
+ # Note that system(*command) and
78
+ # system(Escape.shell_command(command)) is roughly same.
79
+ # There are two exception as follows.
80
+ # * The first is that the later may invokes /bin/sh.
81
+ # * The second is an interpretation of an array with only one element:
82
+ # the element is parsed by the shell with the former but
83
+ # it is recognized as single word with the later.
84
+ # For example, system(*["echo foo"]) invokes echo command with an argument "foo".
85
+ # But system(Escape.shell_command(["echo foo"])) invokes "echo foo" command without arguments (and it probably fails).
86
+ def shell_command(command)
87
+ s = command.map {|word| shell_single_word(word) }.join(' ')
88
+ ShellEscaped.new_no_dup(s)
89
+ end
90
+
91
+ # Escape.shell_single_word quotes shell meta characters.
92
+ # It returns an instance of ShellEscaped.
93
+ #
94
+ # The result string is always single shell word, even if
95
+ # the argument is "".
96
+ # Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
97
+ #
98
+ # Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
99
+ # Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
100
+ # Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
101
+ def shell_single_word(str)
102
+ if str && str.empty?
103
+ ShellEscaped.new_no_dup("''")
104
+ elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
105
+ ShellEscaped.new(str)
106
+ else
107
+ result = ''
108
+ str.scan(/('+)|[^']+/) {
109
+ if $1
110
+ result << %q{\'} * $1.length
111
+ else
112
+ result << "'#{$&}'"
113
+ end
114
+ }
115
+ ShellEscaped.new_no_dup(result)
116
+ end
117
+ end
118
+
119
+ class PercentEncoded < StringWrapper
120
+ end
121
+
122
+ # Escape.uri_segment escapes URI segment using percent-encoding.
123
+ # It returns an instance of PercentEncoded.
124
+ #
125
+ # Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
126
+ #
127
+ # The segment is "/"-splitted element after authority before query in URI, as follows.
128
+ #
129
+ # scheme://authority/segment1/segment2/.../segmentN?query#fragment
130
+ #
131
+ # See RFC 3986 for details of URI.
132
+ def uri_segment(str)
133
+ # pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
134
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
135
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
136
+ s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
137
+ '%' + $&.unpack("H2")[0].upcase
138
+ }
139
+ PercentEncoded.new_no_dup(s)
140
+ end
141
+
142
+ # Escape.uri_path escapes URI path using percent-encoding.
143
+ # The given path should be a sequence of (non-escaped) segments separated by "/".
144
+ # The segments cannot contains "/".
145
+ # It returns an instance of PercentEncoded.
146
+ #
147
+ # Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
148
+ # Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
149
+ #
150
+ # The path is the part after authority before query in URI, as follows.
151
+ #
152
+ # scheme://authority/path#fragment
153
+ #
154
+ # See RFC 3986 for details of URI.
155
+ #
156
+ # Note that this function is not appropriate to convert OS path to URI.
157
+ def uri_path(str)
158
+ s = str.gsub(%r{[^/]+}n) { uri_segment($&) }
159
+ PercentEncoded.new_no_dup(s)
160
+ end
161
+
162
+ # :stopdoc:
163
+ def html_form_fast(pairs, sep='&')
164
+ s = pairs.map {|k, v|
165
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
166
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
167
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
168
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
169
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
170
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
171
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
172
+ k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
173
+ '%' + $&.unpack("H2")[0].upcase
174
+ }
175
+ v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
176
+ '%' + $&.unpack("H2")[0].upcase
177
+ }
178
+ "#{k}=#{v}"
179
+ }.join(sep)
180
+ PercentEncoded.new_no_dup(s)
181
+ end
182
+ # :startdoc:
183
+
184
+ # Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
185
+ # It returns an instance of PercentEncoded.
186
+ #
187
+ # Escape.html_form takes an array of pair of strings or
188
+ # an hash from string to string.
189
+ #
190
+ # Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
191
+ # Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
192
+ #
193
+ # In the array form, it is possible to use same key more than once.
194
+ # (It is required for a HTML form which contains
195
+ # checkboxes and select element with multiple attribute.)
196
+ #
197
+ # Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
198
+ #
199
+ # If the strings contains characters which must be escaped in x-www-form-urlencoded,
200
+ # they are escaped using %-encoding.
201
+ #
202
+ # Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
203
+ #
204
+ # The separator can be specified by the optional second argument.
205
+ #
206
+ # Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
207
+ #
208
+ # See HTML 4.01 for details.
209
+ def html_form(pairs, sep='&')
210
+ r = ''
211
+ first = true
212
+ pairs.each {|k, v|
213
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
214
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
215
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
216
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
217
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
218
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
219
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
220
+ r << sep if !first
221
+ first = false
222
+ k.each_byte {|byte|
223
+ ch = byte.chr
224
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
225
+ r << "%" << ch.unpack("H2")[0].upcase
226
+ else
227
+ r << ch
228
+ end
229
+ }
230
+ r << '='
231
+ v.each_byte {|byte|
232
+ ch = byte.chr
233
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
234
+ r << "%" << ch.unpack("H2")[0].upcase
235
+ else
236
+ r << ch
237
+ end
238
+ }
239
+ }
240
+ PercentEncoded.new_no_dup(r)
241
+ end
242
+
243
+ class HTMLEscaped < StringWrapper
244
+ end
245
+
246
+ # :stopdoc:
247
+ HTML_TEXT_ESCAPE_HASH = {
248
+ '&' => '&amp;',
249
+ '<' => '&lt;',
250
+ '>' => '&gt;',
251
+ }
252
+ # :startdoc:
253
+
254
+ # Escape.html_text escapes a string appropriate for HTML text using character references.
255
+ # It returns an instance of HTMLEscaped.
256
+ #
257
+ # It escapes 3 characters:
258
+ # * '&' to '&amp;'
259
+ # * '<' to '&lt;'
260
+ # * '>' to '&gt;'
261
+ #
262
+ # Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
263
+ # Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a &amp; b &lt; c &gt; d>
264
+ #
265
+ # This function is not appropriate for escaping HTML element attribute
266
+ # because quotes are not escaped.
267
+ def html_text(str)
268
+ s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
269
+ HTMLEscaped.new_no_dup(s)
270
+ end
271
+
272
+ # :stopdoc:
273
+ HTML_ATTR_ESCAPE_HASH = {
274
+ '&' => '&amp;',
275
+ '<' => '&lt;',
276
+ '>' => '&gt;',
277
+ '"' => '&quot;',
278
+ }
279
+ # :startdoc:
280
+
281
+ class HTMLAttrValue < StringWrapper
282
+ end
283
+
284
+ # Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
285
+ # It returns an instance of HTMLAttrValue.
286
+ #
287
+ # Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
288
+ # Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&amp;b">
289
+ # Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&amp;&lt;&gt;&quot;c">
290
+ # Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
291
+ #
292
+ # It escapes 4 characters:
293
+ # * '&' to '&amp;'
294
+ # * '<' to '&lt;'
295
+ # * '>' to '&gt;'
296
+ # * '"' to '&quot;'
297
+ #
298
+ def html_attr_value(str)
299
+ s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
300
+ HTMLAttrValue.new_no_dup(s)
301
+ end
302
+ end
@@ -0,0 +1,78 @@
1
+ require 'fileutils'
2
+
3
+ module FileUtil
4
+
5
+ def FileUtil.read_file(path)
6
+ FileUtil.read_file_to_array(path).join('')
7
+ end
8
+
9
+ def FileUtil.read_file_to_array(path)
10
+ contents = []
11
+ return contents unless File.exists?(path)
12
+
13
+ open(path, 'r') do |l|
14
+ contents = l.readlines
15
+ end
16
+
17
+ contents
18
+ end
19
+ def FileUtil.read_binary_file(path)
20
+ contents = ''
21
+ return contents unless File.exists?(path)
22
+
23
+ open(path, 'rb') do |l|
24
+ while (!l.eof?)
25
+ contents << l.read(4096)
26
+ end
27
+ end
28
+
29
+ contents
30
+ end
31
+
32
+
33
+
34
+ def FileUtil.write_file(path, content, flush=true)
35
+ FileUtil.write_or_append_file('w', path, content, flush)
36
+ end
37
+
38
+ def FileUtil.append_file(path, content, flush=true)
39
+ FileUtil.write_or_append_file('a', path, content, flush)
40
+ end
41
+
42
+ def FileUtil.write_or_append_file(write_or_append, path, content = '', flush = true)
43
+ #STDERR.puts "Writing to #{ path }..."
44
+ FileUtil.create_dir(File.dirname(path))
45
+
46
+ open(path, write_or_append) do |f|
47
+ f.puts content
48
+ f.flush if flush;
49
+ end
50
+ File.chmod(0600, path)
51
+ end
52
+
53
+ def FileUtil.create_file(filepath, perm='w', file_perms=nil, force=false)
54
+ raise Exception.new("File #{filepath} already exists!") if File.exists?(filepath) && !force
55
+
56
+ newfile = File.new(filepath, perm)
57
+ begin
58
+ if file_perms && File.exists?(file_perms)
59
+ File.chown(File.stat(file_perms).uid.to_i, File.stat(file_perms).gid.to_i, filepath)
60
+ end
61
+ rescue NotImplementedError => ex
62
+ end
63
+
64
+ newfile
65
+ end
66
+
67
+ def FileUtil.create_dir(dirpath, dir_perms=nil)
68
+ return if File.directory?(dirpath)
69
+
70
+ #STDERR.puts "Creating #{ dirpath }"
71
+ FileUtils.makedirs(dirpath)
72
+
73
+ if dir_perms && File.exists?(dir_perms)
74
+ File.chown(File.stat(dir_perms).uid.to_i, File.stat(dir_perms).gid.to_i, dirpath)
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,266 @@
1
+
2
+ require 'uri'
3
+ require 'timeout'
4
+ require 'net/http'
5
+
6
+ module HTTPUtil
7
+ VALID_METHODS = %w{GET HEAD POST PUT DELETE}
8
+ @@timeout = 20
9
+
10
+ # Takes a string. See WEBrick::parse_header(string).
11
+ def HTTPUtil.parse_header(raw)
12
+ header = Hash.new([].freeze)
13
+ raw.each_line do |line|
14
+ case line
15
+ when /\A(.+?):\s+(.+)\z/om
16
+ name, value = $1, $2
17
+ name = name.tr('-', '_').to_sym
18
+ value.strip!
19
+
20
+ header[name] = [] unless header.has_key?(name)
21
+ header[name] << value
22
+ end
23
+ end
24
+ header
25
+ end
26
+
27
+ # Takes a string or array. See parse_header_body for further info.
28
+ # Returns +method+, +http_version+, +uri+, +header+, +body+
29
+ def HTTPUtil.parse_http_request(data, host=:unknown, port=80)
30
+ return unless data && !data.empty?
31
+ data = data.split(/\r?\n/) unless data.kind_of? Array
32
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
33
+ request_line = data.shift # i.e. GET /path HTTP/1.1
34
+ method, path, http_version = nil
35
+
36
+ # With WEBrick and other proxies, the entire URI is included in HTTP requests.
37
+ # i.e. GET http://stellaaahhhh.com/streetcar.png HTTP/1.1
38
+ # The parser is expecting just the absolute path.
39
+ if request_line =~ /^(\S+)\s+(http:\/\/.+)\s+(HTTP.+)?/mo
40
+ uri = URI.parse($2)
41
+ request_line = "#{$1} #{uri.request_uri} #{$3}"
42
+ host = uri.host
43
+ end
44
+
45
+ if request_line =~ /^(\S+)\s+(\S+)(?:\s+HTTP\/(\d+\.\d+))?/mo
46
+ method = $1
47
+ http_version = $3 # Comes before $2 b/c the split resets the numbered vars
48
+ path, query_string = $2.split('?')
49
+
50
+ # We only process the header and body data when we know we're
51
+ # starting from the beginning of a request string. We don't
52
+ # want no partials.
53
+ header, body = HTTPUtil.parse_header_body(data)
54
+ query = HTTPUtil.parse_query(method, query_string)
55
+
56
+
57
+ # TODO: Parse username/password
58
+ uri = URI::HTTP.build({
59
+ :scheme => 'http',
60
+ :host => header[:Host][0] || host.to_s,
61
+ :port => port,
62
+ :path => path,
63
+ :query => query_string
64
+ })
65
+
66
+ else
67
+ rl = request_line.sub(/\x0d?\x0a\z/o, '')
68
+ raise "Bad Request-Line `#{rl}'."
69
+ end
70
+
71
+ return method, http_version, uri, header, body
72
+ end
73
+
74
+
75
+ # Takes a string or array. See parse_header_body for further info.
76
+ # Returns +status+, +http_version+, +message+, +header+, +body+
77
+ def HTTPUtil.parse_http_response(data=[])
78
+ return unless data && !data.empty?
79
+ data = data.split(/\r?\n/) unless data.kind_of? Array
80
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
81
+ status_line = data.shift # ie. HTTP/1.1 200 OK
82
+ http_version, status, message = nil
83
+
84
+ if status_line =~ /^HTTP\/(\d.+?)(\s+(\d\d\d)\s+(.+))?$/mo
85
+ http_version = $1
86
+ status = $2
87
+ message = $4
88
+
89
+ header, body, query = HTTPUtil.parse_header_body(data)
90
+
91
+ else
92
+ raise "Bad Response-Line `#{status_line}'."
93
+ end
94
+
95
+ return status, http_version, message, header, body
96
+ end
97
+
98
+ # Process everything after the first line of an HTTP request or response:
99
+ # GET / HTTP/1.1
100
+ # HTTP/1.1 200 OK
101
+ # etc...
102
+ # Used by parse_http_request and parse_http_response but can be used separately.
103
+ # Takes a string or array of strings. A string should be formatted like an HTTP
104
+ # request or response. If a body is present it should be separated by two newlines.
105
+ # An array of string should contain an empty or nil element between the header
106
+ # and body content. This will happen naturally if the raw lines were split by
107
+ # a single line terminator. (i.e. /\n/ rather than /\n\n/)
108
+ # Returns header (hash), body (string)
109
+ def HTTPUtil.parse_header_body(data=[])
110
+ header, body = {}, nil
111
+ data = data.split(/\r?\n/) unless data.kind_of? Array
112
+ data.shift while (data[0].nil? || data[0].empty?) # Remove leading empties
113
+
114
+ return header, body unless data && !data.empty?
115
+
116
+ #puts data.to_yaml
117
+
118
+ # Skip that first line if it exists
119
+ data.shift if data[0].match(/\AHTTP|GET|POST|DELETE|PUT|HEAD/mo)
120
+
121
+ header_lines = []
122
+ header_lines << data.shift while (!data[0].nil? && !data[0].empty?)
123
+ header = HTTPUtil::parse_header(header_lines.join($/))
124
+
125
+ # We omit the blank line that delimits the header from the body
126
+ body = data[1..-1].join($/) unless data.empty?
127
+
128
+ return header, body
129
+ end
130
+
131
+ def HTTPUtil.parse_query(request_method, query_string, content_type='', body='')
132
+ query = Hash.new([].freeze)
133
+
134
+ if request_method == "GET" || request_method == "HEAD"
135
+ query = HTTPUtil::parse_query_from_string(query_string)
136
+ elsif content_type =~ /^application\/x-www-form-urlencoded/
137
+ query = HTTPUtil::parse_query_from_string(body)
138
+ elsif content_type =~ /^multipart\/form-data; boundary=(.+)/
139
+ boundary = $1.tr('"', '')
140
+ query = HTTPUtil::parse_form_data(body, boundary)
141
+ else
142
+ query
143
+ end
144
+
145
+ query
146
+ end
147
+
148
+ def HTTPUtil.validate_method(meth='GET')
149
+ (VALID_METHODS.member? meth.upcase) ? meth : VALID_METHODS[0]
150
+ end
151
+
152
+ # Parses a query string by breaking it up at the '&'
153
+ # and ';' characters. You can also use this to parse
154
+ # cookies by changing the characters used in the second
155
+ # parameter (which defaults to '&;'.
156
+ # Stolen from Mongrel
157
+ def HTTPUtil.parse_query_from_string(qs, d = '&;')
158
+ params = {}
159
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
160
+ k, v=unescape(p).split('=',2)
161
+ next unless k
162
+ k = k.tr('-', '_').to_sym
163
+ if cur = params[k]
164
+ if cur.class == Array
165
+ params[k] << v
166
+ else
167
+ params[k] = [cur, v]
168
+ end
169
+ else
170
+ params[k] = v
171
+ end
172
+ }
173
+
174
+ return params
175
+ end
176
+
177
+
178
+
179
+ # Based on WEBrick::HTTPutils::parse_form_data
180
+ def HTTPUtil.parse_form_data(io, boundary)
181
+ boundary_regexp = /\A--#{boundary}(--)?#{$/}\z/
182
+ form_data = Hash.new
183
+ return form_data unless io
184
+ data = nil
185
+ io.each_line{|line|
186
+ if boundary_regexp =~ line
187
+ if data
188
+ data.chop!
189
+ key = data.name.tr('-', '_').to_sym
190
+ if form_data.has_key?(key)
191
+ form_data[key].append_data(data)
192
+ else
193
+ form_data[key] = data
194
+ end
195
+ end
196
+ data = FormData.new
197
+ next
198
+ else
199
+ if data
200
+ data << line
201
+ end
202
+ end
203
+ }
204
+ return form_data
205
+ end
206
+
207
+
208
+ # Extend the basic query string parser provided by the cgi module.
209
+ # converts single valued params (the most common case) to
210
+ # objects instead of arrays
211
+ #
212
+ # Input:
213
+ # the query string
214
+ #
215
+ # Output:
216
+ # hash of parameters, contains arrays for multivalued parameters
217
+ # (multiselect, checkboxes , etc)
218
+ # If no query string is provided (nil or "") returns an empty hash.
219
+ def HTTPUtil.query_to_hash(query_string)
220
+ return {} unless query_string
221
+
222
+ query_parameters = HTTPUtil.parse_query(query_string)
223
+
224
+ query_parameters.each { |key, val|
225
+ # replace the array with an object
226
+ query_parameters[key] = val[0] if 1 == val.length
227
+ }
228
+
229
+ # set default value to nil! cgi sets this to []
230
+ query_parameters.default = nil
231
+
232
+ return query_parameters
233
+ end
234
+
235
+ def HTTPUtil.hash_to_query(parameters)
236
+ return '' unless parameters
237
+ pairs = []
238
+ parameters.each do |param, value|
239
+ pairs << "#{param}=#{URI.escape(value.to_s)}"
240
+ end
241
+ return pairs.join('&')
242
+ #return pairs.join(";")
243
+ end
244
+
245
+
246
+
247
+ # Performs URI escaping so that you can construct proper
248
+ # query strings faster. Use this rather than the cgi.rb
249
+ # version since it's faster. (Stolen from Mongrel/Camping).
250
+ def HTTPUtil.escape(s)
251
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
252
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
253
+ }.tr(' ', '+')
254
+ end
255
+
256
+
257
+ # Unescapes a URI escaped string. (Stolen from Mongrel/Camping).
258
+ def HTTPUtil.unescape(s)
259
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
260
+ [$1.delete('%')].pack('H*')
261
+ }
262
+ end
263
+
264
+
265
+ end
266
+