rubyforge 0.4.5 → 1.0.0

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.
@@ -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