rubyforge 0.4.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,542 +0,0 @@
1
- # HTTP - HTTP container.
2
- # Copyright (C) 2001, 2002, 2003, 2005 NAKAMURA, Hiroshi.
3
- #
4
- # This module is copyrighted free software by NAKAMURA, Hiroshi.
5
- # You can redistribute it and/or modify it under the same term as Ruby.
6
-
7
- require 'uri'
8
- require 'time'
9
-
10
- module HTTP
11
-
12
-
13
- module Status
14
- OK = 200
15
- MOVED_PERMANENTLY = 301
16
- FOUND = 302
17
- SEE_OTHER = 303
18
- TEMPORARY_REDIRECT = MOVED_TEMPORARILY = 307
19
- BAD_REQUEST = 400
20
- INTERNAL = 500
21
-
22
- def self.redirect?(status)
23
- [
24
- MOVED_PERMANENTLY, FOUND, SEE_OTHER,
25
- TEMPORARY_REDIRECT, MOVED_TEMPORARILY
26
- ].include?(status)
27
- end
28
- end
29
-
30
-
31
- class Error < StandardError; end
32
- class BadResponseError < Error; end
33
-
34
- class << self
35
- def http_date(a_time)
36
- a_time.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")
37
- end
38
-
39
- ProtocolVersionRegexp = Regexp.new('^(?:HTTP/|)(\d+)\.(\d+)$')
40
- def keep_alive_enabled?(version)
41
- ProtocolVersionRegexp =~ version
42
- if !($1 and $2)
43
- false
44
- elsif $1.to_i > 1
45
- true
46
- elsif $1.to_i == 1 and $2.to_i >= 1
47
- true
48
- else
49
- false
50
- end
51
- end
52
- end
53
-
54
-
55
- # HTTP::Message -- HTTP message.
56
- #
57
- # DESCRIPTION
58
- # A class that describes 1 HTTP request / response message.
59
- #
60
- class Message
61
- CRLF = "\r\n"
62
-
63
- # HTTP::Message::Headers -- HTTP message header.
64
- #
65
- # DESCRIPTION
66
- # A class that describes header part of HTTP message.
67
- #
68
- class Headers
69
- # HTTP version string in a HTTP header.
70
- attr_accessor :http_version
71
- # Content-type.
72
- attr_accessor :body_type
73
- # Charset.
74
- attr_accessor :body_charset
75
- # Size of body.
76
- attr_reader :body_size
77
- # A milestone of body.
78
- attr_accessor :body_date
79
- # Chunked or not.
80
- attr_reader :chunked
81
- # Request method.
82
- attr_reader :request_method
83
- # Requested URI.
84
- attr_reader :request_uri
85
- # HTTP status reason phrase.
86
- attr_accessor :reason_phrase
87
-
88
- StatusCodeMap = {
89
- Status::OK => 'OK',
90
- Status::MOVED_PERMANENTLY => 'Moved Permanently',
91
- Status::FOUND => 'Found',
92
- Status::SEE_OTHER => 'See Other',
93
- Status::TEMPORARY_REDIRECT => 'Temporary Redirect',
94
- Status::MOVED_TEMPORARILY => 'Temporary Redirect',
95
- Status::BAD_REQUEST => 'Bad Request',
96
- Status::INTERNAL => 'Internal Server Error',
97
- }
98
-
99
- CharsetMap = {
100
- 'NONE' => 'us-ascii',
101
- 'EUC' => 'euc-jp',
102
- 'SJIS' => 'shift_jis',
103
- 'UTF8' => 'utf-8',
104
- }
105
-
106
- # SYNOPSIS
107
- # HTTP::Message.new
108
- #
109
- # ARGS
110
- # N/A
111
- #
112
- # DESCRIPTION
113
- # Create a instance of HTTP request or HTTP response. Specify
114
- # status_code for HTTP response.
115
- #
116
- def initialize
117
- @is_request = nil # true, false and nil
118
- @http_version = 'HTTP/1.1'
119
- @body_type = nil
120
- @body_charset = nil
121
- @body_size = nil
122
- @body_date = nil
123
- @header_item = []
124
- @chunked = false
125
- @response_status_code = nil
126
- @reason_phrase = nil
127
- @request_method = nil
128
- @request_uri = nil
129
- @request_query = nil
130
- @request_via_proxy = nil
131
- end
132
-
133
- def init_request(method, uri, query = nil, via_proxy = nil)
134
- @is_request = true
135
- @request_method = method
136
- @request_uri = if uri.is_a?(URI)
137
- uri
138
- else
139
- URI.parse(uri.to_s)
140
- end
141
- @request_query = create_query_uri(@request_uri, query)
142
- @request_via_proxy = via_proxy
143
- end
144
-
145
- def init_response(status_code)
146
- @is_request = false
147
- self.response_status_code = status_code
148
- end
149
-
150
- attr_accessor :request_via_proxy
151
-
152
- attr_reader :response_status_code
153
- def response_status_code=(status_code)
154
- @response_status_code = status_code
155
- @reason_phrase = StatusCodeMap[@response_status_code]
156
- end
157
-
158
- def contenttype
159
- self['content-type'][0]
160
- end
161
-
162
- def contenttype=(contenttype)
163
- self['content-type'] = contenttype
164
- end
165
-
166
- # body_size == nil means that the body is_a? IO
167
- def body_size=(body_size)
168
- @body_size = body_size
169
- if @body_size
170
- @chunked = false
171
- else
172
- @chunked = true
173
- end
174
- end
175
-
176
- def dump(dev = '')
177
- set_header
178
- if @is_request
179
- dev << request_line
180
- else
181
- dev << response_status_line
182
- end
183
- dev << @header_item.collect { |key, value|
184
- dump_line("#{ key }: #{ value }")
185
- }.join
186
- dev
187
- end
188
-
189
- def set(key, value)
190
- @header_item.push([key, value])
191
- end
192
-
193
- def get(key = nil)
194
- if !key
195
- @header_item
196
- else
197
- @header_item.find_all { |pair| pair[0].upcase == key.upcase }
198
- end
199
- end
200
-
201
- def []=(key, value)
202
- set(key, value)
203
- end
204
-
205
- def [](key)
206
- get(key).collect { |item| item[1] }
207
- end
208
-
209
- private
210
-
211
- def request_line
212
- path = if @request_via_proxy
213
- if @request_uri.port
214
- "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ @request_query }"
215
- else
216
- "#{ @request_uri.scheme }://#{ @request_uri.host }#{ @request_query }"
217
- end
218
- else
219
- @request_query
220
- end
221
- dump_line("#{ @request_method } #{ path } #{ @http_version }")
222
- end
223
-
224
- def response_status_line
225
- if defined?(Apache)
226
- dump_line("#{ @http_version } #{ response_status_code } #{ @reason_phrase }")
227
- else
228
- dump_line("Status: #{ response_status_code } #{ @reason_phrase }")
229
- end
230
- end
231
-
232
- def set_header
233
- if defined?(Apache)
234
- set('Date', HTTP.http_date(Time.now))
235
- end
236
-
237
- keep_alive = HTTP.keep_alive_enabled?(@http_version)
238
- set('Connection', 'close') unless keep_alive
239
-
240
- if @chunked
241
- set('Transfer-Encoding', 'chunked')
242
- else
243
- if keep_alive or @body_size != 0
244
- set('Content-Length', @body_size.to_s)
245
- end
246
- end
247
-
248
- if @body_date
249
- set('Last-Modified', HTTP.http_date(@body_date))
250
- end
251
-
252
- if @is_request == true
253
- if @http_version >= 'HTTP/1.1'
254
- if @request_uri.port == @request_uri.default_port
255
- set('Host', "#{@request_uri.host}")
256
- else
257
- set('Host', "#{@request_uri.host}:#{@request_uri.port}")
258
- end
259
- end
260
- elsif @is_request == false
261
- set('Content-Type', "#{ @body_type || 'text/html' }; charset=#{ CharsetMap[@body_charset || $KCODE] }")
262
- end
263
- end
264
-
265
- def dump_line(str)
266
- str + CRLF
267
- end
268
-
269
- def create_query_uri(uri, query)
270
- path = uri.path.dup
271
- path = '/' if path.empty?
272
- query_str = nil
273
- if uri.query
274
- query_str = uri.query
275
- end
276
- if query
277
- if query_str
278
- query_str << '&' << Message.create_query_part_str(query)
279
- else
280
- query_str = Message.create_query_part_str(query)
281
- end
282
- end
283
- if query_str
284
- path << '?' << query_str
285
- end
286
- path
287
- end
288
- end
289
-
290
- class Body
291
- attr_accessor :type, :charset, :date, :chunk_size
292
-
293
- def initialize(body = nil, date = nil, type = nil, charset = nil,
294
- boundary = nil)
295
- @body = nil
296
- @boundary = boundary
297
- set_content(body || '', boundary)
298
- @type = type
299
- @charset = charset
300
- @date = date
301
- @chunk_size = 4096
302
- end
303
-
304
- def size
305
- if @body.respond_to?(:read)
306
- nil
307
- else
308
- @body.size
309
- end
310
- end
311
-
312
- def dump(dev = '')
313
- if @body.respond_to?(:read)
314
- begin
315
- while true
316
- chunk = @body.read(@chunk_size)
317
- break if chunk.nil?
318
- dev << dump_chunk(chunk)
319
- end
320
- rescue EOFError
321
- end
322
- dev << (dump_last_chunk + CRLF)
323
- else
324
- dev << @body
325
- end
326
- dev
327
- end
328
-
329
- def content
330
- @body
331
- end
332
-
333
- def set_content(body, boundary = nil)
334
- if body.respond_to?(:read)
335
- @body = body
336
- elsif boundary
337
- @body = Message.create_query_multipart_str(body, boundary)
338
- else
339
- @body = Message.create_query_part_str(body)
340
- end
341
- end
342
-
343
- private
344
-
345
- def dump_chunk(str)
346
- dump_chunk_size(str.size) << (str + CRLF)
347
- end
348
-
349
- def dump_last_chunk
350
- dump_chunk_size(0)
351
- end
352
-
353
- def dump_chunk_size(size)
354
- sprintf("%x", size) << CRLF
355
- end
356
- end
357
-
358
- def initialize
359
- @body = @header = nil
360
- end
361
-
362
- class << self
363
- alias __new new
364
- undef new
365
- end
366
-
367
- def self.new_request(method, uri, query = nil, body = nil, proxy = nil,
368
- boundary = nil)
369
- m = self.__new
370
- m.header = Headers.new
371
- m.header.init_request(method, uri, query, proxy)
372
- m.body = Body.new(body, nil, nil, nil, boundary)
373
- m
374
- end
375
-
376
- def self.new_response(body = '')
377
- m = self.__new
378
- m.header = Headers.new
379
- m.header.init_response(Status::OK)
380
- m.body = Body.new(body)
381
- m
382
- end
383
-
384
- def dump(dev = '')
385
- sync_header
386
- dev = header.dump(dev)
387
- dev << CRLF
388
- dev = body.dump(dev) if body
389
- dev
390
- end
391
-
392
- def load(str)
393
- buf = str.dup
394
- unless self.header.load(buf)
395
- self.body.load(buf)
396
- end
397
- end
398
-
399
- def header
400
- @header
401
- end
402
-
403
- def header=(header)
404
- @header = header
405
- sync_body
406
- end
407
-
408
- def content
409
- @body.content
410
- end
411
-
412
- def body
413
- @body
414
- end
415
-
416
- def body=(body)
417
- @body = body
418
- sync_header
419
- end
420
-
421
- def status
422
- @header.response_status_code
423
- end
424
-
425
- def status=(status)
426
- @header.response_status_code = status
427
- end
428
-
429
- def version
430
- @header.http_version
431
- end
432
-
433
- def version=(version)
434
- @header.http_version = version
435
- end
436
-
437
- def reason
438
- @header.reason_phrase
439
- end
440
-
441
- def reason=(reason)
442
- @header.reason_phrase = reason
443
- end
444
-
445
- def contenttype
446
- @header.contenttype
447
- end
448
-
449
- def contenttype=(contenttype)
450
- @header.contenttype = contenttype
451
- end
452
-
453
- class << self
454
- def create_query_part_str(query)
455
- if multiparam_query?(query)
456
- escape_query(query)
457
- else
458
- query.to_s
459
- end
460
- end
461
-
462
- def create_query_multipart_str(query, boundary)
463
- if multiparam_query?(query)
464
- query.collect { |attr, value|
465
- value ||= ''
466
- if value.is_a? File
467
- params = {
468
- 'filename' => value.path,
469
- # Creation time is not available from File::Stat
470
- # 'creation-date' => value.ctime.rfc822,
471
- 'modification-date' => value.mtime.rfc822,
472
- 'read-date' => value.atime.rfc822,
473
- }
474
- param_str = params.to_a.collect { |k, v|
475
- "#{k}=\"#{v}\""
476
- }.join("; ")
477
- "--#{boundary}\n" +
478
- %{Content-Disposition: form-data; name="#{attr.to_s}"; #{param_str}\n} +
479
- "Content-Type: #{mime_type(value.path)}\n\n#{value.read}\n"
480
- else
481
- "--#{boundary}\n" +
482
- %{Content-Disposition: form-data; name="#{attr.to_s}"\n} +
483
- "\n#{value.to_s}\n"
484
- end
485
- }.join('') + "--#{boundary}--\n"
486
- else
487
- query.to_s
488
- end
489
- end
490
-
491
- def multiparam_query?(query)
492
- query.is_a?(Array) or query.is_a?(Hash)
493
- end
494
-
495
- def escape_query(query)
496
- query.collect { |attr, value|
497
- escape(attr.to_s) << '=' << escape(value.to_s)
498
- }.join('&')
499
- end
500
-
501
- # from CGI.escape
502
- def escape(str)
503
- str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
504
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
505
- }.tr(' ', '+')
506
- end
507
-
508
- def mime_type(path)
509
- case path
510
- when /.(htm|html)$/
511
- 'text/html'
512
- when /.doc$/
513
- 'application/msword'
514
- else
515
- 'text/plain'
516
- end
517
- end
518
- end
519
-
520
- private
521
-
522
- def sync_header
523
- if @header and @body
524
- @header.body_type = @body.type
525
- @header.body_charset = @body.charset
526
- @header.body_size = @body.size
527
- @header.body_date = @body.date
528
- end
529
- end
530
-
531
- def sync_body
532
- if @header and @body
533
- @body.type = @header.body_type
534
- @body.charset = @header.body_charset
535
- @body.size = @header.body_size
536
- @body.date = @header.body_date
537
- end
538
- end
539
- end
540
-
541
-
542
- end