mechanize 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mechanize might be problematic. Click here for more details.
- data/README +15 -0
- data/examples/rubyforge.rb +13 -0
- data/lib/mechanize.rb +447 -0
- data/lib/mechanize/net-overrides/net/http.rb +2107 -0
- data/lib/mechanize/net-overrides/net/https.rb +171 -0
- data/lib/mechanize/net-overrides/net/protocol.rb +380 -0
- data/lib/mechanize/parsing.rb +200 -0
- data/mechanize.gemspec +22 -0
- metadata +59 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= $RCSfile: https.rb,v $ -- SSL/TLS enhancement for Net::HTTP.
|
4
|
+
|
5
|
+
== Info
|
6
|
+
'OpenSSL for Ruby 2' project
|
7
|
+
Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
|
8
|
+
All rights reserved.
|
9
|
+
|
10
|
+
== Licence
|
11
|
+
This program is licenced under the same licence as Ruby.
|
12
|
+
(See the file 'LICENCE'.)
|
13
|
+
|
14
|
+
== Requirements
|
15
|
+
This program requires Net 1.2.0 or higher version.
|
16
|
+
You can get it from RAA or Ruby's CVS repository.
|
17
|
+
|
18
|
+
== Version
|
19
|
+
$Id: https.rb,v 1.3 2004/12/20 05:46:45 gotoyuzo Exp $
|
20
|
+
|
21
|
+
2001-11-06: Contiributed to Ruby/OpenSSL project.
|
22
|
+
2004-03-06: Some code is merged in to net/http.
|
23
|
+
|
24
|
+
== Example
|
25
|
+
|
26
|
+
Here is a simple HTTP client:
|
27
|
+
|
28
|
+
require 'net/http'
|
29
|
+
require 'uri'
|
30
|
+
|
31
|
+
uri = URI.parse(ARGV[0] || 'http://localhost/')
|
32
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
33
|
+
http.start {
|
34
|
+
http.request_get(uri.path) {|res|
|
35
|
+
print res.body
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
It can be replaced by the following code:
|
40
|
+
|
41
|
+
require 'net/https'
|
42
|
+
require 'uri'
|
43
|
+
|
44
|
+
uri = URI.parse(ARGV[0] || 'https://localhost/')
|
45
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
46
|
+
http.use_ssl = true if uri.scheme == "https" # enable SSL/TLS
|
47
|
+
http.start {
|
48
|
+
http.request_get(uri.path) {|res|
|
49
|
+
print res.body
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
== class Net::HTTP
|
54
|
+
|
55
|
+
=== Instance Methods
|
56
|
+
|
57
|
+
: use_ssl?
|
58
|
+
returns true if use SSL/TLS with HTTP.
|
59
|
+
|
60
|
+
: use_ssl=((|true_or_false|))
|
61
|
+
sets use_ssl.
|
62
|
+
|
63
|
+
: peer_cert
|
64
|
+
return the X.509 certificates the server presented.
|
65
|
+
|
66
|
+
: key, key=((|key|))
|
67
|
+
Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
|
68
|
+
(This method is appeared in Michal Rokos's OpenSSL extention.)
|
69
|
+
|
70
|
+
: cert, cert=((|cert|))
|
71
|
+
Sets an OpenSSL::X509::Certificate object as client certificate
|
72
|
+
(This method is appeared in Michal Rokos's OpenSSL extention).
|
73
|
+
|
74
|
+
: ca_file, ca_file=((|path|))
|
75
|
+
Sets path of a CA certification file in PEM format.
|
76
|
+
The file can contrain several CA certificats.
|
77
|
+
|
78
|
+
: ca_path, ca_path=((|path|))
|
79
|
+
Sets path of a CA certification directory containing certifications
|
80
|
+
in PEM format.
|
81
|
+
|
82
|
+
: verify_mode, verify_mode=((|mode|))
|
83
|
+
Sets the flags for server the certification verification at
|
84
|
+
begining of SSL/TLS session.
|
85
|
+
OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable.
|
86
|
+
|
87
|
+
: verify_callback, verify_callback=((|proc|))
|
88
|
+
Sets the verify callback for the server certification verification.
|
89
|
+
|
90
|
+
: verify_depth, verify_depth=((|num|))
|
91
|
+
Sets the maximum depth for the certificate chain verification.
|
92
|
+
|
93
|
+
: cert_store, cert_store=((|store|))
|
94
|
+
Sets the X509::Store to verify peer certificate.
|
95
|
+
|
96
|
+
: ssl_timeout, ssl_timeout=((|sec|))
|
97
|
+
Sets the SSL timeout seconds.
|
98
|
+
|
99
|
+
=end
|
100
|
+
|
101
|
+
require 'net/http'
|
102
|
+
require 'openssl'
|
103
|
+
|
104
|
+
module Net
|
105
|
+
|
106
|
+
class HTTP
|
107
|
+
remove_method :use_ssl?
|
108
|
+
def use_ssl?
|
109
|
+
@use_ssl
|
110
|
+
end
|
111
|
+
|
112
|
+
alias use_ssl use_ssl? # for backward compatibility
|
113
|
+
|
114
|
+
# Turn on/off SSL.
|
115
|
+
# This flag must be set before starting session.
|
116
|
+
# If you change use_ssl value after session started,
|
117
|
+
# a Net::HTTP object raises IOError.
|
118
|
+
def use_ssl=(flag)
|
119
|
+
flag = (flag ? true : false)
|
120
|
+
raise IOError, "use_ssl value changed, but session already started" \
|
121
|
+
if started? and @use_ssl != flag
|
122
|
+
if flag and not @ssl_context
|
123
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
124
|
+
end
|
125
|
+
@use_ssl = flag
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.ssl_context_accessor(name)
|
129
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
130
|
+
def #{name}
|
131
|
+
return nil unless @ssl_context
|
132
|
+
@ssl_context.#{name}
|
133
|
+
end
|
134
|
+
|
135
|
+
def #{name}=(val)
|
136
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new
|
137
|
+
@ssl_context.#{name} = val
|
138
|
+
end
|
139
|
+
End
|
140
|
+
end
|
141
|
+
|
142
|
+
ssl_context_accessor :key
|
143
|
+
ssl_context_accessor :cert
|
144
|
+
ssl_context_accessor :ca_file
|
145
|
+
ssl_context_accessor :ca_path
|
146
|
+
ssl_context_accessor :verify_mode
|
147
|
+
ssl_context_accessor :verify_callback
|
148
|
+
ssl_context_accessor :verify_depth
|
149
|
+
ssl_context_accessor :cert_store
|
150
|
+
|
151
|
+
def ssl_timeout
|
152
|
+
return nil unless @ssl_context
|
153
|
+
@ssl_context.timeout
|
154
|
+
end
|
155
|
+
|
156
|
+
def ssl_timeout=(sec)
|
157
|
+
raise ArgumentError, 'Net::HTTP#ssl_timeout= called but use_ssl=false' \
|
158
|
+
unless use_ssl?
|
159
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new
|
160
|
+
@ssl_context.timeout = sec
|
161
|
+
end
|
162
|
+
|
163
|
+
alias timeout= ssl_timeout= # for backward compatibility
|
164
|
+
|
165
|
+
def peer_cert
|
166
|
+
return nil if not use_ssl? or not @socket
|
167
|
+
@socket.io.peer_cert
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
#
|
2
|
+
# = net/protocol.rb
|
3
|
+
#
|
4
|
+
#--
|
5
|
+
# Copyright (c) 1999-2004 Yukihiro Matsumoto
|
6
|
+
# Copyright (c) 1999-2004 Minero Aoki
|
7
|
+
#
|
8
|
+
# written and maintained by Minero Aoki <aamine@loveruby.net>
|
9
|
+
#
|
10
|
+
# This program is free software. You can re-distribute and/or
|
11
|
+
# modify this program under the same terms as Ruby itself,
|
12
|
+
# Ruby Distribute License or GNU General Public License.
|
13
|
+
#
|
14
|
+
# $Id: protocol.rb,v 1.78 2004/08/18 14:44:41 aamine Exp $
|
15
|
+
#++
|
16
|
+
#
|
17
|
+
# WARNING: This file is going to remove.
|
18
|
+
# Do not rely on the implementation written in this file.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'socket'
|
22
|
+
require 'timeout'
|
23
|
+
|
24
|
+
module Net # :nodoc:
|
25
|
+
|
26
|
+
class Protocol #:nodoc: internal use only
|
27
|
+
private
|
28
|
+
def Protocol.protocol_param(name, val)
|
29
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
30
|
+
def #{name}
|
31
|
+
#{val}
|
32
|
+
end
|
33
|
+
End
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class ProtocolError < StandardError; end
|
39
|
+
class ProtoSyntaxError < ProtocolError; end
|
40
|
+
class ProtoFatalError < ProtocolError; end
|
41
|
+
class ProtoUnknownError < ProtocolError; end
|
42
|
+
class ProtoServerError < ProtocolError; end
|
43
|
+
class ProtoAuthError < ProtocolError; end
|
44
|
+
class ProtoCommandError < ProtocolError; end
|
45
|
+
class ProtoRetriableError < ProtocolError; end
|
46
|
+
ProtocRetryError = ProtoRetriableError
|
47
|
+
|
48
|
+
|
49
|
+
class BufferedIO #:nodoc: internal use only
|
50
|
+
def initialize(io)
|
51
|
+
@io = io
|
52
|
+
@read_timeout = 60
|
53
|
+
@debug_output = nil
|
54
|
+
@rbuf = ''
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :io
|
58
|
+
attr_accessor :read_timeout
|
59
|
+
attr_accessor :debug_output
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"#<#{self.class} io=#{@io}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
def closed?
|
66
|
+
@io.closed?
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
@io.close
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Read
|
75
|
+
#
|
76
|
+
|
77
|
+
public
|
78
|
+
|
79
|
+
def read(len, dest = '', ignore_eof = false)
|
80
|
+
LOG "reading #{len} bytes..."
|
81
|
+
read_bytes = 0
|
82
|
+
begin
|
83
|
+
while read_bytes + @rbuf.size < len
|
84
|
+
dest << (s = rbuf_consume(@rbuf.size))
|
85
|
+
read_bytes += s.size
|
86
|
+
rbuf_fill
|
87
|
+
end
|
88
|
+
dest << (s = rbuf_consume(len - read_bytes))
|
89
|
+
read_bytes += s.size
|
90
|
+
rescue EOFError
|
91
|
+
raise unless ignore_eof
|
92
|
+
end
|
93
|
+
LOG "read #{read_bytes} bytes"
|
94
|
+
dest
|
95
|
+
end
|
96
|
+
|
97
|
+
def read_all(dest = '')
|
98
|
+
LOG 'reading all...'
|
99
|
+
read_bytes = 0
|
100
|
+
begin
|
101
|
+
while true
|
102
|
+
dest << (s = rbuf_consume(@rbuf.size))
|
103
|
+
read_bytes += s.size
|
104
|
+
rbuf_fill
|
105
|
+
end
|
106
|
+
rescue EOFError
|
107
|
+
;
|
108
|
+
end
|
109
|
+
LOG "read #{read_bytes} bytes"
|
110
|
+
dest
|
111
|
+
end
|
112
|
+
|
113
|
+
def readuntil(terminator, ignore_eof = false)
|
114
|
+
begin
|
115
|
+
until idx = @rbuf.index(terminator)
|
116
|
+
rbuf_fill
|
117
|
+
end
|
118
|
+
return rbuf_consume(idx + terminator.size)
|
119
|
+
rescue EOFError
|
120
|
+
raise unless ignore_eof
|
121
|
+
return rbuf_consume(@rbuf.size)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def readline
|
126
|
+
readuntil("\n").chop
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def rbuf_fill
|
132
|
+
timeout(@read_timeout) {
|
133
|
+
@rbuf << @io.sysread(1024)
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def rbuf_consume(len)
|
138
|
+
s = @rbuf.slice!(0, len)
|
139
|
+
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
|
140
|
+
s
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Write
|
145
|
+
#
|
146
|
+
|
147
|
+
public
|
148
|
+
|
149
|
+
def write(str)
|
150
|
+
writing {
|
151
|
+
write0 str
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def writeline(str)
|
156
|
+
writing {
|
157
|
+
write0 str + "\r\n"
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def writing
|
164
|
+
@written_bytes = 0
|
165
|
+
@debug_output << '<- ' if @debug_output
|
166
|
+
yield
|
167
|
+
@debug_output << "\n" if @debug_output
|
168
|
+
bytes = @written_bytes
|
169
|
+
@written_bytes = nil
|
170
|
+
bytes
|
171
|
+
end
|
172
|
+
|
173
|
+
def write0(str)
|
174
|
+
@debug_output << str.dump if @debug_output
|
175
|
+
len = @io.write(str)
|
176
|
+
@written_bytes += len
|
177
|
+
len
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Logging
|
182
|
+
#
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def LOG_off
|
187
|
+
@save_debug_out = @debug_output
|
188
|
+
@debug_output = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def LOG_on
|
192
|
+
@debug_output = @save_debug_out
|
193
|
+
end
|
194
|
+
|
195
|
+
def LOG(msg)
|
196
|
+
return unless @debug_output
|
197
|
+
@debug_output << msg + "\n"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
class InternetMessageIO < BufferedIO #:nodoc: internal use only
|
203
|
+
def initialize(io)
|
204
|
+
super
|
205
|
+
@wbuf = nil
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Read
|
210
|
+
#
|
211
|
+
|
212
|
+
def each_message_chunk
|
213
|
+
LOG 'reading message...'
|
214
|
+
LOG_off()
|
215
|
+
read_bytes = 0
|
216
|
+
while (line = readuntil("\r\n")) != ".\r\n"
|
217
|
+
read_bytes += line.size
|
218
|
+
yield line.sub(/\A\./, '')
|
219
|
+
end
|
220
|
+
LOG_on()
|
221
|
+
LOG "read message (#{read_bytes} bytes)"
|
222
|
+
end
|
223
|
+
|
224
|
+
# *library private* (cannot handle 'break')
|
225
|
+
def each_list_item
|
226
|
+
while (str = readuntil("\r\n")) != ".\r\n"
|
227
|
+
yield str.chop
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def write_message_0(src)
|
232
|
+
prev = @written_bytes
|
233
|
+
each_crlf_line(src) do |line|
|
234
|
+
write0 line.sub(/\A\./, '..')
|
235
|
+
end
|
236
|
+
@written_bytes - prev
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Write
|
241
|
+
#
|
242
|
+
|
243
|
+
def write_message(src)
|
244
|
+
LOG "writing message from #{src.class}"
|
245
|
+
LOG_off()
|
246
|
+
len = writing {
|
247
|
+
using_each_crlf_line {
|
248
|
+
write_message_0 src
|
249
|
+
}
|
250
|
+
}
|
251
|
+
LOG_on()
|
252
|
+
LOG "wrote #{len} bytes"
|
253
|
+
len
|
254
|
+
end
|
255
|
+
|
256
|
+
def write_message_by_block(&block)
|
257
|
+
LOG 'writing message from block'
|
258
|
+
LOG_off()
|
259
|
+
len = writing {
|
260
|
+
using_each_crlf_line {
|
261
|
+
begin
|
262
|
+
block.call(WriteAdapter.new(self, :write_message_0))
|
263
|
+
rescue LocalJumpError
|
264
|
+
# allow `break' from writer block
|
265
|
+
end
|
266
|
+
}
|
267
|
+
}
|
268
|
+
LOG_on()
|
269
|
+
LOG "wrote #{len} bytes"
|
270
|
+
len
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def using_each_crlf_line
|
276
|
+
@wbuf = ''
|
277
|
+
yield
|
278
|
+
if not @wbuf.empty? # unterminated last line
|
279
|
+
write0 @wbuf.chomp + "\r\n"
|
280
|
+
elsif @written_bytes == 0 # empty src
|
281
|
+
write0 "\r\n"
|
282
|
+
end
|
283
|
+
write0 ".\r\n"
|
284
|
+
@wbuf = nil
|
285
|
+
end
|
286
|
+
|
287
|
+
def each_crlf_line(src)
|
288
|
+
buffer_filling(@wbuf, src) do
|
289
|
+
while line = @wbuf.slice!(/\A.*(?:\n|\r\n|\r(?!\z))/n)
|
290
|
+
yield line.chomp("\n") + "\r\n"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def buffer_filling(buf, src)
|
296
|
+
case src
|
297
|
+
when String # for speeding up.
|
298
|
+
0.step(src.size - 1, 1024) do |i|
|
299
|
+
buf << src[i, 1024]
|
300
|
+
yield
|
301
|
+
end
|
302
|
+
when File # for speeding up.
|
303
|
+
while s = src.read(1024)
|
304
|
+
buf << s
|
305
|
+
yield
|
306
|
+
end
|
307
|
+
else # generic reader
|
308
|
+
src.each do |s|
|
309
|
+
buf << s
|
310
|
+
yield if buf.size > 1024
|
311
|
+
end
|
312
|
+
yield unless buf.empty?
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
#
|
319
|
+
# The writer adapter class
|
320
|
+
#
|
321
|
+
class WriteAdapter
|
322
|
+
def initialize(socket, method)
|
323
|
+
@socket = socket
|
324
|
+
@method_id = method
|
325
|
+
end
|
326
|
+
|
327
|
+
def inspect
|
328
|
+
"#<#{self.class} socket=#{@socket.inspect}>"
|
329
|
+
end
|
330
|
+
|
331
|
+
def write(str)
|
332
|
+
@socket.__send__(@method_id, str)
|
333
|
+
end
|
334
|
+
|
335
|
+
alias print write
|
336
|
+
|
337
|
+
def <<(str)
|
338
|
+
write str
|
339
|
+
self
|
340
|
+
end
|
341
|
+
|
342
|
+
def puts(str = '')
|
343
|
+
write str.chomp("\n") + "\n"
|
344
|
+
end
|
345
|
+
|
346
|
+
def printf(*args)
|
347
|
+
write sprintf(*args)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
class ReadAdapter #:nodoc: internal use only
|
353
|
+
def initialize(block)
|
354
|
+
@block = block
|
355
|
+
end
|
356
|
+
|
357
|
+
def inspect
|
358
|
+
"#<#{self.class}>"
|
359
|
+
end
|
360
|
+
|
361
|
+
def <<(str)
|
362
|
+
call_block(str, &@block) if @block
|
363
|
+
end
|
364
|
+
|
365
|
+
private
|
366
|
+
|
367
|
+
# This method is needed because @block must be called by yield,
|
368
|
+
# not Proc#call. You can see difference when using `break' in
|
369
|
+
# the block.
|
370
|
+
def call_block(str)
|
371
|
+
yield str
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
module NetPrivate #:nodoc: obsolete
|
377
|
+
Socket = ::Net::InternetMessageIO
|
378
|
+
end
|
379
|
+
|
380
|
+
end # module Net
|