vmc 0.0.8 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +8 -3
  2. data/README.md +83 -0
  3. data/Rakefile +11 -65
  4. data/bin/vmc +3 -2
  5. data/lib/cli/commands/admin.rb +57 -0
  6. data/lib/cli/commands/apps.rb +828 -0
  7. data/lib/cli/commands/base.rb +56 -0
  8. data/lib/cli/commands/misc.rb +99 -0
  9. data/lib/cli/commands/services.rb +84 -0
  10. data/lib/cli/commands/user.rb +60 -0
  11. data/lib/cli/config.rb +109 -0
  12. data/lib/cli/core_ext.rb +119 -0
  13. data/lib/cli/errors.rb +19 -0
  14. data/lib/cli/frameworks.rb +97 -0
  15. data/lib/cli/runner.rb +437 -0
  16. data/lib/cli/services_helper.rb +74 -0
  17. data/lib/cli/usage.rb +94 -0
  18. data/lib/cli/version.rb +5 -0
  19. data/lib/cli/zip_util.rb +61 -0
  20. data/lib/cli.rb +30 -0
  21. data/lib/vmc/client.rb +415 -0
  22. data/lib/vmc/const.rb +19 -0
  23. data/lib/vmc.rb +2 -1589
  24. data/spec/assets/app_info.txt +9 -0
  25. data/spec/assets/app_listings.txt +9 -0
  26. data/spec/assets/bad_create_app.txt +9 -0
  27. data/spec/assets/delete_app.txt +9 -0
  28. data/spec/assets/global_service_listings.txt +9 -0
  29. data/spec/assets/good_create_app.txt +9 -0
  30. data/spec/assets/good_create_service.txt +9 -0
  31. data/spec/assets/info_authenticated.txt +27 -0
  32. data/spec/assets/info_return.txt +15 -0
  33. data/spec/assets/info_return_bad.txt +16 -0
  34. data/spec/assets/login_fail.txt +9 -0
  35. data/spec/assets/login_success.txt +9 -0
  36. data/spec/assets/sample_token.txt +1 -0
  37. data/spec/assets/service_already_exists.txt +9 -0
  38. data/spec/assets/service_listings.txt +9 -0
  39. data/spec/assets/service_not_found.txt +9 -0
  40. data/spec/assets/user_info.txt +9 -0
  41. data/spec/spec_helper.rb +11 -0
  42. data/spec/unit/cli_opts_spec.rb +73 -0
  43. data/spec/unit/client_spec.rb +284 -0
  44. metadata +114 -71
  45. data/README +0 -58
  46. data/lib/parse.rb +0 -719
  47. data/lib/vmc_base.rb +0 -205
  48. data/vendor/gems/httpclient/VERSION +0 -1
  49. data/vendor/gems/httpclient/lib/http-access2/cookie.rb +0 -1
  50. data/vendor/gems/httpclient/lib/http-access2/http.rb +0 -1
  51. data/vendor/gems/httpclient/lib/http-access2.rb +0 -53
  52. data/vendor/gems/httpclient/lib/httpclient/auth.rb +0 -522
  53. data/vendor/gems/httpclient/lib/httpclient/cacert.p7s +0 -1579
  54. data/vendor/gems/httpclient/lib/httpclient/cacert_sha1.p7s +0 -1579
  55. data/vendor/gems/httpclient/lib/httpclient/connection.rb +0 -84
  56. data/vendor/gems/httpclient/lib/httpclient/cookie.rb +0 -562
  57. data/vendor/gems/httpclient/lib/httpclient/http.rb +0 -867
  58. data/vendor/gems/httpclient/lib/httpclient/session.rb +0 -864
  59. data/vendor/gems/httpclient/lib/httpclient/ssl_config.rb +0 -417
  60. data/vendor/gems/httpclient/lib/httpclient/timeout.rb +0 -136
  61. data/vendor/gems/httpclient/lib/httpclient/util.rb +0 -86
  62. data/vendor/gems/httpclient/lib/httpclient.rb +0 -1020
  63. data/vendor/gems/httpclient/lib/tags +0 -908
