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