rest-open-uri 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/CHANGELOG +1 -0
  2. data/lib/rest-open-uri.rb +721 -0
  3. metadata +46 -0
@@ -0,0 +1 @@
1
+ 1.0.0 - 20061221 - Initial release.
@@ -0,0 +1,721 @@
1
+ # This is a hack of (and drop-in replacement for) open-uri that
2
+ # supports entity-bodies, and HTTP methods other than GET. This makes
3
+ # it easy to build clients for REST web services.
4
+
5
+ require 'uri'
6
+ require 'stringio'
7
+ require 'time'
8
+ require 'net/http'
9
+
10
+ module Kernel
11
+ private
12
+ alias open_uri_original_open open # :nodoc:
13
+
14
+ # makes possible to open various resources including URIs.
15
+ # If the first argument respond to `open' method,
16
+ # the method is called with the rest arguments.
17
+ #
18
+ # If the first argument is a string which begins with xxx://,
19
+ # it is parsed by URI.parse. If the parsed object respond to `open' method,
20
+ # the method is called with the rest arguments.
21
+ #
22
+ # Otherwise original open is called.
23
+ #
24
+ # Since open-uri.rb provides URI::HTTP#open, URI::HTTPS#open and
25
+ # URI::FTP#open,
26
+ # Kernel[#.]open can accepts such URIs and strings which begins with
27
+ # http://, https:// and ftp://.
28
+ # In these case, the opened file object is extended by OpenURI::Meta.
29
+ def open(name, *rest, &block) # :doc:
30
+ if name.respond_to?(:open)
31
+ name.open(*rest, &block)
32
+ elsif name.respond_to?(:to_str) &&
33
+ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
34
+ (uri = URI.parse(name)).respond_to?(:open)
35
+ uri.open(*rest, &block)
36
+ else
37
+ open_uri_original_open(name, *rest, &block)
38
+ end
39
+ end
40
+ module_function :open
41
+ end
42
+
43
+ # OpenURI is an easy-to-use wrapper for net/http, net/https and net/ftp.
44
+ #
45
+ #== Example
46
+ #
47
+ # It is possible to open http/https/ftp URL as usual like opening a file:
48
+ #
49
+ # open("http://www.ruby-lang.org/") {|f|
50
+ # f.each_line {|line| p line}
51
+ # }
52
+ #
53
+ # The opened file has several methods for meta information as follows since
54
+ # it is extended by OpenURI::Meta.
55
+ #
56
+ # open("http://www.ruby-lang.org/en") {|f|
57
+ # f.each_line {|line| p line}
58
+ # p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
59
+ # p f.content_type # "text/html"
60
+ # p f.charset # "iso-8859-1"
61
+ # p f.content_encoding # []
62
+ # p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
63
+ # }
64
+ #
65
+ # Additional header fields can be specified by an optional hash argument.
66
+ #
67
+ # open("http://www.ruby-lang.org/en/",
68
+ # "User-Agent" => "Ruby/#{RUBY_VERSION}",
69
+ # "From" => "foo@bar.invalid",
70
+ # "Referer" => "http://www.ruby-lang.org/") {|f|
71
+ # # ...
72
+ # }
73
+ #
74
+ # The environment variables such as http_proxy, https_proxy and ftp_proxy
75
+ # are in effect by default. :proxy => nil disables proxy.
76
+ #
77
+ # open("http://www.ruby-lang.org/en/raa.html", :proxy => nil) {|f|
78
+ # # ...
79
+ # }
80
+ #
81
+ # URI objects can be opened in a similar way.
82
+ #
83
+ # uri = URI.parse("http://www.ruby-lang.org/en/")
84
+ # uri.open {|f|
85
+ # # ...
86
+ # }
87
+ #
88
+ # URI objects can be read directly. The returned string is also extended by
89
+ # OpenURI::Meta.
90
+ #
91
+ # str = uri.read
92
+ # p str.base_uri
93
+ #
94
+ # Author:: Tanaka Akira <akr@m17n.org>
95
+
96
+ module OpenURI
97
+ Options = {
98
+ :proxy => true,
99
+ :method => true,
100
+ :progress_proc => true,
101
+ :content_length_proc => true,
102
+ :http_basic_authentication => true,
103
+ :body => true
104
+ }
105
+
106
+ # xxx: I'd like a better way of representing this trivial mapping.
107
+ Methods = {
108
+ :copy => Net::HTTP::Copy,
109
+ :delete => Net::HTTP::Delete,
110
+ :get => Net::HTTP::Get,
111
+ :head => Net::HTTP::Head,
112
+ :lock => Net::HTTP::Lock,
113
+ :mkcol => Net::HTTP::Mkcol,
114
+ :options => Net::HTTP::Options,
115
+ :post => Net::HTTP::Post,
116
+ :propfind => Net::HTTP::Propfind,
117
+ :proppatch => Net::HTTP::Proppatch,
118
+ :put => Net::HTTP::Put,
119
+ :trace => Net::HTTP::Trace,
120
+ :unlock => Net::HTTP::Unlock,
121
+ }
122
+
123
+
124
+ def OpenURI.check_options(options) # :nodoc:
125
+ options.each {|k, v|
126
+ next unless Symbol === k
127
+ unless Options.include? k
128
+ raise ArgumentError, "unrecognized option: #{k}"
129
+ end
130
+ }
131
+
132
+ m = options[:method]
133
+ if m && !Methods[m]
134
+ raise ArgumentError, "unrecognized HTTP method symbol: #{m}"
135
+ end
136
+ end
137
+
138
+ def OpenURI.scan_open_optional_arguments(*rest) # :nodoc:
139
+ if !rest.empty? && (String === rest.first || Integer === rest.first)
140
+ mode = rest.shift
141
+ if !rest.empty? && Integer === rest.first
142
+ perm = rest.shift
143
+ end
144
+ end
145
+ return mode, perm, rest
146
+ end
147
+
148
+ def OpenURI.open_uri(name, *rest) # :nodoc:
149
+ uri = URI::Generic === name ? name : URI.parse(name)
150
+ mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest)
151
+ options = rest.shift if !rest.empty? && Hash === rest.first
152
+ raise ArgumentError.new("extra arguments") if !rest.empty?
153
+ options ||= {}
154
+ OpenURI.check_options(options)
155
+
156
+ unless mode == nil ||
157
+ mode == 'r' || mode == 'rb' ||
158
+ mode == File::RDONLY
159
+ raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
160
+ end
161
+
162
+ io = open_loop(uri, options)
163
+ if block_given?
164
+ begin
165
+ yield io
166
+ ensure
167
+ io.close
168
+ end
169
+ else
170
+ io
171
+ end
172
+ end
173
+
174
+ def OpenURI.open_loop(uri, options) # :nodoc:
175
+ case opt_proxy = options.fetch(:proxy, true)
176
+ when true
177
+ find_proxy = lambda {|u| u.find_proxy}
178
+ when nil, false
179
+ find_proxy = lambda {|u| nil}
180
+ when String
181
+ opt_proxy = URI.parse(opt_proxy)
182
+ find_proxy = lambda {|u| opt_proxy}
183
+ when URI::Generic
184
+ find_proxy = lambda {|u| opt_proxy}
185
+ else
186
+ raise ArgumentError.new("Invalid proxy option: #{opt_proxy}")
187
+ end
188
+
189
+ uri_set = {}
190
+ buf = nil
191
+ while true
192
+ redirect = catch(:open_uri_redirect) {
193
+ buf = Buffer.new
194
+ uri.buffer_open(buf, find_proxy.call(uri), options)
195
+ nil
196
+ }
197
+ if redirect
198
+ if redirect.relative?
199
+ # Although it violates RFC2616, Location: field may have relative
200
+ # URI. It is converted to absolute URI using uri as a base URI.
201
+ redirect = uri + redirect
202
+ end
203
+ unless OpenURI.redirectable?(uri, redirect)
204
+ raise "redirection forbidden: #{uri} -> #{redirect}"
205
+ end
206
+ if options.include? :http_basic_authentication
207
+ # send authentication only for the URI directly specified.
208
+ options = options.dup
209
+ options.delete :http_basic_authentication
210
+ end
211
+ uri = redirect
212
+ raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s
213
+ uri_set[uri.to_s] = true
214
+ else
215
+ break
216
+ end
217
+ end
218
+ io = buf.io
219
+ io.base_uri = uri
220
+ io
221
+ end
222
+
223
+ def OpenURI.redirectable?(uri1, uri2) # :nodoc:
224
+ # This test is intended to forbid a redirection from http://... to
225
+ # file:///etc/passwd.
226
+ # However this is ad hoc. It should be extensible/configurable.
227
+ uri1.scheme.downcase == uri2.scheme.downcase ||
228
+ (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme)
229
+ end
230
+
231
+ def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
232
+ if proxy
233
+ raise "Non-HTTP proxy URI: #{proxy}" if proxy.class != URI::HTTP
234
+ end
235
+
236
+ if target.userinfo && "1.9.0" <= RUBY_VERSION
237
+ # don't raise for 1.8 because compatibility.
238
+ raise ArgumentError, "userinfo not supported. [RFC3986]"
239
+ end
240
+
241
+ klass = Net::HTTP
242
+ if URI::HTTP === target
243
+ # HTTP or HTTPS
244
+ if proxy
245
+ klass = Net::HTTP::Proxy(proxy.host, proxy.port)
246
+ end
247
+ target_host = target.host
248
+ target_port = target.port
249
+ request_uri = target.request_uri
250
+ else
251
+ # FTP over HTTP proxy
252
+ target_host = proxy.host
253
+ target_port = proxy.port
254
+ request_uri = target.to_s
255
+ end
256
+
257
+ http = klass.new(target_host, target_port)
258
+ if target.class == URI::HTTPS
259
+ require 'net/https'
260
+ http.use_ssl = true
261
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
262
+ store = OpenSSL::X509::Store.new
263
+ store.set_default_paths
264
+ http.cert_store = store
265
+ end
266
+
267
+ header = {}
268
+ options.each {|k, v| header[k] = v if String === k }
269
+
270
+ resp = nil
271
+ http.start {
272
+ if target.class == URI::HTTPS
273
+ # xxx: information hiding violation
274
+ sock = http.instance_variable_get(:@socket)
275
+ if sock.respond_to?(:io)
276
+ sock = sock.io # 1.9
277
+ else
278
+ sock = sock.instance_variable_get(:@socket) # 1.8
279
+ end
280
+ sock.post_connection_check(target_host)
281
+ end
282
+
283
+ methodclass = Methods[options[:method] || :get]
284
+ req = methodclass.new(request_uri, header)
285
+ req.body = options[:body] if methodclass::REQUEST_HAS_BODY
286
+
287
+ if options.include? :http_basic_authentication
288
+ user, pass = options[:http_basic_authentication]
289
+ req.basic_auth user, pass
290
+ end
291
+ http.request(req) {|response|
292
+ resp = response
293
+ if options[:content_length_proc] && Net::HTTPSuccess === resp
294
+ if resp.key?('Content-Length')
295
+ options[:content_length_proc].call(resp['Content-Length'].to_i)
296
+ else
297
+ options[:content_length_proc].call(nil)
298
+ end
299
+ end
300
+ resp.read_body {|str|
301
+ buf << str
302
+ if options[:progress_proc] && Net::HTTPSuccess === resp
303
+ options[:progress_proc].call(buf.size)
304
+ end
305
+ }
306
+ }
307
+ }
308
+ io = buf.io
309
+ io.rewind
310
+ io.status = [resp.code, resp.message]
311
+ resp.each {|name,value| buf.io.meta_add_field name, value }
312
+ case resp
313
+ when Net::HTTPSuccess
314
+ when Net::HTTPMovedPermanently, # 301
315
+ Net::HTTPFound, # 302
316
+ Net::HTTPSeeOther, # 303
317
+ Net::HTTPTemporaryRedirect # 307
318
+ throw :open_uri_redirect, URI.parse(resp['location'])
319
+ else
320
+ raise OpenURI::HTTPError.new(io.status.join(' '), io)
321
+ end
322
+ end
323
+
324
+ class HTTPError < StandardError
325
+ def initialize(message, io)
326
+ super(message)
327
+ @io = io
328
+ end
329
+ attr_reader :io
330
+ end
331
+
332
+ class Buffer # :nodoc:
333
+ def initialize
334
+ @io = StringIO.new
335
+ @size = 0
336
+ end
337
+ attr_reader :size
338
+
339
+ StringMax = 10240
340
+ def <<(str)
341
+ @io << str
342
+ @size += str.length
343
+ if StringIO === @io && StringMax < @size
344
+ require 'tempfile'
345
+ io = Tempfile.new('open-uri')
346
+ io.binmode
347
+ Meta.init io, @io if @io.respond_to? :meta
348
+ io << @io.string
349
+ @io = io
350
+ end
351
+ end
352
+
353
+ def io
354
+ Meta.init @io unless @io.respond_to? :meta
355
+ @io
356
+ end
357
+ end
358
+
359
+ # Mixin for holding meta-information.
360
+ module Meta
361
+ def Meta.init(obj, src=nil) # :nodoc:
362
+ obj.extend Meta
363
+ obj.instance_eval {
364
+ @base_uri = nil
365
+ @meta = {}
366
+ }
367
+ if src
368
+ obj.status = src.status
369
+ obj.base_uri = src.base_uri
370
+ src.meta.each {|name, value|
371
+ obj.meta_add_field(name, value)
372
+ }
373
+ end
374
+ end
375
+
376
+ # returns an Array which consists status code and message.
377
+ attr_accessor :status
378
+
379
+ # returns a URI which is base of relative URIs in the data.
380
+ # It may differ from the URI supplied by a user because redirection.
381
+ attr_accessor :base_uri
382
+
383
+ # returns a Hash which represents header fields.
384
+ # The Hash keys are downcased for canonicalization.
385
+ attr_reader :meta
386
+
387
+ def meta_add_field(name, value) # :nodoc:
388
+ @meta[name.downcase] = value
389
+ end
390
+
391
+ # returns a Time which represents Last-Modified field.
392
+ def last_modified
393
+ if v = @meta['last-modified']
394
+ Time.httpdate(v)
395
+ else
396
+ nil
397
+ end
398
+ end
399
+
400
+ RE_LWS = /[\r\n\t ]+/n
401
+ RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n
402
+ RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n
403
+ RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n
404
+
405
+ def content_type_parse # :nodoc:
406
+ v = @meta['content-type']
407
+ # The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045.
408
+ if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v
409
+ type = $1.downcase
410
+ subtype = $2.downcase
411
+ parameters = []
412
+ $3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval|
413
+ val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval
414
+ parameters << [att.downcase, val]
415
+ }
416
+ ["#{type}/#{subtype}", *parameters]
417
+ else
418
+ nil
419
+ end
420
+ end
421
+
422
+ # returns "type/subtype" which is MIME Content-Type.
423
+ # It is downcased for canonicalization.
424
+ # Content-Type parameters are stripped.
425
+ def content_type
426
+ type, *parameters = content_type_parse
427
+ type || 'application/octet-stream'
428
+ end
429
+
430
+ # returns a charset parameter in Content-Type field.
431
+ # It is downcased for canonicalization.
432
+ #
433
+ # If charset parameter is not given but a block is given,
434
+ # the block is called and its result is returned.
435
+ # It can be used to guess charset.
436
+ #
437
+ # If charset parameter and block is not given,
438
+ # nil is returned except text type in HTTP.
439
+ # In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1.
440
+ def charset
441
+ type, *parameters = content_type_parse
442
+ if pair = parameters.assoc('charset')
443
+ pair.last.downcase
444
+ elsif block_given?
445
+ yield
446
+ elsif type && %r{\Atext/} =~ type &&
447
+ @base_uri && /\Ahttp\z/i =~ @base_uri.scheme
448
+ "iso-8859-1" # RFC2616 3.7.1
449
+ else
450
+ nil
451
+ end
452
+ end
453
+
454
+ # returns a list of encodings in Content-Encoding field
455
+ # as an Array of String.
456
+ # The encodings are downcased for canonicalization.
457
+ def content_encoding
458
+ v = @meta['content-encoding']
459
+ if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v
460
+ v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase}
461
+ else
462
+ []
463
+ end
464
+ end
465
+ end
466
+
467
+ # Mixin for HTTP and FTP URIs.
468
+ module OpenRead
469
+ # OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
470
+ #
471
+ # OpenURI::OpenRead#open takes optional 3 arguments as:
472
+ # OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
473
+ #
474
+ # `mode', `perm' is same as Kernel#open.
475
+ #
476
+ # However, `mode' must be read mode because OpenURI::OpenRead#open doesn't
477
+ # support write mode (yet).
478
+ # Also `perm' is just ignored because it is meaningful only for file
479
+ # creation.
480
+ #
481
+ # `options' must be a hash.
482
+ #
483
+ # Each pairs which key is a string in the hash specify a extra header
484
+ # field for HTTP.
485
+ # I.e. it is ignored for FTP without HTTP proxy.
486
+ #
487
+ # The hash may include other options which key is a symbol:
488
+ #
489
+ # [:proxy]
490
+ # Synopsis:
491
+ # :proxy => "http://proxy.foo.com:8000/"
492
+ # :proxy => URI.parse("http://proxy.foo.com:8000/")
493
+ # :proxy => true
494
+ # :proxy => false
495
+ # :proxy => nil
496
+ #
497
+ # If :proxy option is specified, the value should be String, URI,
498
+ # boolean or nil.
499
+ # When String or URI is given, it is treated as proxy URI.
500
+ # When true is given or the option itself is not specified,
501
+ # environment variable `scheme_proxy' is examined.
502
+ # `scheme' is replaced by `http', `https' or `ftp'.
503
+ # When false or nil is given, the environment variables are ignored and
504
+ # connection will be made to a server directly.
505
+ #
506
+ # [:http_basic_authentication]
507
+ # Synopsis:
508
+ # :http_basic_authentication=>[user, password]
509
+ #
510
+ # If :http_basic_authentication is specified,
511
+ # the value should be an array which contains 2 strings:
512
+ # username and password.
513
+ # It is used for HTTP Basic authentication defined by RFC 2617.
514
+ #
515
+ # [:content_length_proc]
516
+ # Synopsis:
517
+ # :content_length_proc => lambda {|content_length| ... }
518
+ #
519
+ # If :content_length_proc option is specified, the option value procedure
520
+ # is called before actual transfer is started.
521
+ # It takes one argument which is expected content length in bytes.
522
+ #
523
+ # If two or more transfer is done by HTTP redirection, the procedure
524
+ # is called only one for a last transfer.
525
+ #
526
+ # When expected content length is unknown, the procedure is called with
527
+ # nil.
528
+ # It is happen when HTTP response has no Content-Length header.
529
+ #
530
+ # [:progress_proc]
531
+ # Synopsis:
532
+ # :progress_proc => lambda {|size| ...}
533
+ #
534
+ # If :progress_proc option is specified, the proc is called with one
535
+ # argument each time when `open' gets content fragment from network.
536
+ # The argument `size' `size' is a accumulated transfered size in bytes.
537
+ #
538
+ # If two or more transfer is done by HTTP redirection, the procedure
539
+ # is called only one for a last transfer.
540
+ #
541
+ # :progress_proc and :content_length_proc are intended to be used for
542
+ # progress bar.
543
+ # For example, it can be implemented as follows using Ruby/ProgressBar.
544
+ #
545
+ # pbar = nil
546
+ # open("http://...",
547
+ # :content_length_proc => lambda {|t|
548
+ # if t && 0 < t
549
+ # pbar = ProgressBar.new("...", t)
550
+ # pbar.file_transfer_mode
551
+ # end
552
+ # },
553
+ # :progress_proc => lambda {|s|
554
+ # pbar.set s if pbar
555
+ # }) {|f| ... }
556
+ #
557
+ # OpenURI::OpenRead#open returns an IO like object if block is not given.
558
+ # Otherwise it yields the IO object and return the value of the block.
559
+ # The IO object is extended with OpenURI::Meta.
560
+ def open(*rest, &block)
561
+ OpenURI.open_uri(self, *rest, &block)
562
+ end
563
+
564
+ # OpenURI::OpenRead#read([options]) reads a content referenced by self and
565
+ # returns the content as string.
566
+ # The string is extended with OpenURI::Meta.
567
+ # The argument `options' is same as OpenURI::OpenRead#open.
568
+ def read(options={})
569
+ self.open(options) {|f|
570
+ str = f.read
571
+ Meta.init str, f
572
+ str
573
+ }
574
+ end
575
+ end
576
+ end
577
+
578
+ module URI
579
+ class Generic
580
+ # returns a proxy URI.
581
+ # The proxy URI is obtained from environment variables such as http_proxy,
582
+ # ftp_proxy, no_proxy, etc.
583
+ # If there is no proper proxy, nil is returned.
584
+ #
585
+ # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.)
586
+ # are examined too.
587
+ #
588
+ # But http_proxy and HTTP_PROXY is treated specially under CGI environment.
589
+ # It's because HTTP_PROXY may be set by Proxy: header.
590
+ # So HTTP_PROXY is not used.
591
+ # http_proxy is not used too if the variable is case insensitive.
592
+ # CGI_HTTP_PROXY can be used instead.
593
+ def find_proxy
594
+ name = self.scheme.downcase + '_proxy'
595
+ proxy_uri = nil
596
+ if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI?
597
+ # HTTP_PROXY conflicts with *_proxy for proxy settings and
598
+ # HTTP_* for header information in CGI.
599
+ # So it should be careful to use it.
600
+ pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k }
601
+ case pairs.length
602
+ when 0 # no proxy setting anyway.
603
+ proxy_uri = nil
604
+ when 1
605
+ k, v = pairs.shift
606
+ if k == 'http_proxy' && ENV[k.upcase] == nil
607
+ # http_proxy is safe to use because ENV is case sensitive.
608
+ proxy_uri = ENV[name]
609
+ else
610
+ proxy_uri = nil
611
+ end
612
+ else # http_proxy is safe to use because ENV is case sensitive.
613
+ proxy_uri = ENV[name]
614
+ end
615
+ if !proxy_uri
616
+ # Use CGI_HTTP_PROXY. cf. libwww-perl.
617
+ proxy_uri = ENV["CGI_#{name.upcase}"]
618
+ end
619
+ elsif name == 'http_proxy'
620
+ unless proxy_uri = ENV[name]
621
+ if proxy_uri = ENV[name.upcase]
622
+ warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.'
623
+ end
624
+ end
625
+ else
626
+ proxy_uri = ENV[name] || ENV[name.upcase]
627
+ end
628
+
629
+ if proxy_uri && self.host
630
+ require 'socket'
631
+ begin
632
+ addr = IPSocket.getaddress(self.host)
633
+ proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr
634
+ rescue SocketError
635
+ end
636
+ end
637
+
638
+ if proxy_uri
639
+ proxy_uri = URI.parse(proxy_uri)
640
+ name = 'no_proxy'
641
+ if no_proxy = ENV[name] || ENV[name.upcase]
642
+ no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port|
643
+ if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host &&
644
+ (!port || self.port == port.to_i)
645
+ proxy_uri = nil
646
+ break
647
+ end
648
+ }
649
+ end
650
+ proxy_uri
651
+ else
652
+ nil
653
+ end
654
+ end
655
+ end
656
+
657
+ class HTTP
658
+ def buffer_open(buf, proxy, options) # :nodoc:
659
+ OpenURI.open_http(buf, self, proxy, options)
660
+ end
661
+
662
+ include OpenURI::OpenRead
663
+ end
664
+
665
+ class FTP
666
+ def buffer_open(buf, proxy, options) # :nodoc:
667
+ if proxy
668
+ OpenURI.open_http(buf, self, proxy, options)
669
+ return
670
+ end
671
+ require 'net/ftp'
672
+
673
+ directories = self.path.split(%r{/}, -1)
674
+ directories.shift if directories[0] == '' # strip a field before leading slash
675
+ directories.each {|d|
676
+ d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
677
+ }
678
+ unless filename = directories.pop
679
+ raise ArgumentError, "no filename: #{self.inspect}"
680
+ end
681
+ directories.each {|d|
682
+ if /[\r\n]/ =~ d
683
+ raise ArgumentError, "invalid directory: #{d.inspect}"
684
+ end
685
+ }
686
+ if /[\r\n]/ =~ filename
687
+ raise ArgumentError, "invalid filename: #{filename.inspect}"
688
+ end
689
+ typecode = self.typecode
690
+ if typecode && /\A[aid]\z/ !~ typecode
691
+ raise ArgumentError, "invalid typecode: #{typecode.inspect}"
692
+ end
693
+
694
+ # The access sequence is defined by RFC 1738
695
+ ftp = Net::FTP.open(self.host)
696
+ # todo: extract user/passwd from .netrc.
697
+ user = 'anonymous'
698
+ passwd = nil
699
+ user, passwd = self.userinfo.split(/:/) if self.userinfo
700
+ ftp.login(user, passwd)
701
+ directories.each {|cwd|
702
+ ftp.voidcmd("CWD #{cwd}")
703
+ }
704
+ if typecode
705
+ # xxx: typecode D is not handled.
706
+ ftp.voidcmd("TYPE #{typecode.upcase}")
707
+ end
708
+ if options[:content_length_proc]
709
+ options[:content_length_proc].call(ftp.size(filename))
710
+ end
711
+ ftp.retrbinary("RETR #{filename}", 4096) { |str|
712
+ buf << str
713
+ options[:progress_proc].call(buf.size) if options[:progress_proc]
714
+ }
715
+ ftp.close
716
+ buf.io.rewind
717
+ end
718
+
719
+ include OpenURI::OpenRead
720
+ end
721
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: rest-open-uri
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-12-21 00:00:00 -05:00
8
+ summary: A drop-in replacement for open-uri for use in REST clients.
9
+ require_paths:
10
+ - lib
11
+ email: leonardr@segfault.org
12
+ homepage: http://rubyforge.org/tracker/?func=detail&aid=6321&group_id=426&atid=1700
13
+ rubyforge_project:
14
+ description: rest-open-uri is a hack of, and drop-in replacement for, open-uri. It supports all standard HTTP methods (not just GET) and allows you to send an entity-body. This gem will be deprecated if and when equivalent functionality makes it into core Ruby.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Leonard Richardson
30
+ files:
31
+ - lib/rest-open-uri.rb
32
+ - CHANGELOG
33
+ test_files: []
34
+
35
+ rdoc_options: []
36
+
37
+ extra_rdoc_files:
38
+ - CHANGELOG
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ requirements: []
44
+
45
+ dependencies: []
46
+