kamu-wang 0.02

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/wang.rb ADDED
@@ -0,0 +1,437 @@
1
+ # vim: set noet:
2
+ #
3
+ # WANG - Web Access with No Grief
4
+ # http://github.com/kamu/wang/
5
+
6
+ require 'socket'
7
+ require 'uri'
8
+ require 'stringio'
9
+ require 'zlib'
10
+ require 'logger'
11
+ require 'yaml'
12
+ require 'timeout'
13
+ require 'base64'
14
+
15
+ class URI::Generic
16
+ def to_uri
17
+ self
18
+ end
19
+ end
20
+
21
+ class String
22
+ def to_uri
23
+ URI.parse(self)
24
+ end
25
+ end
26
+
27
+ module WANG
28
+ Response = Struct.new(:method, :uri, :status, :headers)
29
+
30
+ DEFAULT_OPEN_TIMEOUT = 60
31
+ DEFAULT_READ_TIMEOUT = 60
32
+ INFINITE_REDIR_COUNT = 7
33
+
34
+ # Creates a new instance of WANG::Client
35
+ #
36
+ # For more info, check WANG::Client.new
37
+ def self.new *args
38
+ Client.new(*args)
39
+ end
40
+
41
+ class TCPSocket < TCPSocket # add the timeouts :nodoc:
42
+ def initialize *args # allows passing of the timeout values
43
+ custom_args = args.shift
44
+ @read_timeout = custom_args[:read_timeout]
45
+ open_timeout = custom_args[:open_timeout]
46
+ Timeout::timeout(open_timeout) { super }
47
+ end
48
+
49
+ TIMEOUT_READ = %w{read readpartial gets readline}
50
+ TIMEOUT_READ.each {|m|
51
+ class_eval "def #{m}(*args); Timeout::timeout(@read_timeout) { super }; end;"
52
+ }
53
+ end
54
+
55
+ class Client
56
+ attr_accessor :responses
57
+
58
+ # Creates a new instance of WANG::Client
59
+ #
60
+ # Accepts a hash containing named arguments. Arguments:
61
+ # [:read_timeout] defines the timeout for socket reading in seconds
62
+ # [:open_timeout] defines the timeout for connecting in seconds
63
+ # [:debug] any value passed defines debug mode
64
+ # [:proxy_address] defines the proxy address to use for all the requests made (host:port)
65
+ def initialize args = {}
66
+ @log = Logger.new(STDOUT)
67
+ @log.level = args[:debug] ? Logger::DEBUG : Logger::WARN
68
+
69
+ @jar = Jar.new
70
+ @socket = nil
71
+ @host, @port = nil, nil # create a struct that combines host & port?
72
+ @responses = []
73
+ @read_timeout = args[:read_timeout] || DEFAULT_READ_TIMEOUT
74
+ @open_timeout = args[:open_timeout] || DEFAULT_OPEN_TIMEOUT
75
+ @proxy_host, @proxy_port = args[:proxy_address] ? args[:proxy_address].split(':', 2) : [nil, nil]
76
+
77
+ @log.debug("Connecting through a proxy: #{@proxy_host}:#{@proxy_port}") if @proxy_host
78
+ @log.debug("Using #{@read_timeout} as the read timeout and #{@open_timeout} as the open timeout")
79
+ end
80
+
81
+ # Issues a HEAD request.
82
+ #
83
+ # Returns +nil+ for the body.
84
+ def head url, referer = nil
85
+ @log.debug("HEAD: #{url.to_s}")
86
+ request('HEAD', url.to_uri, referer)
87
+ end
88
+
89
+ # Fetches a page using GET method
90
+ #
91
+ # If passed, referer will be sent to the server. Otherwise the last visited URL will be sent to the server as the referer.
92
+ def get url, referer = nil
93
+ @log.debug("GET: #{url.to_s}")
94
+ request("GET", url.to_uri, referer)
95
+ end
96
+
97
+ # Fetches a page using POST method
98
+ #
99
+ # Data can either be a String or a Hash. If passed a String, it will send it to the server as the POST data. If passed a Hash, it will be converted to post data and correctly escaped.
100
+ #
101
+ # If passed, referer will be sent to the server. Otherwise the last visited URL will be sent to the server as the referer.
102
+ def post url, data, referer = nil
103
+ @log.debug("POST: #{url.to_s}")
104
+ request("POST", url.to_uri, referer, data)
105
+ end
106
+
107
+ # Issues a PUT request. See post for more details.
108
+ def put url, data, referer = nil
109
+ @log.debug("PUT: #{url.to_s}")
110
+ request("PUT", url.to_uri, referer, data)
111
+ end
112
+
113
+ # Issues a DELETE request.
114
+ #
115
+ # Returns +nil+ for the body.
116
+ def delete url, referer = nil
117
+ @log.debug("DELETE: #{url.to_s}")
118
+ request("DELETE", url.to_uri, referer)
119
+ end
120
+
121
+ # Saves cookie from this Client instance's Jar to the given io
122
+ def save_cookies io
123
+ @jar.save(io)
124
+ end
125
+
126
+ # Loads cookies to this Client instance's Jar from the given io
127
+ def load_cookies io
128
+ @jar.load(io)
129
+ end
130
+
131
+ # Sets the HTTP authentication username & password, which are then used for requests
132
+ # Call with +nil+ as username to remove authentication
133
+ def set_auth username, password=''
134
+ @http_auth = username ? Base64.encode64(username+':'+password).chomp : nil # for some reason, encode64 might add a \n
135
+ end
136
+
137
+ private
138
+ def request method, uri, referer = nil, data = nil
139
+ uri.path = "/" if uri.path.empty? # fix the path to contain / right here, otherwise it should be added to cookie stuff too
140
+ uri = @responses.last.uri + uri unless uri.is_a?(URI::HTTP) # if the uri is relative, combine it with the uri of the latest response
141
+ check_socket *(@proxy_host ? [@proxy_host, @proxy_port] : [uri.host, uri.port])
142
+
143
+ referer = referer || @responses.last.nil? ? nil : @responses.last.uri
144
+ @responses.clear if not @responses.empty? and not redirect?(@responses.last.status)
145
+
146
+ #http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
147
+ #there's no defined redir count that's considered infinite, so try something that makes sense
148
+ if @responses.length > INFINITE_REDIR_COUNT
149
+ return #raise an error?
150
+ end
151
+
152
+ @socket << generate_request_headers(method, uri, referer)
153
+ @socket << "Authorization: Basic #{@http_auth}\r\n" if @http_auth
154
+
155
+ if @jar.has_cookies_for?(uri)
156
+ @socket << "Cookie: #{@jar.cookies_for(uri)}\r\n"
157
+ @log.debug("SENDING COOKIES: #{@jar.cookies_for(uri)}")
158
+ end
159
+
160
+ data = data.map {|k,v| "#{Utils.escape(k)}=#{Utils.escape(v)}"}.join("&") if data.is_a?(Hash)
161
+
162
+ if data
163
+ @socket << "Content-Type: application/x-www-form-urlencoded\r\n"
164
+ @socket << "Content-Length: #{data.length}\r\n"
165
+ end
166
+ @socket << "\r\n"
167
+ @socket << data if data
168
+
169
+ status = read_status
170
+ @log.debug("STATUS: #{status}")
171
+ headers = read_headers(uri)
172
+ @log.debug("HEADERS: #{headers.inspect}")
173
+ body = read_body(headers) if returns_body?(method)
174
+ @log.debug("WANGJAR: #{@jar.inspect}")
175
+
176
+ @socket.close if headers["connection"] =~ /close/
177
+
178
+ @responses << Response.new(method, uri, status, headers)
179
+ return follow_redirect(headers["location"]) if redirect?(status)
180
+
181
+ body &&= decompress(headers["content-encoding"], body)
182
+
183
+ return status, headers, body
184
+ end
185
+
186
+ def generate_request_headers request_method, uri, referer
187
+ request_path = uri.path + (uri.query.nil? ? '' : "?#{uri.query}")
188
+ request_host = uri.host + (uri.port ? ":#{uri.port}" : '')
189
+ [
190
+ "#{request_method} #{@proxy_host ? uri.to_s : request_path} HTTP/1.1",
191
+ "Host: #{request_host}",
192
+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12",
193
+ "Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
194
+ "Accept-Language: en-us,en;q=0.5",
195
+ "Accept-Encoding: gzip,deflate,identity",
196
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
197
+ "Keep-Alive: 300",
198
+ "Connection: keep-alive",
199
+ referer.nil? ? "" : "Referer: #{referer}\r\n" # an extra \r\n is needed for the last entry
200
+ ].join("\r\n")
201
+ end
202
+
203
+ def read_status
204
+ line = @socket.gets("\n")
205
+ status = line.match(%r{^HTTP/1\.\d (\d+) })[1]
206
+ return status.to_i
207
+ end
208
+
209
+ def read_headers uri
210
+ headers = Hash.new
211
+ key = nil # allow using the latest key when appending multiline headers
212
+ while header = @socket.gets("\n")
213
+ header.chomp!
214
+ break if header.empty?
215
+ if header =~ /^\s/
216
+ headers[key] << header.strip
217
+ else
218
+ key, val = header.split(": ", 2)
219
+ if key =~ /^Set-Cookie2?$/i #do we dare consider set-cookie2 the same?
220
+ @jar.consume(val, uri)
221
+ else
222
+ headers.store(key.downcase, val)
223
+ end
224
+ end
225
+ end
226
+
227
+ return headers
228
+ end
229
+
230
+ def read_body headers
231
+ body = ""
232
+ if headers["transfer-encoding"] =~ /chunked/i # read chunked body
233
+ while true
234
+ line = @socket.readline
235
+ chunk_len = line.slice(/[0-9a-fA-F]+/).hex
236
+ break if chunk_len == 0
237
+ while chunk_len > 0 # make sure to read the whole chunk
238
+ buf = @socket.read(chunk_len)
239
+ chunk_len -= buf.length
240
+ body << buf
241
+ end
242
+ @socket.read 2 # read the damn linechange
243
+ end
244
+ until (line = @socket.gets) and (line.nil? or line.sub(/\r?\n?/, "").empty?); end # read the chunk footers and the last line
245
+ elsif headers["content-length"]
246
+ clen = headers["content-length"].to_i
247
+ while body.length < clen
248
+ body << @socket.read([clen - body.length, 4096].min)
249
+ end
250
+ else #fallback that'll just consume all the data available
251
+ begin
252
+ while true
253
+ body << @socket.readpartial(4096)
254
+ end
255
+ rescue EOFError
256
+ end
257
+ end
258
+
259
+ return body
260
+ end
261
+
262
+ # Given an HTTP status code, returns whether it signifies a redirect.
263
+ def redirect? status
264
+ status.to_i / 100 == 3
265
+ end
266
+
267
+ # Given an HTTP method, returns whether a body should be read.
268
+ def returns_body? request_method
269
+ not ['HEAD', 'DELETE'].include?(request_method)
270
+ end
271
+
272
+ def follow_redirect location
273
+ @log.debug(location.inspect)
274
+ dest = location.to_uri
275
+ get(dest)
276
+ end
277
+
278
+ def decompress type, body
279
+ case type
280
+ when "gzip"
281
+ return Zlib::GzipReader.new(StringIO.new(body)).read
282
+ when "deflate"
283
+ begin
284
+ return Zlib::Inflate.inflate(body)
285
+ rescue Zlib::DataError # check http://www.ruby-forum.com/topic/136825 for more info
286
+ return Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
287
+ end
288
+ else
289
+ return body
290
+ end
291
+ end
292
+
293
+ def check_socket host, port = 'http'
294
+ connect(host, port) if @socket.nil? or @socket.closed? or @host.nil? or not @host.eql? host or @port.nil? or not @port.eql? port
295
+ end
296
+
297
+ def connect host, port
298
+ @log.debug("Connecting to #{host}:#{port}")
299
+ @socket.close unless @socket.nil? or @socket.closed?
300
+ @socket = TCPSocket.new({:read_timeout => @read_timeout, :open_timeout => @open_timeout}, host, port)
301
+ @host = host
302
+ @port = port
303
+ end
304
+ end
305
+
306
+ class Cookie
307
+ attr_accessor :key, :value, :domain, :path, :expires
308
+
309
+ def initialize key = nil, value = nil
310
+ @key, @value = key, value
311
+ @domain, @path, @expires = nil
312
+ end
313
+
314
+ def parse raw_cookie, uri = nil
315
+ keyval, *attributes = raw_cookie.split(/;\s*/)
316
+ @key, @value = keyval.split("=", 2)
317
+
318
+ attributes.each do |at|
319
+ case at
320
+ when /domain=(.*)/i
321
+ @domain = $1
322
+ when /expires=(.*)/i
323
+ @expires = begin
324
+ Time.parse($1)
325
+ rescue
326
+ nil
327
+ end
328
+ when /path=(.*)/i
329
+ @path = $1
330
+ end
331
+ end
332
+
333
+ @domain = uri.host if @domain.nil? and uri
334
+ @path = "/" if @path.nil? and uri
335
+ @path.sub!(/\/$/, "") if @path #remove the trailing /, because path matching automatically adds it
336
+
337
+ self
338
+ end
339
+
340
+ def same? c
341
+ self.key.eql? c.key and self.domain.eql? c.domain and self.path.eql? c.path
342
+ end
343
+
344
+ def match? uri
345
+ match_domain?(uri.host) and match_path?(uri.path)
346
+ end
347
+
348
+ def expired?
349
+ @expires.is_a?(Time) ? @expires < Time.now : false
350
+ end
351
+
352
+ def match_domain? domain # TODO check if this fully follows the spec
353
+ case @domain
354
+ when /^\d+\.\d+\.\d+\.\d+$/ # ip address
355
+ domain.eql?(@domain)
356
+ when /^\./ # so domain = site.com and subdomains could match @domain to .site.com
357
+ domain =~ /#{Regexp.escape(@domain)}$/i
358
+ else
359
+ domain.downcase.eql?(@domain.downcase)
360
+ end
361
+ end
362
+
363
+ def match_path? path
364
+ path =~ /^#{Regexp.escape(@path)}(?:\/.*)?$/
365
+ end
366
+ end
367
+
368
+ class Jar
369
+ def initialize
370
+ @jar = []
371
+ end
372
+
373
+ def consume raw_cookie, uri = nil
374
+ cookie = Cookie.new.parse(raw_cookie, uri)
375
+ add(cookie)
376
+ end
377
+
378
+ def add c
379
+ i = index(c)
380
+ if i.nil?
381
+ @jar << c
382
+ else
383
+ @jar[i] = c
384
+ end
385
+ end
386
+
387
+ def has_cookies_for? uri
388
+ not cookies_for(uri).empty?
389
+ end
390
+
391
+ def cookies_for uri
392
+ @jar -= @jar.select { |c| c.expired? }
393
+ @jar.select { |c| c.match?(uri) }.map{ |c| "#{c.key}=#{c.value}" }.join("; ")
394
+ end
395
+
396
+ def index c
397
+ @jar.each do |cookie|
398
+ return @jar.index(cookie) if cookie.same? c
399
+ end
400
+
401
+ nil
402
+ end
403
+
404
+ def include? c
405
+ not index(c).nil?
406
+ end
407
+
408
+ def save io
409
+ io << @jar.to_yaml
410
+ io.close
411
+ end
412
+
413
+ def load io
414
+ saved_jar = YAML::load(io)
415
+
416
+ saved_jar.each do |c|
417
+ add(c)
418
+ end
419
+ end
420
+ end
421
+
422
+ module Utils
423
+ # URL-encode a string.
424
+ def self.escape string
425
+ string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
426
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
427
+ end.tr(' ', '+')
428
+ end
429
+
430
+ # URL-decode a string.
431
+ def self.unescape string
432
+ string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
433
+ [$1.delete('%')].pack('H*')
434
+ end
435
+ end
436
+ end
437
+ end
data/rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+ require 'rcov/rcovtask'
5
+
6
+ task :default => [:test]
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.platform = Gem::Platform::RUBY
10
+ s.name = 'wang'
11
+ s.version = "0.01"
12
+ s.summary = "Web Access with No Grief."
13
+ s.authors = ["Kamu", "Joux3"]
14
+ s.email = "mr.kamu@gmail.com"
15
+ s.homepage = "http://github.com/kamu/wang/tree"
16
+ s.requirements << 'none'
17
+ s.require_path = 'lib'
18
+ s.files = FileList["rakefile", "lib/**/*", "test/**/*"]
19
+ s.test_files = FileList["test/wang_test.rb"]
20
+ end
21
+
22
+ Rake::TestTask.new do |t|
23
+ t.test_files = FileList["test/wang_test.rb"]
24
+ t.verbose = true
25
+ end
26
+
27
+ Rake::GemPackageTask.new(spec) do |p|
28
+ p.need_tar = true
29
+ end
30
+
31
+ Rcov::RcovTask.new do |t|
32
+ t.test_files = FileList["test/wang_test.rb"]
33
+ t.verbose = true
34
+ end
data/test/htdigest ADDED
@@ -0,0 +1 @@
1
+ tester:WANG digest HTTP auth test:45485e998d046e343f2b4550520a1ae6
data/test/wang_test.rb ADDED
@@ -0,0 +1,178 @@
1
+ # vim: set noet:
2
+ #
3
+ # WANG - Web Access with No Grief
4
+ # http://github.com/kamu/wang/
5
+
6
+ require 'test/unit'
7
+ require 'wang'
8
+ require 'test/wang_test_server'
9
+
10
+ $test_server = WANGTestServer.new
11
+ Thread.new do
12
+ $test_server.start
13
+ end
14
+
15
+ class WangTest < Test::Unit::TestCase
16
+ def setup
17
+ @client = WANG.new(:debug => true, :read_timeout=>0.9) # small read timeout shouldn't fail local tests
18
+ end
19
+
20
+ def test_returns_success
21
+ status, headers, body = @client.get('http://localhost:8080/')
22
+ assert_equal 200, status
23
+ end
24
+
25
+ def test_compression
26
+ status, headers, body = @client.get('http://localhost:8080/test_inflate')
27
+ assert_equal 'deflate', headers['content-encoding']
28
+ assert_equal 'inflate worked', body
29
+
30
+ status, headers, body = @client.get('http://localhost:8080/test_gzip')
31
+ assert_equal 'gzip', headers['content-encoding']
32
+ assert_equal 'gzip worked', body
33
+ end
34
+
35
+ def test_returns_headers_hash
36
+ status, headers, body = @client.get('http://localhost:8080/')
37
+ assert headers.is_a?(Hash)
38
+ assert_equal 'text/html', headers['content-type']
39
+ end
40
+
41
+ def test_supports_custom_ports
42
+ assert_nothing_raised { @client.get('http://localhost:8080/redirect') }
43
+ end
44
+
45
+ def test_follows_redirect
46
+ status, headers, body = @client.get('http://localhost:8080/redirect')
47
+ assert_equal 'http://localhost:8080/redirect', @client.responses.first.uri.to_s
48
+ assert_equal 'http://localhost:8080/redirected/elsewhere', @client.responses.last.uri.to_s
49
+ assert_equal 200, status
50
+ assert_equal "The redirect worked.\n", body
51
+ end
52
+
53
+ def test_posts_data_using_query_string
54
+ status, headers, body = @client.post('http://localhost:8080/canhaspost', 'mopar=dongs&joux3=king')
55
+ assert_equal 200, status
56
+ assert body =~ /mopar => dongs/
57
+ assert body =~ /joux3 => king/
58
+ end
59
+
60
+ def test_posts_data_using_hash
61
+ status, headers, body = @client.post('http://localhost:8080/canhaspost', {'mopar'=>'dongs', 'joux3'=>'king'})
62
+ assert_equal 200, status
63
+ assert body =~ /mopar => dongs/
64
+ assert body =~ /joux3 => king/
65
+ end
66
+
67
+ def test_head_http_method
68
+ status, headers, body = @client.head('http://localhost:8080/whatmethod')
69
+ assert_equal 'HEAD', headers['method-used']
70
+ end
71
+
72
+ def test_get_http_method
73
+ status, headers, body = @client.get('http://localhost:8080/whatmethod')
74
+ assert_equal 'GET', headers['method-used']
75
+ end
76
+
77
+ def test_post_http_method
78
+ status, headers, body = @client.post('http://localhost:8080/whatmethod', {'some' => 'query'})
79
+ assert_equal 'POST', headers['method-used']
80
+ end
81
+
82
+ def test_put_http_method
83
+ status, headers, body = @client.put('http://localhost:8080/whatmethod', {'some' => 'query'})
84
+ assert_equal 'PUT', headers['method-used']
85
+ end
86
+
87
+ def test_delete_http_method
88
+ status, headers, body = @client.delete('http://localhost:8080/whatmethod')
89
+ assert_equal 'DELETE', headers['method-used']
90
+ end
91
+
92
+ def test_head_requests_return_nil_body
93
+ status, headers, body = @client.head('http://localhost:8080/')
94
+ assert_equal nil, body
95
+ end
96
+
97
+ def test_delete_requests_return_nil_body
98
+ status, headers, body = @client.delete('http://localhost:8080/whatmethod')
99
+ assert_equal nil, body
100
+ end
101
+
102
+ def test_read_timeout
103
+ assert_raise Timeout::Error do
104
+ @client.get('http://localhost:8080/timeout')
105
+ end
106
+ end
107
+
108
+ def test_infinite_redirection
109
+ @client.get('http://localhost:8080/infiniteredirect')
110
+ end
111
+
112
+ def test_basic_auth
113
+ status, headers, body = @client.get('http://localhost:8080/basic_auth')
114
+ assert_equal status, 401 #unauthorized!
115
+
116
+ @client.set_auth('tester', 'wanger')
117
+ status, headers, body = @client.get('http://localhost:8080/basic_auth')
118
+ assert_equal status, 200
119
+ assert body =~ /auth successful/
120
+ end
121
+
122
+ def test_cookie_domain
123
+ cookie = WANG::Cookie.new.parse("x=y; domain=cat.com")
124
+ assert cookie.match_domain?("cat.com")
125
+ assert !cookie.match_domain?("cat.com.au")
126
+ assert !cookie.match_domain?("cat,com") #just incase we are using the regexp '.'
127
+ assert !cookie.match_domain?("dogeatcat.com")
128
+ assert !cookie.match_domain?("ihatethat.cat.com")
129
+
130
+ cookie = WANG::Cookie.new.parse("x=y; domain=.cat.com")
131
+ assert cookie.match_domain?("blah.cat.com")
132
+ assert !cookie.match_domain?("cat.com") #this is what I read in the spec
133
+ assert !cookie.match_domain?("blah.cat.com.au")
134
+ end
135
+
136
+ def test_cookie_paths
137
+ cookie = WANG::Cookie.new.parse("x=y; path=/lamo/")
138
+ assert cookie.match_path?("/lamo")
139
+ assert !cookie.match_path?("/lamoa")
140
+ assert cookie.match_path?("/lamo/aaa/bb")
141
+ assert !cookie.match_path?("/lam")
142
+ assert !cookie.match_path?("/lam/oo")
143
+ end
144
+
145
+ def test_cookie_setting
146
+ status, headers, body = @client.get('http://localhost:8080/cookie_check')
147
+ assert_equal status, 401 #no cookies set
148
+
149
+ status, headers, body = @client.get('http://localhost:8080/cookie_set')
150
+ assert_equal status, 200
151
+
152
+ status, headers, body = @client.get('http://localhost:8080/cookie_check')
153
+ assert_equal status, 200
154
+
155
+ status, headers, body = @client.get('http://localhost:8080/cookie_set2')
156
+ assert_equal status, 200 #set the same cookie to something else
157
+
158
+ status, headers, body = @client.get('http://localhost:8080/cookie_check')
159
+ assert_equal status, 401 #wrong value because we set it again
160
+
161
+ status, headers, body = @client.get('http://localhost:8080/cookie_check2')
162
+ assert_equal status, 200
163
+ end
164
+
165
+ def test_cookie_expiry
166
+ status, headers, body = @client.get('http://localhost:8080/cookie_set_expired')
167
+ assert_equal status, 200
168
+
169
+ status, headers, body = @client.get('http://localhost:8080/cookie_check')
170
+ assert_equal status, 401 #wang should remove the cookie since it expired an hour ago
171
+
172
+ status, headers, body = @client.get('http://localhost:8080/cookie_set_nonexpired')
173
+ assert_equal status, 200
174
+
175
+ status, headers, body = @client.get('http://localhost:8080/cookie_check')
176
+ assert_equal status, 200 #wang should accept the cookie and send it, because it still has an hour till expiry
177
+ end
178
+ end
@@ -0,0 +1,143 @@
1
+ # vim: set noet:
2
+ #
3
+ # WANG - Web Access with No Grief
4
+ # http://github.com/kamu/wang/
5
+
6
+ require 'webrick'
7
+ require 'stringio'
8
+ require 'zlib'
9
+
10
+ class HTTPMethodServlet < WEBrick::HTTPServlet::AbstractServlet
11
+ %w(HEAD GET POST PUT DELETE).each do |http_method|
12
+ define_method("do_#{http_method}") do |request, response|
13
+ response['Method-Used'] = request.request_method
14
+ unless request.request_method == 'DELETE'
15
+ response.body = 'Some meaningless body'
16
+ end
17
+ raise WEBrick::HTTPStatus::OK
18
+ end
19
+ end
20
+ end
21
+
22
+ class WANGTestServer
23
+ class BlackHole
24
+ def <<(x)
25
+ end
26
+ end
27
+
28
+ def initialize
29
+ log = WEBrick::Log.new(BlackHole.new)
30
+ @server = WEBrick::HTTPServer.new(:Port => 8080, :AccessLog => [], :Logger => log)
31
+
32
+ @server.mount_proc('/redirect') do |request, response|
33
+ response['Location'] = '/redirected/elsewhere'
34
+ raise WEBrick::HTTPStatus::MovedPermanently
35
+ end
36
+ @server.mount_proc('/redirected/elsewhere') do |request, response|
37
+ response.body = "The redirect worked.\n"
38
+ response['Content-Type'] = 'text/plain'
39
+ raise WEBrick::HTTPStatus::OK
40
+ end
41
+ @server.mount_proc('/') do |request, response|
42
+ response.body = "<html><head><title>hi</title></head><body><p>Hullo!</p></body></html>"
43
+ response['Content-Type'] = 'text/html'
44
+ raise WEBrick::HTTPStatus::OK
45
+ end
46
+ @server.mount_proc('/canhaspost') do |request, response|
47
+ response.body = request.query.map do |key, val|
48
+ "#{key} => #{val}"
49
+ end.join("\n")
50
+ response['Content-Type'] = 'text/plain'
51
+ raise WEBrick::HTTPStatus::OK
52
+ end
53
+ @server.mount_proc('/timeout') do |request, response|
54
+ sleep 1
55
+ raise WEBrick::HTTPStatus::OK
56
+ end
57
+ @server.mount_proc('/infiniteredirect') do |request, response|
58
+ response['Location'] = '/infiniteredirect'
59
+ raise WEBrick::HTTPStatus::TemporaryRedirect
60
+ end
61
+ @server.mount_proc('/basic_auth') do |request, response|
62
+ WEBrick::HTTPAuth.basic_auth(request, response, "WANG basic HTTP auth test") {|user, pass|
63
+ user == 'tester' && pass == 'wanger'
64
+ }
65
+ response.body = "Basic auth successful!"
66
+ raise WEBrick::HTTPStatus::OK
67
+ end
68
+ @htdigest = WEBrick::HTTPAuth::Htdigest.new('test/htdigest')
69
+ @authenticator = WEBrick::HTTPAuth::DigestAuth.new(
70
+ :UserDB => @htdigest,
71
+ :Realm => 'WANG digest HTTP auth test'
72
+ )
73
+ @server.mount_proc('/digest_auth') do |request, response|
74
+ @authenticator.authenticate(request, response)
75
+ response.body = "Digest auth successful!"
76
+ raise WEBrick::HTTPStatus::OK
77
+ end
78
+ @server.mount('/whatmethod', HTTPMethodServlet)
79
+ @server.mount_proc('/cookie_check') do |request, response|
80
+ request.cookies.each do |cookie|
81
+ raise WEBrick::HTTPStatus::OK if cookie.name.eql?("wangcookie") and cookie.value.eql?("getyerhandoffmywang")
82
+ end
83
+ raise WEBrick::HTTPStatus::Unauthorized
84
+ end
85
+ @server.mount_proc('/cookie_set') do |request, response|
86
+ cookie = WEBrick::Cookie.new('wangcookie', 'getyerhandoffmywang')
87
+ response.cookies << cookie
88
+ raise WEBrick::HTTPStatus::OK
89
+ end
90
+ @server.mount_proc('/cookie_set2') do |request, response|
91
+ cookie = WEBrick::Cookie.new('wangcookie', 'touchmywang')
92
+ response.cookies << cookie
93
+ raise WEBrick::HTTPStatus::OK
94
+ end
95
+ @server.mount_proc('/cookie_check2') do |request, response|
96
+ request.cookies.each do |cookie|
97
+ raise WEBrick::HTTPStatus::OK if cookie.name.eql?("wangcookie") and cookie.value.eql?("touchmywang")
98
+ end
99
+ raise WEBrick::HTTPStatus::Unauthorized
100
+ end
101
+ @server.mount_proc('/cookie_set_expired') do |request, response|
102
+ cookie = WEBrick::Cookie.new('wangcookie', 'getyerhandoffmywang')
103
+ cookie.expires = (Time.now - 60*60*1)
104
+ response.cookies << cookie
105
+ raise WEBrick::HTTPStatus::OK
106
+ end
107
+
108
+ @server.mount_proc('/cookie_set_nonexpired') do |request, response|
109
+ cookie = WEBrick::Cookie.new('wangcookie', 'getyerhandoffmywang')
110
+ cookie.expires = (Time.now + 60*60*1)
111
+ response.cookies << cookie
112
+ raise WEBrick::HTTPStatus::OK
113
+ end
114
+ @server.mount_proc('/test_inflate') do |request, response|
115
+ response['content-encoding'] = 'deflate'
116
+ response.body = Zlib::Deflate.deflate("inflate worked")
117
+ raise WEBrick::HTTPStatus::OK
118
+ end
119
+ @server.mount_proc('/test_gzip') do |request, response|
120
+ response['content-encoding'] = 'gzip'
121
+ output = StringIO.open('', 'w')
122
+ gz = Zlib::GzipWriter.new(output)
123
+ gz.write("gzip worked")
124
+ gz.close
125
+ response.body = output.string
126
+ raise WEBrick::HTTPStatus::OK
127
+ end
128
+ end
129
+
130
+ def start
131
+ @server.start
132
+ end
133
+
134
+ def shutdown
135
+ @server.shutdown
136
+ end
137
+ end
138
+
139
+ if __FILE__ == $0
140
+ server = WANGTestServer.new
141
+ trap('INT') { server.shutdown }
142
+ server.start
143
+ end
data/wang.gemspec ADDED
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.platform = Gem::Platform::RUBY
3
+ s.name = 'wang'
4
+ s.version = "0.02"
5
+ s.summary = "Web Access with No Grief."
6
+ s.authors = ["Kamu", "Joux3"]
7
+ s.email = "mr.kamu@gmail.com"
8
+ s.homepage = "http://github.com/kamu/wang/tree"
9
+ s.requirements << 'none'
10
+ s.require_path = 'lib'
11
+ s.files = ["rakefile", "wang.gemspec", "lib/wang.rb", "test/wang_test.rb", "test/wang_test_server.rb", "test/htdigest"]
12
+ s.test_files = ["test/wang_test.rb"]
13
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kamu-wang
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.02"
5
+ platform: ruby
6
+ authors:
7
+ - Kamu
8
+ - Joux3
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-06-05 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email: mr.kamu@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - rakefile
27
+ - wang.gemspec
28
+ - lib/wang.rb
29
+ - test/wang_test.rb
30
+ - test/wang_test_server.rb
31
+ - test/htdigest
32
+ has_rdoc: false
33
+ homepage: http://github.com/kamu/wang/tree
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements:
52
+ - none
53
+ rubyforge_project:
54
+ rubygems_version: 1.0.1
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: Web Access with No Grief.
58
+ test_files:
59
+ - test/wang_test.rb