@@ -1,867 +0,0 @@
1
- # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
- #
4
- # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
- # redistribute it and/or modify it under the same terms of Ruby's license;
6
- # either the dual license version in 2003, or any later version.
7
-
8
-
9
- require 'time'
10
-
11
-
12
- # A namespace module for HTTP Message definitions used by HTTPClient.
13
- module HTTP
14
-
15
-
16
- # Represents HTTP response status code. Defines constants for HTTP response
17
- # and some conditional methods.
18
- module Status
19
- OK = 200
20
- CREATED = 201
21
- ACCEPTED = 202
22
- NON_AUTHORITATIVE_INFORMATION = 203
23
- NO_CONTENT = 204
24
- RESET_CONTENT = 205
25
- PARTIAL_CONTENT = 206
26
- MOVED_PERMANENTLY = 301
27
- FOUND = 302
28
- SEE_OTHER = 303
29
- TEMPORARY_REDIRECT = MOVED_TEMPORARILY = 307
30
- BAD_REQUEST = 400
31
- UNAUTHORIZED = 401
32
- PROXY_AUTHENTICATE_REQUIRED = 407
33
- INTERNAL = 500
34
-
35
- # Status codes for successful HTTP response.
36
- SUCCESSFUL_STATUS = [
37
- OK, CREATED, ACCEPTED,
38
- NON_AUTHORITATIVE_INFORMATION, NO_CONTENT,
39
- RESET_CONTENT, PARTIAL_CONTENT
40
- ]
41
-
42
- # Status codes which is a redirect.
43
- REDIRECT_STATUS = [
44
- MOVED_PERMANENTLY, FOUND, SEE_OTHER,
45
- TEMPORARY_REDIRECT, MOVED_TEMPORARILY
46
- ]
47
-
48
- # Returns true if the given status represents successful HTTP response.
49
- # See also SUCCESSFUL_STATUS.
50
- def self.successful?(status)
51
- SUCCESSFUL_STATUS.include?(status)
52
- end
53
-
54
- # Returns true if the given status is thought to be redirect.
55
- # See also REDIRECT_STATUS.
56
- def self.redirect?(status)
57
- REDIRECT_STATUS.include?(status)
58
- end
59
- end
60
-
61
-
62
- # Represents a HTTP message. A message is for a request or a response.
63
- #
64
- # Request message is generated from given parameters internally so users
65
- # don't need to care about it. Response message is the instance that
66
- # methods of HTTPClient returns so users need to know how to extract
67
- # HTTP response data from Message.
68
- #
69
- # Some attributes are only for a request or a response, not both.
70
- #
71
- # == How to use HTTP response message
72
- #
73
- # 1. Gets response message body.
74
- #
75
- # res = clnt.get(url)
76
- # p res.content #=> String
77
- #
78
- # 2. Gets response status code.
79
- #
80
- # res = clnt.get(url)
81
- # p res.status #=> 200, 501, etc. (Integer)
82
- #
83
- # 3. Gets response header.
84
- #
85
- # res = clnt.get(url)
86
- # res.header['set-cookie'].each do |value|
87
- # p value
88
- # end
89
- # assert_equal(1, res.header['last-modified'].size)
90
- # p res.header['last-modified'].first
91
- #
92
- class Message
93
-
94
- CRLF = "\r\n"
95
-
96
- # Represents HTTP message header.
97
- class Headers
98
- # HTTP version in a HTTP header. Float.
99
- attr_accessor :http_version
100
- # Size of body. nil when size is unknown (e.g. chunked response).
101
- attr_reader :body_size
102
- # Request/Response is chunked or not.
103
- attr_accessor :chunked
104
-
105
- # Request only. Requested method.
106
- attr_reader :request_method
107
- # Request only. Requested URI.
108
- attr_accessor :request_uri
109
- # Request only. Requested query.
110
- attr_accessor :request_query
111
- # Request only. Requested via proxy or not.
112
- attr_accessor :request_via_proxy
113
-
114
- # Response only. HTTP status
115
- attr_reader :status_code
116
- # Response only. HTTP status reason phrase.
117
- attr_accessor :reason_phrase
118
-
119
- # Used for dumping response.
120
- attr_accessor :body_type # :nodoc:
121
- # Used for dumping response.
122
- attr_accessor :body_charset # :nodoc:
123
- # Used for dumping response.
124
- attr_accessor :body_date # :nodoc:
125
-
126
- # HTTP response status code to reason phrase mapping definition.
127
- STATUS_CODE_MAP = {
128
- Status::OK => 'OK',
129
- Status::CREATED => "Created",
130
- Status::NON_AUTHORITATIVE_INFORMATION => "Non-Authoritative Information",
131
- Status::NO_CONTENT => "No Content",
132
- Status::RESET_CONTENT => "Reset Content",
133
- Status::PARTIAL_CONTENT => "Partial Content",
134
- Status::MOVED_PERMANENTLY => 'Moved Permanently',
135
- Status::FOUND => 'Found',
136
- Status::SEE_OTHER => 'See Other',
137
- Status::TEMPORARY_REDIRECT => 'Temporary Redirect',
138
- Status::MOVED_TEMPORARILY => 'Temporary Redirect',
139
- Status::BAD_REQUEST => 'Bad Request',
140
- Status::INTERNAL => 'Internal Server Error',
141
- }
142
-
143
- # $KCODE to charset mapping definition.
144
- CHARSET_MAP = {
145
- 'NONE' => 'us-ascii',
146
- 'EUC' => 'euc-jp',
147
- 'SJIS' => 'shift_jis',
148
- 'UTF8' => 'utf-8',
149
- }
150
-
151
- # Creates a Message::Headers. Use init_request, init_response, or
152
- # init_connect_request for acutual initialize.
153
- def initialize
154
- @http_version = 1.1
155
- @body_size = nil
156
- @chunked = false
157
-
158
- @request_method = nil
159
- @request_uri = nil
160
- @request_query = nil
161
- @request_via_proxy = nil
162
-
163
- @status_code = nil
164
- @reason_phrase = nil
165
-
166
- @body_type = nil
167
- @body_charset = nil
168
- @body_date = nil
169
-
170
- @is_request = nil
171
- @header_item = []
172
- @dumped = false
173
- end
174
-
175
- # Initialize this instance as a CONNECT request.
176
- def init_connect_request(uri)
177
- @is_request = true
178
- @request_method = 'CONNECT'
179
- @request_uri = uri
180
- @request_query = nil
181
- @http_version = 1.0
182
- end
183
-
184
- # Placeholder URI object for nil uri.
185
- NIL_URI = URI.parse('http://nil-uri-given/')
186
- # Initialize this instance as a general request.
187
- def init_request(method, uri, query = nil)
188
- @is_request = true
189
- @request_method = method
190
- @request_uri = uri || NIL_URI
191
- @request_query = query
192
- @request_via_proxy = false
193
- end
194
-
195
- # Initialize this instance as a response.
196
- def init_response(status_code)
197
- @is_request = false
198
- self.status_code = status_code
199
- end
200
-
201
- # Sets status code and reason phrase.
202
- def status_code=(status_code)
203
- @status_code = status_code
204
- @reason_phrase = STATUS_CODE_MAP[@status_code]
205
- end
206
-
207
- # Returns 'Content-Type' header value.
208
- def contenttype
209
- self['Content-Type'][0]
210
- end
211
-
212
- # Sets 'Content-Type' header value. Overrides if already exists.
213
- def contenttype=(contenttype)
214
- delete('Content-Type')
215
- self['Content-Type'] = contenttype
216
- end
217
-
218
- # Sets byte size of message body.
219
- # body_size == nil means that the body is_a? IO
220
- def body_size=(body_size)
221
- @body_size = body_size
222
- end
223
-
224
- # Dumps message header part and returns a dumped String.
225
- def dump
226
- set_header
227
- str = nil
228
- if @is_request
229
- str = request_line
230
- else
231
- str = response_status_line
232
- end
233
- str + @header_item.collect { |key, value|
234
- "#{ key }: #{ value }#{ CRLF }"
235
- }.join
236
- end
237
-
238
- # Adds a header. Addition order is preserved.
239
- def add(key, value)
240
- if value.is_a?(Array)
241
- value.each do |v|
242
- @header_item.push([key, v])
243
- end
244
- else
245
- @header_item.push([key, value])
246
- end
247
- end
248
-
249
- # Sets a header.
250
- def set(key, value)
251
- delete(key)
252
- add(key, value)
253
- end
254
-
255
- # Returns an Array of headers for the given key. Each element is a pair
256
- # of key and value. It returns an single element Array even if the only
257
- # one header exists. If nil key given, it returns all headers.
258
- def get(key = nil)
259
- if key.nil?
260
- all
261
- else
262
- key = key.upcase
263
- @header_item.find_all { |k, v| k.upcase == key }
264
- end
265
- end
266
-
267
- # Returns an Array of all headers.
268
- def all
269
- @header_item
270
- end
271
-
272
- # Deletes headers of the given key.
273
- def delete(key)
274
- key = key.upcase
275
- @header_item.delete_if { |k, v| k.upcase == key }
276
- end
277
-
278
- # Adds a header. See set.
279
- def []=(key, value)
280
- set(key, value)
281
- end
282
-
283
- # Returns an Array of header values for the given key.
284
- def [](key)
285
- get(key).collect { |item| item[1] }
286
- end
287
-
288
- private
289
-
290
- def request_line
291
- path = create_query_uri(@request_uri, @request_query)
292
- if @request_via_proxy
293
- path = "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ path }"
294
- end
295
- "#{ @request_method } #{ path } HTTP/#{ @http_version }#{ CRLF }"
296
- end
297
-
298
- def response_status_line
299
- if defined?(Apache)
300
- "HTTP/#{ @http_version } #{ @status_code } #{ @reason_phrase }#{ CRLF }"
301
- else
302
- "Status: #{ @status_code } #{ @reason_phrase }#{ CRLF }"
303
- end
304
- end
305
-
306
- def set_header
307
- if @is_request
308
- set_request_header
309
- else
310
- set_response_header
311
- end
312
- end
313
-
314
- def set_request_header
315
- return if @dumped
316
- @dumped = true
317
- keep_alive = Message.keep_alive_enabled?(@http_version)
318
- if !keep_alive and @request_method != 'CONNECT'
319
- set('Connection', 'close')
320
- end
321
- if @chunked
322
- set('Transfer-Encoding', 'chunked')
323
- elsif @body_size and (keep_alive or @body_size != 0)
324
- set('Content-Length', @body_size.to_s)
325
- end
326
- if @http_version >= 1.1
327
- if @request_uri.port == @request_uri.default_port
328
- # GFE/1.3 dislikes default port number (returns 404)
329
- set('Host', "#{@request_uri.host}")
330
- else
331
- set('Host', "#{@request_uri.host}:#{@request_uri.port}")
332
- end
333
- end
334
- end
335
-
336
- def set_response_header
337
- return if @dumped
338
- @dumped = true
339
- if defined?(Apache) && self['Date'].empty?
340
- set('Date', Time.now.httpdate)
341
- end
342
- keep_alive = Message.keep_alive_enabled?(@http_version)
343
- if @chunked
344
- set('Transfer-Encoding', 'chunked')
345
- else
346
- if keep_alive or @body_size != 0
347
- set('Content-Length', @body_size.to_s)
348
- end
349
- end
350
- if @body_date
351
- set('Last-Modified', @body_date.httpdate)
352
- end
353
- if self['Content-Type'].empty?
354
- set('Content-Type', "#{ @body_type || 'text/html' }; charset=#{ charset_label(@body_charset || $KCODE) }")
355
- end
356
- end
357
-
358
- def charset_label(charset)
359
- CHARSET_MAP[charset] || 'us-ascii'
360
- end
361
-
362
- def create_query_uri(uri, query)
363
- if @request_method == 'CONNECT'
364
- return "#{uri.host}:#{uri.port}"
365
- end
366
- path = uri.path
367
- path = '/' if path.nil? or path.empty?
368
- query_str = nil
369
- if uri.query
370
- query_str = uri.query
371
- end
372
- if query
373
- if query_str
374
- query_str += "&#{Message.create_query_part_str(query)}"
375
- else
376
- query_str = Message.create_query_part_str(query)
377
- end
378
- end
379
- if query_str
380
- path += "?#{query_str}"
381
- end
382
- path
383
- end
384
- end
385
-
386
-
387
- # Represents HTTP message body.
388
- class Body
389
- # Size of body. nil when size is unknown (e.g. chunked response).
390
- attr_reader :size
391
- # maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.
392
- attr_accessor :chunk_size
393
-
394
- # Default value for chunk_size
395
- DEFAULT_CHUNK_SIZE = 1024 * 16
396
-
397
- # Creates a Message::Body. Use init_request or init_response
398
- # for acutual initialize.
399
- def initialize
400
- @body = nil
401
- @size = nil
402
- @positions = nil
403
- @chunk_size = nil
404
- end
405
-
406
- # Initialize this instance as a request.
407
- def init_request(body = nil, boundary = nil)
408
- @boundary = boundary
409
- @positions = {}
410
- set_content(body, boundary)
411
- @chunk_size = DEFAULT_CHUNK_SIZE
412
- end
413
-
414
- # Initialize this instance as a response.
415
- def init_response(body = nil)
416
- @body = body
417
- if @body.respond_to?(:size)
418
- @size = @body.size
419
- else
420
- @size = nil
421
- end
422
- end
423
-
424
- # Dumps message body to given dev.
425
- # dev needs to respond to <<.
426
- #
427
- # Message header must be given as the first argument for performance
428
- # reason. (header is dumped to dev, too)
429
- # If no dev (the second argument) given, this method returns a dumped
430
- # String.
431
- def dump(header = '', dev = '')
432
- if @body.is_a?(Parts)
433
- dev << header
434
- buf = ''
435
- @body.parts.each do |part|
436
- if Message.file?(part)
437
- reset_pos(part)
438
- while !part.read(@chunk_size, buf).nil?
439
- dev << buf
440
- end
441
- else
442
- dev << part
443
- end
444
- end
445
- elsif @body
446
- dev << header + @body
447
- else
448
- dev << header
449
- end
450
- dev
451
- end
452
-
453
- # Dumps message body with chunked encoding to given dev.
454
- # dev needs to respond to <<.
455
- #
456
- # Message header must be given as the first argument for performance
457
- # reason. (header is dumped to dev, too)
458
- # If no dev (the second argument) given, this method returns a dumped
459
- # String.
460
- def dump_chunked(header = '', dev = '')
461
- dev << header
462
- if @body.is_a?(Parts)
463
- @body.parts.each do |part|
464
- if Message.file?(part)
465
- reset_pos(part)
466
- dump_chunks(part, dev)
467
- else
468
- dev << dump_chunk(part)
469
- end
470
- end
471
- dev << (dump_last_chunk + CRLF)
472
- elsif @body
473
- reset_pos(@body)
474
- dump_chunks(@body, dev)
475
- dev << (dump_last_chunk + CRLF)
476
- end
477
- dev
478
- end
479
-
480
- # Returns a message body itself.
481
- def content
482
- @body
483
- end
484
-
485
- private
486
-
487
- def set_content(body, boundary = nil)
488
- if body.respond_to?(:read)
489
- # uses Transfer-Encoding: chunked. bear in mind that server may not
490
- # support it. at least ruby's CGI doesn't.
491
- @body = body
492
- remember_pos(@body)
493
- @size = nil
494
- elsif boundary and Message.multiparam_query?(body)
495
- @body = build_query_multipart_str(body, boundary)
496
- @size = @body.size
497
- else
498
- @body = Message.create_query_part_str(body)
499
- @size = @body.size
500
- end
501
- end
502
-
503
- def remember_pos(io)
504
- # IO may not support it (ex. IO.pipe)
505
- @positions[io] = io.pos rescue nil
506
- end
507
-
508
- def reset_pos(io)
509
- io.pos = @positions[io] if @positions.key?(io)
510
- end
511
-
512
- def dump_chunks(io, dev)
513
- buf = ''
514
- while !io.read(@chunk_size, buf).nil?
515
- dev << dump_chunk(buf)
516
- end
517
- end
518
-
519
- def dump_chunk(str)
520
- dump_chunk_size(str.size) + (str + CRLF)
521
- end
522
-
523
- def dump_last_chunk
524
- dump_chunk_size(0)
525
- end
526
-
527
- def dump_chunk_size(size)
528
- sprintf("%x", size) + CRLF
529
- end
530
-
531
- class Parts
532
- attr_reader :size
533
-
534
- def initialize
535
- @body = []
536
- @size = 0
537
- @as_stream = false
538
- end
539
-
540
- def add(part)
541
- if Message.file?(part)
542
- @as_stream = true
543
- @body << part
544
- if part.respond_to?(:size)
545
- if sz = part.size
546
- @size += sz
547
- else
548
- @size = nil
549
- end
550
- elsif part.respond_to?(:lstat)
551
- @size += part.lstat.size
552
- else
553
- # use chunked upload
554
- @size = nil
555
- end
556
- elsif @body[-1].is_a?(String)
557
- @body[-1] += part.to_s
558
- @size += part.to_s.size if @size
559
- else
560
- @body << part.to_s
561
- @size += part.to_s.size if @size
562
- end
563
- end
564
-
565
- def parts
566
- if @as_stream
567
- @body
568
- else
569
- [@body.join]
570
- end
571
- end
572
- end
573
-
574
- def build_query_multipart_str(query, boundary)
575
- parts = Parts.new
576
- query.each do |attr, value|
577
- value ||= ''
578
- headers = ["--#{boundary}"]
579
- if Message.file?(value)
580
- remember_pos(value)
581
- param_str = params_from_file(value).collect { |k, v|
582
- "#{k}=\"#{v}\""
583
- }.join("; ")
584
- if value.respond_to?(:mime_type)
585
- content_type = value.mime_type
586
- else
587
- content_type = Message.mime_type(value.path)
588
- end
589
- headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
590
- headers << %{Content-Type: #{content_type}}
591
- else
592
- headers << %{Content-Disposition: form-data; name="#{attr}"}
593
- end
594
- parts.add(headers.join(CRLF) + CRLF + CRLF)
595
- parts.add(value)
596
- parts.add(CRLF)
597
- end
598
- parts.add("--#{boundary}--" + CRLF + CRLF) # empty epilogue
599
- parts
600
- end
601
-
602
- def params_from_file(value)
603
- params = {}
604
- params['filename'] = File.basename(value.path || '')
605
- # Creation time is not available from File::Stat
606
- if value.respond_to?(:mtime)
607
- params['modification-date'] = value.mtime.rfc822
608
- end
609
- if value.respond_to?(:atime)
610
- params['read-date'] = value.atime.rfc822
611
- end
612
- params
613
- end
614
- end
615
-
616
-
617
- class << self
618
- private :new
619
-
620
- # Creates a Message instance of 'CONNECT' request.
621
- # 'CONNECT' request does not have Body.
622
- # uri:: an URI that need to connect. Only uri.host and uri.port are used.
623
- def new_connect_request(uri)
624
- m = new
625
- m.header.init_connect_request(uri)
626
- m.header.body_size = nil
627
- m
628
- end
629
-
630
- # Creates a Message instance of general request.
631
- # method:: HTTP method String.
632
- # uri:: an URI object which represents an URL of web resource.
633
- # query:: a Hash or an Array of query part of URL.
634
- # e.g. { "a" => "b" } => 'http://host/part?a=b'
635
- # Give an array to pass multiple value like
636
- # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
637
- # body:: a Hash or an Array of body part.
638
- # e.g. { "a" => "b" } => 'a=b'.
639
- # Give an array to pass multiple value like
640
- # [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
641
- # boundary:: When the boundary given, it is sent as
642
- # a multipart/form-data using this boundary String.
643
- def new_request(method, uri, query = nil, body = nil, boundary = nil)
644
- m = new
645
- m.header.init_request(method, uri, query)
646
- m.body = Body.new
647
- m.body.init_request(body || '', boundary)
648
- if body
649
- m.header.body_size = m.body.size
650
- m.header.chunked = true if m.body.size.nil?
651
- else
652
- m.header.body_size = nil
653
- end
654
- m
655
- end
656
-
657
- # Creates a Message instance of response.
658
- # body:: a String or an IO of response message body.
659
- def new_response(body)
660
- m = new
661
- m.header.init_response(Status::OK)
662
- m.body = Body.new
663
- m.body.init_response(body)
664
- m.header.body_size = m.body.size || 0
665
- m
666
- end
667
-
668
- @@mime_type_handler = nil
669
-
670
- # Sets MIME type handler.
671
- #
672
- # handler must respond to :call with a single argument :path and returns
673
- # a MIME type String e.g. 'text/html'.
674
- # When the handler returns nil or an empty String,
675
- # 'application/octet-stream' is used.
676
- #
677
- # When you set nil to the handler, internal_mime_type is used instead.
678
- # The handler is nil by default.
679
- def mime_type_handler=(handler)
680
- @@mime_type_handler = handler
681
- end
682
-
683
- # Returns MIME type handler.
684
- def mime_type_handler
685
- @@mime_type_handler
686
- end
687
-
688
- # For backward compatibility.
689
- alias set_mime_type_func mime_type_handler=
690
- alias get_mime_type_func mime_type_handler
691
-
692
- def mime_type(path) # :nodoc:
693
- if @@mime_type_handler
694
- res = @@mime_type_handler.call(path)
695
- if !res || res.to_s == ''
696
- return 'application/octet-stream'
697
- else
698
- return res
699
- end
700
- else
701
- internal_mime_type(path)
702
- end
703
- end
704
-
705
- # Default MIME type handler.
706
- # See mime_type_handler=.
707
- def internal_mime_type(path)
708
- case path
709
- when /\.txt$/i
710
- 'text/plain'
711
- when /\.(htm|html)$/i
712
- 'text/html'
713
- when /\.doc$/i
714
- 'application/msword'
715
- when /\.png$/i
716
- 'image/png'
717
- when /\.gif$/i
718
- 'image/gif'
719
- when /\.(jpg|jpeg)$/i
720
- 'image/jpeg'
721
- else
722
- 'application/octet-stream'
723
- end
724
- end
725
-
726
- # Returns true if the given HTTP version allows keep alive connection.
727
- # version:: Float
728
- def keep_alive_enabled?(version)
729
- version >= 1.1
730
- end
731
-
732
- # Returns true if the given query (or body) has a multiple parameter.
733
- def multiparam_query?(query)
734
- query.is_a?(Array) or query.is_a?(Hash)
735
- end
736
-
737
- # Returns true if the given object is a File. In HTTPClient, a file is;
738
- # * must respond to :read for retrieving String chunks.
739
- # * must respond to :path and returns a path for Content-Disposition.
740
- # * must respond to :pos and :pos= to rewind for reading.
741
- # Rewinding is only needed for following HTTP redirect. Some IO impl
742
- # defines :pos= but raises an Exception for pos= such as StringIO
743
- # but there's no problem as far as using it for non-following methods
744
- # (get/post/etc.)
745
- def file?(obj)
746
- obj.respond_to?(:read) and obj.respond_to?(:path) and
747
- obj.respond_to?(:pos) and obj.respond_to?(:pos=)
748
- end
749
-
750
- def create_query_part_str(query) # :nodoc:
751
- if multiparam_query?(query)
752
- escape_query(query)
753
- elsif query.respond_to?(:read)
754
- query = query.read
755
- else
756
- query.to_s
757
- end
758
- end
759
-
760
- def escape_query(query) # :nodoc:
761
- query.collect { |attr, value|
762
- if value.respond_to?(:read)
763
- value = value.read
764
- end
765
- escape(attr.to_s) << '=' << escape(value.to_s)
766
- }.join('&')
767
- end
768
-
769
- # from CGI.escape
770
- def escape(str) # :nodoc:
771
- str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
772
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
773
- }.tr(' ', '+')
774
- end
775
- end
776
-
777
-
778
- # HTTP::Message::Headers:: message header.
779
- attr_accessor :header
780
-
781
- # HTTP::Message::Body:: message body.
782
- attr_reader :body
783
-
784
- # OpenSSL::X509::Certificate:: response only. server certificate which is
785
- # used for retrieving the response.
786
- attr_accessor :peer_cert
787
-
788
- # Creates a Message. This method should be used internally.
789
- # Use Message.new_connect_request, Message.new_request or
790
- # Message.new_response instead.
791
- def initialize # :nodoc:
792
- @header = Headers.new
793
- @body = @peer_cert = nil
794
- end
795
-
796
- # Dumps message (header and body) to given dev.
797
- # dev needs to respond to <<.
798
- def dump(dev = '')
799
- str = header.dump + CRLF
800
- if header.chunked
801
- dev = body.dump_chunked(str, dev)
802
- elsif body
803
- dev = body.dump(str, dev)
804
- else
805
- dev << str
806
- end
807
- dev
808
- end
809
-
810
- # Sets a new body. header.body_size is updated with new body.size.
811
- def body=(body)
812
- @body = body
813
- @header.body_size = @body.size if @header
814
- end
815
-
816
- # Returns HTTP version in a HTTP header. Float.
817
- def version
818
- @header.http_version
819
- end
820
-
821
- # Sets HTTP version in a HTTP header. Float.
822
- def version=(version)
823
- @header.http_version = version
824
- end
825
-
826
- # Returns HTTP status code in response. Integer.
827
- def status
828
- @header.status_code
829
- end
830
-
831
- alias code status
832
- alias status_code status
833
-
834
- # Sets HTTP status code of response. Integer.
835
- # Reason phrase is updated, too.
836
- def status=(status)
837
- @header.status_code = status
838
- end
839
-
840
- # Returns HTTP status reason phrase in response. String.
841
- def reason
842
- @header.reason_phrase
843
- end
844
-
845
- # Sets HTTP status reason phrase of response. String.
846
- def reason=(reason)
847
- @header.reason_phrase = reason
848
- end
849
-
850
- # Sets 'Content-Type' header value. Overrides if already exists.
851
- def contenttype
852
- @header.contenttype
853
- end
854
-
855
- # Returns 'Content-Type' header value.
856
- def contenttype=(contenttype)
857
- @header.contenttype = contenttype
858
- end
859
-
860
- # Returns a content of message body. A String or an IO.
861
- def content
862
- @body.content
863
- end
864
- end
865
-
866
-
867
- end