rmail-1.1.4/0000755000004100000410000000000013701337101012645 5ustar www-datawww-datarmail-1.1.4/test/0000755000004100000410000000000013701337101013624 5ustar www-datawww-datarmail-1.1.4/test/testmboxreader.rb0000644000004100000410000001147013701337101017204 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (c) 2002 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/mailbox/mboxreader' class TextRMailMBoxReader < TestBase def test_mbox_s_new r = RMail::Mailbox::MBoxReader.new("") assert_instance_of(RMail::Mailbox::MBoxReader, r) end def test_mbox_simple data_as_file("mbox.simple") { |f| mbox = RMail::Mailbox::MBoxReader.new(f) chunk = mbox.read(nil) assert_equal("From foo@bar Wed Nov 27 12:27:32 2002\nmessage1\n", chunk) chunk = mbox.read(nil) assert_nil(chunk) mbox.next chunk = mbox.read(nil) assert_equal("From foo@bar Wed Nov 27 12:27:36 2002\nmessage2\n", chunk) chunk = mbox.read(nil) assert_nil(chunk) mbox.next chunk = mbox.read(nil) assert_equal("From foo@bar Wed Nov 27 12:27:40 2002\nmessage3\n", chunk) chunk = mbox.read(nil) assert_nil(chunk) mbox.next chunk = mbox.read(nil) assert_nil(chunk) } end def test_mbox_odd data_as_file("mbox.odd") { |f| mbox = RMail::Mailbox::MBoxReader.new(f) chunk = mbox.read(nil) assert_equal("From foo@bar Wed Nov 27 12:27:36 2002\nmessage1\n", chunk) chunk = mbox.read(nil) assert_nil(chunk) mbox.next chunk = mbox.read(nil) assert_equal("From foo@bar Wed Nov 27 12:27:40 2002\nmessage2\n", chunk) chunk = mbox.read(nil) assert_nil(chunk) mbox.next chunk = mbox.read(nil) assert_nil(chunk) } end def t_chunksize_helper(reader, expected) chunk = reader.read(nil) assert_equal(expected, chunk) end def test_mbox_chunksize 1.upto(80) { |chunk_size| data_as_file("mbox.simple") { |f| mbox = RMail::Mailbox::MBoxReader.new(f) mbox.chunk_size = chunk_size t_chunksize_helper(mbox, "From foo@bar Wed Nov 27 12:27:32 2002\nmessage1\n") mbox.next t_chunksize_helper(mbox, "From foo@bar Wed Nov 27 12:27:36 2002\nmessage2\n") mbox.next t_chunksize_helper(mbox, "From foo@bar Wed Nov 27 12:27:40 2002\nmessage3\n") mbox.next chunk = mbox.read assert_nil(chunk) } } end def t_randomly_randomize(template) count = rand(10) messages = [] 0.upto(count) { |i| messages << template[0, 5 + rand(template.length - 5)] + "\n" } mbox_string = messages.join("\n") return [ messages, mbox_string ] end def t_randomly_parse_messages(reader) messages = [] while message = reader.read(nil) messages << message reader.next end return messages end def t_randomly_parse_helper(chunk_size, messages, mbox_string) reader = RMail::Mailbox::MBoxReader.new(mbox_string) reader.chunk_size = chunk_size unless chunk_size.nil? messages2 = t_randomly_parse_messages(reader) assert_equal(messages, messages2) end def test_mbox_randomly 5.times { template = ("From foo@bar\n" + "text1\ntext2\ntext3\ntexttexttexttexttext4\n" + (("abcdefghijklmnopqrstuvwxyz" * rand(5)) + "\n") * rand(20)) messages, mbox_string = t_randomly_randomize(template) 1.upto(200) { |chunk_size| t_randomly_parse_helper(chunk_size, messages, mbox_string) } t_randomly_parse_helper(nil, messages, mbox_string) } end end rmail-1.1.4/test/testpushbackreader.rb0000644000004100000410000000331113701337101020032 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (c) 2002 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/mailbox/mboxreader' class TextRMailParserPushbackReader < TestBase def test_pushback reader = RMail::Parser::PushbackReader.new("") assert_raise(RMail::Parser::Error) { reader.pushback("hi bob") } end end rmail-1.1.4/test/runtests.rb0000644000004100000410000000332313701337101016041 0ustar www-datawww-data#!/usr/bin/env ruby # # Copyright (C) 2001, 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # fail "must run this script directly" unless __FILE__ == $0 path = File.expand_path(File.join(File.dirname($0), '..', 'lib')) puts "Prepending #{path} to the $LOAD_PATH" $LOAD_PATH.unshift(path) # get our stuff first Dir['test/test*.rb'].each {|f| require f } rmail-1.1.4/test/addrgrammar.txt0000644000004100000410000000730413701337101016652 0ustar www-datawww-dataPrimarily concerned about mailbox, mailbox-list, and address-list mailbox - sender, resent-sender mailbox-list - from, resent-from address-list - reply-to, to, cc, bcc, resent-to, resent-cc, resent-bcc ---------------------------------------------------------------------- Tokens ---------------------------------------------------------------------- Primitive -- these do not appear in Address Spedifications ---------------------------------------------------------------------- NO-WS-CTL = %d1-8 / ; US-ASCII control characters %d11 / ; that do not include the %d12 / ; carriage return, line feed, %d14-31 / ; and white space characters %d127 atext = a-z / A-Z / ; Any character except controls, 0-9 / "!" / "#" / ; SP, and specials. "$" / "%" / ; Used for atoms "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~" quoted-pair = "\" (%d0-127) qtext = NO-WS-CTL / ; Non white space controls %d33 / ; The rest of the US-ASCII %d35-91 / ; characters not including "\" %d93-126 ; or the quote character WSP = %d32 / %d09 ; SPACE or TAB ctext = NO-WS-CTL / ; Non white space controls %d33-39 / ; The rest of the US-ASCII %d42-91 / ; characters not including "(", %d93-126 ; ")", or "\" comment = "(" *([FWS] ccontent) [FWS] ")" ccontent = ctext / quoted-pair / comment qcontent = qtext / quoted-pair dtext = NO-WS-CTL / ; Non white space controls %d33-90 / ; The rest of the US-ASCII %d94-126 ; characters not including "[", ; "]", or "\" Less primitive - these appear in Address Specifications ---------------------------------------------------------------------- FWS = 1*([CRLF] WSP) CFWS = *([FWS] comment) (([FWS] comment) / FWS) atom = [CFWS] 1*atext [CFWS] quoted-string = [CFWS] " *([FWS] qcontent) [FWS] " [CFWS] phrase = word *(word / "." / CFWS) word = atom / quoted-string dcontent = dtext / quoted-pair ---------------------------------------------------------------------- Address Specification ---------------------------------------------------------------------- address-list = 1*([address] [CFWS] "," [CFWS]) [address] address = mailbox / group mailbox = name-addr / addr-spec group = display-name ":" [mailbox-list / CFWS] ";" [CFWS] mailbox-list = 1*([mailbox] [CFWS] "," [CFWS]) [mailbox] name-addr = [display-name] angle-addr display-name = phrase angle-addr = [CFWS] "<" [obs-route] addr-spec ">" [CFWS] obs-route = [CFWS] obs-domain-list ":" [CFWS] obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) domain = domain-literal / obs-domain domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] obs-domain = atom *("." atom) addr-spec = local-part "@" domain local-part = word *("." word) rmail-1.1.4/test/testheader.rb0000644000004100000410000010434513701337101016310 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2001, 2002, 2003, 2004, 2007 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/header' class TestRMailHeader < TestBase def test_AREF # '[]' h = RMail::Header.new h['From'] = 'setting1' h['From'] = 'setting2' h['From'] = 'setting3' assert_equal('setting1', h[0]) assert_equal('setting2', h[1]) assert_equal('setting3', h[2]) assert_equal('setting1', h['From']) assert_equal('setting1', h['from']) assert_equal('setting1', h['FROM']) assert_equal('setting1', h['From:']) assert_equal('setting1', h['From : ']) assert_nil(h['From ']) assert_nil(h[' From ']) assert_nil(h[' From : ']) end def test_ASET # '[]=' # # Test that the object stores the exact objects we pass it in (at # least, when they are strings) and that it freezes them. # bob = "Bob" bob_value = "bobvalue" sally = "Sally" sally_value = "sallyvalue" h = RMail::Header.new assert_same(bob_value, h[bob] = bob_value) assert_same(sally_value, h[sally] = sally_value) h.each_with_index do |pair, index| case index when 0 assert_equal(bob, pair[0]) assert_equal(bob_value, pair[1]) assert(pair[0].frozen?) assert(pair[1].frozen?) when 1 assert_equal(sally, pair[0]) assert_equal(sally_value, pair[1]) assert(pair[0].frozen?) assert(pair[1].frozen?) else raise end end # Test that passing in symbols will not get converted into strings # strings h = RMail::Header.new assert_raise(NoMethodError) { h[:Kelly] = :the_value } # Test that the : will be stripped h = RMail::Header.new h["bob:"] = "bob" h["sally : "] = "sally" h.each_with_index do |pair, index| case index when 0 assert_equal("bob", pair[0]) assert_equal("bob", pair[1]) when 1 assert_equal("sally", pair[0]) assert_equal("sally", pair[1]) else raise end end end def test_EQUAL # '==' h1 = RMail::Header.new h2 = RMail::Header.new assert_equal(h1, h2) assert_equal(h2, h1) h1['foo'] = 'a' h2['FOO'] = 'a' h1['bar'] = 'b' h2['bar'] = 'b' assert_equal(h1, h2) assert_equal(h2, h1) h1 = RMail::Header.new h2 = RMail::Header.new h1['foo'] = 'a' h2['foo'] = 'b' assert(! (h1 == h2)) assert(! (h2 == h1)) h1 = RMail::Header.new h2 = RMail::Header.new h1['foo'] = 'a' h2['foo'] = 'a' h1.mbox_from = "From bo diddly" assert(! (h1 == h2)) assert(! (h2 == h1)) h1 = RMail::Header.new assert(! (h1 == Object.new)) assert(! (h1 == Hash.new)) assert(! (h1 == Array.new)) end def test_address_list_fetch h = RMail::Header.new assert_equal([], h.address_list_fetch("From")) h.add("From", "bob@example.com") assert_equal(["bob@example.com"], h.address_list_fetch("From")) end def test_add # # Test that the object stores the exact objects we pass it in (at # least, when they are strings) and that it freezes them. # bob = "Bob" bob_value = "bobvalue" sally = "Sally" sally_value = "sallyvalue" h = RMail::Header.new assert_same(h, h.add(bob, bob_value)) assert_same(h, h.add(sally, sally_value)) h.each_with_index do |pair, index| case index when 0 assert_equal(bob, pair[0]) assert_equal(bob_value, pair[1]) assert(pair[0].frozen?) assert(pair[1].frozen?) when 1 assert_equal(sally, pair[0]) assert_equal(sally_value, pair[1]) assert(pair[0].frozen?) assert(pair[1].frozen?) else raise end end # Test that passing in symbol values raises an exception h = RMail::Header.new assert_raise(NoMethodError) { assert_same(h, h.add("bob", :the_value)) } # Test that we can put stuff in arbitrary locations h = RMail::Header.new assert_same(h, h.add("last", "last value")) assert_same(h, h.add("first", "first value", 0)) assert_same(h, h.add("middle", "middle value", 1)) h.each_with_index do |pair, index| case index when 0 assert_equal("first", pair[0]) assert_equal("first value", pair[1]) when 1 assert_equal("middle", pair[0]) assert_equal("middle value", pair[1]) when 2 assert_equal("last", pair[0]) assert_equal("last value", pair[1]) else raise end end # Test the params argument h = RMail::Header.new h.add("name", "value", nil, 'param1' => 'value1', 'param2' => '+value2') # Param order can not be guaranteed since they are given as dict to the # function. #assert_equal('value; param1=value1; param2="+value2"', h['name']) header_check = (h['name'] == 'value; param1=value1; param2="+value2"' or h['name'] == 'value; param2="+value2"; param1=value1') assert(header_check) h = RMail::Header.new h.add_raw("MIME-Version: 1.0") h.add_raw("Content-Type: multipart/alternative; boundary=X") assert_match(/\b1\.0\b/, h['mime-version:']) assert_not_nil(h.param('content-type', 'boundary')) assert_equal("multipart", h.media_type) end def test_set # Test that set works like delete+add h = RMail::Header.new h.add_raw("Foo: Bar") h.set('foo', 'expected') assert_equal('expected', h['foo']) assert_equal(1, h.length) h = RMail::Header.new h.set('foo', 'expected') assert_equal('expected', h['foo']) assert_equal(1, h.length) end def test_clear # Test that we can put stuff in arbitrary locations h = RMail::Header.new h.add("first", "first value", 0) h.add("middle", "middle value", 1) h.add("last", "last value") h.mbox_from = "mbox from" assert_equal(3, h.length) assert_same(h, h.clear) assert_equal(0, h.length) assert_equal(nil, h['first']) assert_equal(nil, h['middle']) assert_equal(nil, h['last']) assert_equal(nil, h[0]) assert_equal(nil, h[1]) assert_equal(nil, h[2]) assert_equal(nil, h.mbox_from) end def test_dup h1 = RMail::Header.new h1["field1"] = "field1 value" h1.mbox_from = "mbox from" h2 = h1.dup assert(! h1.equal?(h2)) assert_equal(h1, h2) assert_same(h1.mbox_from, h2.mbox_from) h1.each_with_index do |pair, index| assert_same(h1[index], h2[index]) case index when 0 assert_equal("field1", pair[0]) assert_equal("field1 value", pair[1]) else raise end end h2.mbox_from = "bob" assert_equal("mbox from", h1.mbox_from) h1["field2"] = "field2 value" assert_equal("field2 value", h1["field2"]) assert_nil(h2["field2"]) # Make sure singleton methods are not carried over through a dup def h1.my_singleton_method end assert_respond_to(h1, :my_singleton_method) h2 = h1.dup assert(! h2.respond_to?(:my_singleton_method)) end def test_clone h1 = RMail::Header.new h1["field1"] = "field1 value" h1.mbox_from = "mbox from" h2 = h1.clone assert(! h1.equal?(h2)) assert_equal(h1, h2) assert_equal(h1.mbox_from, h2.mbox_from) assert(! h1.mbox_from.equal?(h2.mbox_from)) h1.each_with_index do |pair, index| assert_equal(h1[index], h2[index]) assert(! h1[index].equal?(h2[index])) case index when 0 assert_equal("field1", pair[0]) assert_equal("field1 value", pair[1]) else raise end end h2.mbox_from = "bob" assert_equal("mbox from", h1.mbox_from) h1["field2"] = "field2 value" assert_equal("field2 value", h1["field2"]) assert_nil(h2["field2"]) # Make sure singleton methods are carried over through a clone h1 = RMail::Header.new def h1.my_singleton_method end assert_respond_to(h1, :my_singleton_method) h2 = h1.clone assert(!h1.equal?(h2)) assert_respond_to(h2, :my_singleton_method) end def test_replace h1 = RMail::Header.new h1['From'] = "bob@example.net" h1['To'] = "sam@example.net" h1.mbox_from = "mbox from" h2 = RMail::Header.new h2['From'] = "sally@example.net" h2.mbox_from = "h2 mbox from" assert_same(h2, h2.replace(h1)) assert_equal(h1, h2) assert_same(h1['From'], h2['From']) assert_same(h1['To'], h2['To']) assert_same(h1.mbox_from, h2.mbox_from) e = assert_raise(TypeError) { h2.replace("hi mom") } assert_equal('String is not of type RMail::Header', e.message) end def test_delete h = RMail::Header.new h['Foo'] = 'bar' h['Bazo'] = 'bingo' h['Foo'] = 'yo' assert_same(h, h.delete('Foo')) assert_nil(h['Foo']) assert_equal('bingo', h[0]) assert_equal(1, h.length) end def test_delete_at h = RMail::Header.new h['Foo'] = 'bar' h['Baz'] = 'bingo' h['Foo'] = 'yo' assert_same(h, h.delete_at(1)) assert_equal(2, h.length) assert_nil(h['Baz']) assert_equal('bar', h[0]) assert_equal('yo', h[1]) assert_raise(TypeError) { h.delete_at("1") } end def test_delete_if h = RMail::Header.new h['Foo'] = 'bar' h['Baz'] = 'bingo' h['Foo'] = 'yo' assert_same(h, h.delete_if { |n, v| v =~ /^b/ }) assert_nil(h['Baz']) assert_equal('yo', h['Foo']) assert_equal(1, h.length) assert_raise(LocalJumpError) { h.delete_if } end def each_helper(method) h = RMail::Header.new h['name1'] = 'value1' h['name2'] = 'value2' i = 1 h.send(method) { |n, v| assert_equal("name#{i}", n) assert_equal("value#{i}", v) i += 1 } assert_raise(LocalJumpError) { h.send(method) } end def test_each each_helper(:each) end def test_each_pair each_helper(:each_pair) end def each_name_helper(method) h = RMail::Header.new h['name1'] = 'value1' h['name2'] = 'value2' i = 1 h.send(method) { |n| assert_equal("name#{i}", n) i += 1 } assert_raise(LocalJumpError) { h.send(method) } end def test_each_name each_name_helper(:each_name) end def test_each_key each_name_helper(:each_key) end def test_each_value h = RMail::Header.new h['name1'] = 'value1' h['name2'] = 'value2' i = 1 h.each_value { |v| assert_equal("value#{i}", v) i += 1 } assert_raise(LocalJumpError) { h.each_value } end def test_empty? h = RMail::Header.new assert(h.empty?) h['To'] = "president@example.com" assert_equal(false, h.empty?) end def test_fetch h = RMail::Header.new h['To'] = "bob@example.net" h['To'] = "sally@example.net" assert_equal("bob@example.net", h.fetch('to')) assert_equal(1, h.fetch('notthere', 1)) assert_equal(2, h.fetch('notthere', 1) { 2 }) e = assert_raise(ArgumentError) { h.fetch(1,2,3) } assert_equal('wrong # of arguments(3 for 2)', e.message) end def test_fetch_all h = RMail::Header.new h['To'] = "bob@example.net" h['To'] = "sally@example.net" assert_equal([ "bob@example.net", "sally@example.net" ], h.fetch_all('to')) assert_equal(1, h.fetch('notthere', 1)) assert_equal(2, h.fetch('notthere', 1) { 2 }) e = assert_raise(ArgumentError) { h.fetch_all(1,2,3) } assert_equal('wrong # of arguments(3 for 2)', e.message) end def field_helper(method) h = RMail::Header.new h['Subject'] = 'the sky is blue' assert(h.send(method, 'Subject')) assert_equal(false, h.send(method, 'Return-Path')) end def test_field? field_helper(:field?) end def test_has_key? field_helper(:has_key?) end def test_include? field_helper(:include?) end def test_member? field_helper(:member?) end def test_key? field_helper(:key?) end def test_select_on_empty_header_returns_empty_array h = RMail::Header.new assert_equal([], h.select("From")) end def test_select h = RMail::Header.new h['To'] = 'matt@example.net' h['From'] = 'bob@example.net' h['Subject'] = 'test_select' assert_equal([ [ 'To', 'matt@example.net' ] ], h.select('To')) assert_equal([ [ 'To', 'matt@example.net' ], [ 'From', 'bob@example.net' ] ], h.select('To', 'From')) assert_equal([], h.select) end def names_helper(method) h = RMail::Header.new assert_equal([], h.send(method)) h['To'] = 'matt@example.net' h['from'] = 'bob@example.net' h['SUBJECT'] = 'test_select' assert_equal([ 'To', 'from', 'SUBJECT' ], h.send(method)) end def test_names names_helper(:names) end def test_keys names_helper(:keys) end def length_helper(method) h = RMail::Header.new assert_equal(0, h.send(method)) h['To'] = 'matt@example.net' assert_equal(1, h.send(method)) h['from'] = 'bob@example.net' assert_equal(2, h.send(method)) h['SUBJECT'] = 'test_select' assert_equal(3, h.send(method)) h.mbox_from = "foo" assert_equal(3, h.send(method)) h.delete('from') assert_equal(2, h.send(method)) end def test_length length_helper(:length) end def test_size length_helper(:size) end def test_to_a h = RMail::Header.new assert_equal([ ], h.to_a) h['To'] = 'to value' h['From'] = 'from value' assert_equal([ [ 'To', 'to value' ], [ 'From', 'from value' ] ], h.to_a) end def test_to_string h = RMail::Header.new assert_equal("", h.to_string(true)) assert_equal("", h.to_string(false)) assert_equal(h.to_s, h.to_string(true)) h['To'] = 'matt@example.net' assert_equal("To: matt@example.net\n", h.to_string(true)) assert_equal("To: matt@example.net\n", h.to_string(false)) assert_equal(h.to_s, h.to_string(true)) h.mbox_from = "From matt@example.net blah blah" assert_equal(<=', expected[index].length, "Expected result item must have no more than three " + "elements. " + testcase_desc) assert_equal(2, value.length, testcase_desc) expected_tag, expected_header = expected[index] got_tag, got_header = value assert_equal(header[index], got_header, testcase_desc) assert_equal(expected_tag, got_tag, "field #{index} has incorrect name. " + testcase_desc) assert_equal(expected_header, got_header, "field #{index} has incorrect line, " + "expected #{expected_header.inspect} got " + "#{got_header.inspect}. " + testcase_desc) assert_equal(header[expected_tag], expected_header, testcase_desc) } assert_equal(count, expected.length, "result has too few elements " + "(#{count} < #{expected.length}). " + testcase_desc) end def verify_match(header, name, value, expected_result) h = header.match(name, value) assert_kind_of(RMail::Header, h) if h.length == 0 assert_equal(expected_result, nil) else assert_not_nil(expected_result) compare_header(h, expected_result) end end def test_match h = RMail::Header.new h['To'] = 'bob@example.net' h['Cc'] = 'sammy@example.com' h['Resent-To'] = 'president@example.com' h['Subject'] = 'yoda lives!' # First verify argument type checking e = assert_raise(ArgumentError) { h.match(12, "foo") } assert_match(/name not a Regexp or String/, e.message) assert_nothing_raised { h.match(/not_case_insensitive/, "foo") } e = assert_raise(ArgumentError) { h.match(/this is okay/i, 12) } assert_match(/value not a Regexp or String/, e.message) assert_nothing_raised { h.match(/this is okay/i, /this_not_multiline_or_insensitive/) } assert_nothing_raised { h.match(/this is okay/i, /this_not_multiline/i) } assert_nothing_raised { h.match(/this is okay/i, /this_not_inesnsitive/m) } verify_match(h, /./i, /this will not match anything/im, nil) verify_match(h, "to", /./im, [ [ 'To', "bob@example.net" ] ]) verify_match(h, "tO", /./im, [ [ 'To', "bob@example.net" ] ]) verify_match(h, "To", /./im, [ [ 'To', "bob@example.net" ] ]) verify_match(h, "^to", /./im, nil) verify_match(h, /^(to|cc|resent-to)/i, /.*/im, [ [ 'To', "bob@example.net" ], [ 'Cc', "sammy@example.com" ], [ 'Resent-To', "president@example.com"] ]) end def test_match? h = RMail::Header.new h['To'] = 'bob@example.net' h['Cc'] = 'sammy@example.com' h['Resent-To'] = 'president@example.com' h['Subject'] = "yoda\n lives! [bob]\\s" # First verify argument type checking e = assert_raise(ArgumentError) { h.match?(12, "foo") } assert_match(/name not a Regexp or String/, e.message) assert_nothing_raised { h.match?(/not_case_insensitive/, "foo") } e = assert_raise(ArgumentError) { h.match?(/this is okay/i, 12) } assert_match(/value not a Regexp or String/, e.message) assert_nothing_raised { h.match?(/this is okay/i, /this_not_multiline_or_insensitive/) } assert_nothing_raised { h.match?(/this is okay/i, /this_not_multiline/i) } assert_nothing_raised { h.match?(/this is okay/i, /this_not_inesnsitive/m) } assert_equal(false, h.match?(/./i, /this will not match anything/im)) assert_equal(true, h.match?("to", /./im)) assert_equal(true, h.match?("To", /./im)) assert_equal(false, h.match?("^to", /./im)) assert_equal(true, h.match?(/^(to|cc|resent-to)/i, /.*/im)) assert_equal(true, h.match?('subject', 'yoda')) assert_equal(true, h.match?('subject', /yoda\s+lives/)) assert_equal(true, h.match?('subject', '[bob]\s')) assert_equal(true, h.match?('subject', '[BOB]\s')) end def test_content_type h = RMail::Header.new assert_equal(nil, h.content_type) h['content-type'] = ' text/html; charset=ISO-8859-1' assert_equal("text/html", h.content_type) h.delete('content-type') h['content-type'] = ' foo/html ; charset=ISO-8859-1' assert_equal("foo/html", h.content_type) h = RMail::Header.new h['content-type'] = '' assert_equal(nil, h.content_type) end def test_media_type h = RMail::Header.new assert_nil(h.media_type) assert_equal("foo", h.media_type("foo")) assert_equal("bar", h.media_type("foo") { "bar" }) h['content-type'] = ' text/html; charset=ISO-8859-1' assert_equal("text", h.media_type) h.delete('content-type') h['content-type'] = 'foo/html ; charset=ISO-8859-1' assert_equal("foo", h.media_type) end def test_subtype h = RMail::Header.new assert_nil(h.subtype) assert_equal("foo", h.subtype("foo")) assert_equal("bar", h.subtype("foo") { "bar" }) h['content-type'] = ' text/html; charset=ISO-8859-1' assert_equal("html", h.subtype) h.delete('content-type') h['content-type'] = 'foo/yoda ; charset=ISO-8859-1' assert_equal("yoda", h.subtype) end def test_params begin h = RMail::Header.new assert_nil(h.params('foo')) assert_nil(h.params('foo', nil)) default = "foo" ignore = "ignore" assert_same(default, h.params('foo', default)) assert_same(default, h.params('foo') { |field_name| assert_equal('foo', field_name) default }) assert_same(default, h.params('foo', ignore) { default }) end begin h = RMail::Header.new h['Content-Disposition'] = 'attachment; filename="delete_product_recover_flag.cmd"' assert_equal( { "filename" => "delete_product_recover_flag.cmd" }, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = 'attachment; filename="delete=_product=_recover;_flag;.cmd"' assert_equal( { "filename" => "delete=_product=_recover;_flag;.cmd" }, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = ' attachment ; filename = "trailing_Whitespace.cmd" ' assert_equal( { "filename" => "trailing_Whitespace.cmd" }, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = '' assert_equal({}, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = ' ' assert_equal({}, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = '=' assert_equal({}, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = 'ass; param1 = "p1"; param2 = "p2"' assert_equal({ 'param1' => 'p1', 'param2' => 'p2' }, h.params('content-disposition')) end begin h = RMail::Header.new h['Content-Disposition'] = 'ass; Foo = "" ; bar = "asdf"' assert_equal({ "foo" => '""', "bar" => "asdf" }, h.params('content-disposition')) end end def test_set_boundary begin h = RMail::Header.new h.set_boundary("b") assert_equal("b", h.param('content-type', 'boundary')) assert_equal("multipart/mixed", h.content_type) assert_equal('multipart/mixed; boundary=b', h['content-type']) end begin h = RMail::Header.new h['content-type'] = "multipart/alternative" h.set_boundary("b") assert_equal("b", h.param('content-type', 'boundary')) assert_equal("multipart/alternative", h.content_type) assert_equal('multipart/alternative; boundary=b', h['content-type']) end begin h = RMail::Header.new h['content-type'] = 'multipart/alternative; boundary="a"' h.set_boundary("b") assert_equal("b", h.param('content-type', 'boundary')) assert_equal("multipart/alternative", h.content_type) assert_equal('multipart/alternative; boundary=b', h['content-type']) end end def test_params_random_string # find_shortest_failure("],C05w\010O\e]b\">%\023[1{:L1o>B\"|\024fDJ@u{)\\\021\t\036\034)ZJ\034&/+]owh=?{Yc)}vi\000\"=@b^(J'\\,O|4v=\"q,@p@;\037[\"{!Dg*(\010\017WQ]:Q;$\004x]\032\035\003a#\"=;\005@&\003:;({>`y{?.p/\022jP\f/4U)\006+\022(<{|.<|]]\032.,N,\016\000\036T,;\\49C>C[{b[v") { |str| # h = RMail::Header.new # h['header'] = str # h.params('header') # } 0.upto(25) { specials = '()<>@,;:\\"/[]?=' strings = [(0..rand(255)).collect {rand(127).chr}.to_s, (0..255).collect {rand(255).chr}.to_s, (0..255).collect { r = rand(specials.length * 5) case r when 0 .. specials.length - 1 specials[r].chr else rand(127).chr end }.to_s ] strings.each {|string| assert_nothing_raised("failed for string #{string.inspect}") { h = RMail::Header.new h['header'] = string params = h.params('header') params.each { |name, value| assert_kind_of(String, name) assert(! ("" == name) ) assert_kind_of(String, value) } } } } end def test_param begin h = RMail::Header.new assert_nil(h.param('bar', 'foo')) assert_nil(h.param('bar', 'foo', nil)) default = "foo" ignore = "ignore" assert_same(default, h.param('bar', 'foo', default)) assert_same(default, h.param('bar', 'foo') { |field_name, param_name| assert_equal('bar', field_name) assert_equal('foo', param_name) default }) assert_same(default, h.param('bar', 'foo', ignore) { default }) end begin h = RMail::Header.new h['Content-Disposition'] = 'attachment; filename="delete_product_recover_flag.cmd"' assert_equal('delete_product_recover_flag.cmd', h.param('content-disposition', 'filename')) assert_nil(h.param('content-disposition', 'notthere')) end begin h = RMail::Header.new h['Content-Disposition'] = 'attachment; filename="delete=_product=_recover;_flag;.cmd"' assert_equal("delete=_product=_recover;_flag;.cmd", h.param('content-disposition', 'filename')) end begin h = RMail::Header.new h['Content-Disposition'] = ' attachment ; filename = " trailing_Whitespace.cmd " ' assert_equal(" trailing_Whitespace.cmd ", h.param('content-disposition', 'filename')) end end def test_date begin h = RMail::Header.new h.add_raw("Date: Sat, 18 Jan 2003 21:00:09 -0700") t = h.date assert(!t.utc?) t.utc assert_equal([9, 0, 4, 19, 1, 2003, 0], t.to_a[0, 7]) assert_match(/Sun, 19 Jan 2003 04:00:09 [+-]0000/, t.rfc2822) end begin h = RMail::Header.new h.add_raw("Date: Sat,18 Jan 2003 02:04:27 +0100 (CET)") t = h.date assert(!t.utc?) t.utc assert_equal([27, 4, 1, 18, 1, 2003, 6, 18], t.to_a[0, 8]) assert_match(/Sat, 18 Jan 2003 01:04:27 [+-]0000/, t.rfc2822) end begin h = RMail::Header.new # This one is bogus and can't even be parsed. h.add_raw("Date: A long time ago") t = assert_nothing_raised { h.date } assert_nil(t) end end def test_date_eq h = RMail::Header.new t = Time.at(1042949885).utc h.date = t assert_match(/Sun, 19 Jan 2003 04:18:05 [+-]0000/, h['date']) end def test_from begin h = RMail::Header.new h.add_raw('From: matt@example.net') a = h.from assert_kind_of(Array, a) assert_kind_of(RMail::Address::List, a) assert_equal(1, a.length) assert_kind_of(RMail::Address, a.first) assert_equal(RMail::Address.new("matt@example.net"), a.first) end begin h = RMail::Header.new h.add_raw('From: Matt Armstrong , Bob Smith ') a = h.from assert_kind_of(Array, a) assert_kind_of(RMail::Address::List, a) assert_equal(2, a.length) assert_kind_of(RMail::Address, a[0]) assert_equal("matt@example.net", a[0].address) assert_equal("Matt Armstrong", a[0].display_name) assert_kind_of(RMail::Address, a[1]) assert_equal("bob@example.com", a[1].address) assert_equal("Bob Smith", a[1].display_name) end begin h = RMail::Header.new a = h.from assert_kind_of(Array, a) assert_kind_of(RMail::Address::List, a) assert_equal(0, a.length) assert_nil(h.from.first) end end def common_test_address_list_header_assign(field_name) h = RMail::Header.new get = field_name.downcase.gsub(/-/, '_') assign = get + '=' h.__send__("#{assign}", "bob@example.net") assert_equal(1, h.length) assert_equal("bob@example.net", h[field_name]) h[field_name] = "bob2@example.net" assert_equal(2, h.length) assert_equal("bob@example.net", h[field_name]) assert_equal(["bob@example.net", "bob2@example.net"], h.fetch_all(field_name)) assert_equal(["bob@example.net", "bob2@example.net"], h.__send__("#{get}")) h.__send__("#{assign}", "sally@example.net") assert_equal(1, h.length) assert_equal("sally@example.net", h[field_name]) h.__send__("#{assign}", "Sally , bob@example.invalid (Bob)") assert_equal(1, h.length) assert_equal(2, h.__send__(get).length) assert_equal(%w{ sally bob }, h.__send__(get).locals) assert_equal([ 'Sally', nil ], h.__send__(get).display_names) assert_equal([ 'Sally', 'Bob' ], h.__send__(get).names, "got wrong result for #names") assert_equal(%w{ example.net example.invalid }, h.__send__(get).domains) assert_equal(%w{ sally@example.net bob@example.invalid }, h.__send__(get).addresses) assert_equal([ "Sally ", "bob@example.invalid (Bob)" ], h.__send__(get).format) h.__send__("#{assign}", RMail::Address.new('Bill ')) assert_equal(1, h.length) assert_equal(1, h.__send__(get).length) assert_equal(%w{ bill@example.net }, h.__send__(get).addresses) assert_equal('bill@example.net', h.__send__(get)[0].address) assert_equal('Bill', h.__send__(get)[0].display_name) h.__send__("#{assign}", RMail::Address.parse('Bob , ' + 'Sally ')) assert_equal(1, h.length) assert_equal(2, h.__send__(get).length) assert_equal(%w{ bob@example.net sally@example.net }, h.__send__(get).addresses) assert_equal('bob@example.net', h.__send__(get)[0].address) assert_equal('Bob', h.__send__(get)[0].display_name) assert_equal('sally@example.net', h.__send__(get)[1].address) assert_equal('Sally', h.__send__(get)[1].display_name) end def test_from_assign common_test_address_list_header_assign('From') end def test_to_assign common_test_address_list_header_assign('To') end def test_reply_cc_assign common_test_address_list_header_assign('Cc') end def test_reply_bcc_assign common_test_address_list_header_assign('Bcc') end def test_reply_to_assign common_test_address_list_header_assign('Reply-To') end def test_message_id h = RMail::Header.new h.set('Message-Id', '') assert_equal('', h.message_id) end def test_add_message_id h = RMail::Header.new h.add_message_id a = RMail::Address.parse(h.message_id).first require 'socket' assert_equal(Socket.gethostname + '.invalid', a.domain) assert_equal('rubymail', a.local.split('.')[3]) assert_equal('0', a.local.split('.')[2], "md5 data present for empty header") assert_match(/[a-z0-9]{5,6}/, a.local.split('.')[0]) assert_match(/[a-z0-9]{5,6}/, a.local.split('.')[1]) h.to = "matt@lickey.com" h.delete('message-id') h.add_message_id a = RMail::Address.parse(h.message_id).first assert_equal('70bmbq38pc5q462kl4ikv0mcq', a.local.split('.')[2], "md5 hash wrong for header") end def test_subject h = RMail::Header.new h['subject'] = 'hi mom' assert_equal('hi mom', h.subject) assert_equal('hi mom', h['subject']) h.subject = 'hi dad' assert_equal(1, h.length) assert_equal('hi dad', h['subject']) assert_equal('hi dad', h['Subject']) assert_equal("Subject: hi dad\n", h.to_s) end def test_recipients %w{ to cc bcc }.each { |field_name| h = RMail::Header.new h[field_name] = 'matt@lickey.com' assert_equal([ 'matt@lickey.com' ], h.recipients ) h[field_name] = 'bob@lickey.com' assert_equal([ 'matt@lickey.com', 'bob@lickey.com' ], h.recipients ) } h = RMail::Header.new h.to = [ 'bob@example.net', 'sally@example.net' ] h.cc = 'bill@example.net' h.bcc = 'samuel@example.net' assert_kind_of(RMail::Address::List, h.recipients) assert_equal([ 'bill@example.net', 'bob@example.net', 'sally@example.net', 'samuel@example.net' ], h.recipients.sort) h = RMail::Header.new assert_kind_of(RMail::Address::List, h.recipients) assert_equal(0, h.recipients.length) end def test_invalid_encoding_utf8 _, value = RMail::Header::Field.parse('Subject: abgeändertes Angebot') assert_equal 'abgeändertes Angebot', value end def test_invalid_encoding_iso_8859_1 _, value = RMail::Header::Field.parse('Subject: abgeändertes Angebot'.encode(Encoding::ISO_8859_1)) assert_equal 'abgeändertes Angebot'.encode(Encoding::ISO_8859_1), value end end rmail-1.1.4/test/data/0000755000004100000410000000000013701337101014535 5ustar www-datawww-datarmail-1.1.4/test/data/mbox.odd0000644000004100000410000000013713701337101016173 0ustar www-datawww-dataFrom foo@bar Wed Nov 27 12:27:36 2002 message1 From foo@bar Wed Nov 27 12:27:40 2002 message2rmail-1.1.4/test/data/parser/0000755000004100000410000000000013701337101016031 5ustar www-datawww-datarmail-1.1.4/test/data/parser/multipart.50000644000004100000410000000010713701337101020136 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa-- rmail-1.1.4/test/data/parser/multipart.130000644000004100000410000000012413701337101020214 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa part1 rmail-1.1.4/test/data/parser/multipart.140000644000004100000410000000012313701337101020214 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa part1rmail-1.1.4/test/data/parser/multipart.160000644000004100000410000000012413701337101020217 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble line1 line2rmail-1.1.4/test/data/parser/multipart.20000644000004100000410000000011713701337101020134 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa --aa-- rmail-1.1.4/test/data/parser/multipart.70000644000004100000410000000012113701337101020134 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa-- rmail-1.1.4/test/data/parser/multipart.40000644000004100000410000000013113701337101020132 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa-- epilogue rmail-1.1.4/test/data/parser/multipart.90000644000004100000410000000011513701337101020141 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa --aa--rmail-1.1.4/test/data/parser/multipart.120000644000004100000410000000022513701337101020215 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aaZ part1 --aa ignored part2 --aa ignored part3 --aa--ignored epilogue rmail-1.1.4/test/data/parser/multipart.110000644000004100000410000000020113701337101020206 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa part1 --aa part2 --aa part3 --aa-- epilogue rmail-1.1.4/test/data/parser/multipart.30000644000004100000410000000011413701337101020132 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa --aa-- rmail-1.1.4/test/data/parser/multipart.60000644000004100000410000000011013701337101020131 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa-- rmail-1.1.4/test/data/parser/multipart.100000644000004100000410000000010613701337101020211 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 --aa--rmail-1.1.4/test/data/parser/multipart.150000644000004100000410000000012513701337101020217 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble line1 line2 rmail-1.1.4/test/data/parser/multipart.10000644000004100000410000000014413701337101020133 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa part1 --aa-- epilogue rmail-1.1.4/test/data/parser/multipart.80000644000004100000410000000014313701337101020141 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa part1 --aa-- epiloguermail-1.1.4/test/data/parser.nested-multipart0000644000004100000410000000305513701337101021257 0ustar www-datawww-dataReturn-Path: Delivered-To: matt@lickey.com Received: from localhost (localhost [127.0.0.1]) by squeaker.lickey.com (Postfix) with ESMTP id 78B08C0BE for ; Thu, 7 Feb 2002 19:35:13 -0700 (MST) Received: by squeaker.lickey.com (Postfix, from userid 1000) id 52497BE6D; Thu, 7 Feb 2002 19:35:12 -0700 (MST) To: matt@lickey.com Subject: Some nested multiparts From: Matt Armstrong Date: Thu, 07 Feb 2002 19:35:11 -0700 X-Gnus-Mail-Source: directory:~/.incoming Message-ID: <877kpolmzk.fsf@squeaker.lickey.com> User-Agent: Gnus/5.090006 (Oort Gnus v0.06) Emacs/21.1 (i386-debian-linux-gnu) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Virus-Scanned: by AMaViS snapshot-20010714 Lines: 45 Xref: squeaker.lickey.com inbox:7709 This is level 1's preamble. --=-=-= Let's see here. --=-=-= Content-Disposition: inline This is the first part. --=-=-= Content-Type: multipart/mixed; boundary="==-=-=" This is level 2's preamble. --==-=-= Content-Disposition: inline This is the first nested part. --==-=-= Content-Disposition: inline This is the second nested part. --==-=-= Content-Type: multipart/mixed; boundary="===-=-=" This is level 3's preamble. --===-=-= This is the first doubly nested part. --===-=-= Content-Disposition: inline This is the second doubly nested part. --===-=-=-- This is level 3's epilogue. --==-=-=-- This is level 2's epilogue. It has no trailing end of line. --=-=-= Content-Disposition: inline This is the third part. --=-=-=-- This is level 1's epilogue. rmail-1.1.4/test/data/parser.nested-simple20000644000004100000410000000025213701337101020605 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= Content-Type: multipart/mixed; boundary="==-=-=" --==-=-= --==-=-= --==-=-=-- --=-=-=-- rmail-1.1.4/test/data/transparency/0000755000004100000410000000000013701337101017246 5ustar www-datawww-datarmail-1.1.4/test/data/transparency/message.60000644000004100000410000025654413701337101021001 0ustar www-datawww-dataFrom foo@bar Wed Feb 20 22:42:48 2002 Return-Path: Delivered-To: matt@lickey.com Received: by squeaker.lickey.com (Postfix, from userid 1000) id 543A0BF06; Wed, 20 Feb 2002 22:42:43 -0700 (MST) To: matt@lickey.com Subject: A test with a bigish attachment. From: Matt Armstrong Date: Wed, 20 Feb 2002 22:42:42 -0700 Message-ID: <87r8nfz8z1.fsf@squeaker.lickey.com> Lines: 8 User-Agent: Gnus/5.090006 (Oort Gnus v0.06) Emacs/21.1 (i386-debian-linux-gnu) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= This is a 64k file full of zeroes. It'll be big when base64'd but hopefully it'll compress well. --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename=zeros Content-Transfer-Encoding: base64 Content-Description: This file consists entirely of zeroes for better compression. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== --=-=-= -- matt --=-=-=-- rmail-1.1.4/test/data/transparency/message.50000644000004100000410000000116013701337101020756 0ustar www-datawww-dataFrom matt@lickey.com Fri Dec 28 00:00:11 2001 Return-Path: Delivered-To: matt@lickey.com Received: from localhost (localhost [127.0.0.1]) by squeaker.lickey.com (Postfix) with ESMTP id 8D079BE82 for ; Fri, 28 Dec 2001 00:00:06 -0700 (MST) Received: by squeaker.lickey.com (Postfix, from userid 1000) id B8DFBBD2C; Fri, 28 Dec 2001 00:00:02 -0700 (MST) To: matt@lickey.com Subject: Upcoming Events Message-Id: <20011228070002.B8DFBBD2C@squeaker.lickey.com> Date: Fri, 28 Dec 2001 00:00:02 -0700 (MST) From: matt@lickey.com (Matt Armstrong) X-Virus-Scanned: by AMaViS snapshot-20010714 rmail-1.1.4/test/data/transparency/absolute.30000644000004100000410000000011513701337101021145 0ustar www-datawww-dataMime-Version: 1.0 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE" rmail-1.1.4/test/data/transparency/absolute.60000644000004100000410000000254213701337101021156 0ustar www-datawww-dataMime-Version: 1.0 Content-Type: multipart/mixed; boundary="boundaryA" --boundaryA Content-Type: text/plain; charset="us-ascii" ; format="flowed" Hi, --boundaryA Content-Id: Content-Type: multipart/appledouble; boundary="boundaryB" --boundaryB Content-Transfer-Encoding: base64 Content-Type: application/applefile; name="%tstrtok.h" Content-Disposition: attachment; filename="%tstrtok.h" ; modification-date="Thu, 6 Dec 2001 17:24:21 +0900" AAOiZkUDomZFS20MAAOj9Cs= --boundaryB Content-Type: application/octet-stream; name="tstrtok.h" ; x-mac-type="3F3F3F3F" ; x-mac-creator="2D2D2D2D" Content-Disposition: attachment; filename="tstrtok.h" Content-Transfer-Encoding: base64 UiAqX3NzYXZlOw19Ow0NI2VuZGlmDQ== --boundaryB-- --boundaryA Content-Id: Content-Type: multipart/appledouble; boundary="boundaryB" --boundaryB Content-Transfer-Encoding: base64 Content-Type: application/applefile; name="%tstrtok.cc" Content-Disposition: attachment; filename="%tstrtok.cc" ; modification-date="Thu, 6 Dec 2001 17:24:21 +0900" AAADomZFA6JmRUttDAADo/Qr --boundaryB Content-Type: application/octet-stream; name="tstrtok.cc" ; x-mac-type="3F3F3F3F" ; x-mac-creator="2D2D2D2D" Content-Disposition: attachment; filename="tstrtok.cc" Content-Transfer-Encoding: base64 Mik7DX0N --boundaryB-- --boundaryA-- rmail-1.1.4/test/data/transparency/message.30000644000004100000410000000352313701337101020761 0ustar www-datawww-dataFrom: Chris Halls To: apt-proxy-users@lists.sourceforge.net Message-ID: <20020206115353.GB13304@shawn.nikocity.de> Mail-Followup-To: apt-proxy-users@lists.sourceforge.net Mime-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="p2kqVDKq5asng8Dg" Content-Disposition: inline User-Agent: Mutt/1.3.25i Subject: [Apt-proxy-users] [ANNOUNCE] apt-proxy 1.2.0 is out! Sender: apt-proxy-users-admin@lists.sourceforge.net --p2kqVDKq5asng8Dg Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable apt-proxy 1.2.0 has been released. =20 This version features: - More advanced cache management with MAX_VERSIONS and file corruption=20 detection - New log and config file command line options - Bugfixes to streaming code and cache management - ready to go out of the box: user, cache directory and logfile are created during first installation - documentation updates - debian packaging directory included in source You can download it from Sourceforge or from Debian unstable, once it was been uploaded there. Many thanks to Martin Schwenke, Gerhard Muntingh, Stephen Rothwell, Thorsten Gunkel and others for their contributions and improvements. Enjoy! - Chris Halls --=20 Chris Halls | Frankfurt, Germany --p2kqVDKq5asng8Dg Content-Type: application/pgp-signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE8YRlRexmdExmX588RAjERAKDI5Ll83WnSpK5eU+1JYIndMMpcnQCfZv3H 8OyQUJrkuEWlB+nei1ChDrc= =EUG3 -----END PGP SIGNATURE----- --p2kqVDKq5asng8Dg-- _______________________________________________ Apt-proxy-users mailing list Apt-proxy-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/apt-proxy-users rmail-1.1.4/test/data/transparency/absolute.50000644000004100000410000000011713701337101021151 0ustar www-datawww-dataMime-Version: 1.0 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE" rmail-1.1.4/test/data/transparency/absolute.10000644000004100000410000000022313701337101021143 0ustar www-datawww-dataReturn-Path: To: This message has various embedded tabs and spaces in the headers. We want to preserve them all. rmail-1.1.4/test/data/transparency/message.10000644000004100000410000000765413701337101020770 0ustar www-datawww-dataReturn-Path: Delivered-To: matt+bigfoot@lickey.com Received: from localhost (localhost [127.0.0.1]) by squeaker.lickey.com (Postfix) with ESMTP id 158C3BEF6 for ; Sun, 27 Jan 2002 13:34:34 -0700 (MST) Received: from hank.lickey.com (burt.lickey.com [192.168.100.1]) by squeaker.lickey.com (Postfix) with ESMTP id B28CEBEEE for ; Sun, 27 Jan 2002 13:34:33 -0700 (MST) Received: from bigfoot.com (mail.bigfoot.com [64.15.239.140]) by hank.lickey.com (Postfix) with SMTP id 63AD9EE2B for ; Sun, 27 Jan 2002 13:34:32 -0700 (MST) Received: from mailcity.com ([148.223.80.6]) by BFLITEMAIL2A.bigfoot.com (LiteMail v3.01(BFLITEMAIL2A)) with SMTP id 27Jan2002_BFLITEMAIL2A_23874_112857240; Sun, 27 Jan 2002 15:34:21 -0500 EST Received: from 102.177.145.252 ([102.177.145.252]) by mailout2-eri1.midsouth.rr.com with NNFMP; 07 Mar 2000 16:22:43 +1000 Received: from 200.118.98.31 ([200.118.98.31]) by mta05bw.bigpond.com with QMQP; Tue, 07 Mar 2000 21:19:35 +0500 Received: from 81.241.80.247 ([81.241.80.247]) by a231242.upc-a.chello.nl with SMTP; 08 Mar 2000 12:16:27 -1000 Received: from [82.235.149.104] by rly-xw01.mx.aol.com with esmtp; Wed, 08 Mar 2000 03:13:19 -0100 Reply-To: Message-ID: <008e22e61aad$1332d8a4$1dd68bb3@mxsysb> From: To: Subject: Life Insurance up to 75% Off. Get a FREE Quote Now! MiME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_00C8_42D56D1A.D4060D84" X-Priority: 3 (Normal) X-MSMail-Priority: Normal X-Mailer: MIME-tools 5.503 (Entity 5.501) Importance: Normal Date: Sun, 27 Jan 2002 13:34:32 -0700 (MST) X-Virus-Scanned: by AMaViS snapshot-20010714 ------=_NextPart_000_00C8_42D56D1A.D4060D84 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: base64 PGh0bWw+DQo8aGVhZD4NCjwvaGVhZD4NCjxib2R5Pg0KDQo8Y2VudGVyPg0K PGZvbnQgZmFjZT0idGltZXMiIHNpemU9IjYiIGNvbG9yPSIjMDAwMDAwIj5T YXZlIHVwIHRvIA0KPGZvbnQgY29sb3I9IiNmZjAwMDAiPjc1JTwvZm9udD4g b24geW91ciBUZXJtIExpZmUgSW5zdXJhbmNlITwvZm9udD4NCjxicj4gIA0K PGZvbnQgZmFjZT0idGltZXMiIHNpemU9IjQiIGNvbG9yPSIjMDAwMDAwIj4N CjxpPkNvbXBhcmUgcmF0ZXMgZnJvbSB0b3AgaW5zdXJhbmNlIGNvbXBhbmll cyBhcm91bmQgdGhlIGNvdW50cnk8L2k+PC9mb250Pg0KPGJyPjxicj4NCjxm b250IGZhY2U9ImFyaWFsIiBzaXplPSI0IiBjb2xvcj0iIzcwODRENiI+DQo8 Yj5JbiBvdXIgbGlmZSBhbmQgdGltZXMsIGl0J3MgaW1wb3J0YW50IHRvIHBs YW4gZm9yIHlvdXIgZmFtaWx5J3MgZnV0dXJlLCB3aGlsZSANCjxicj5iZWlu ZyBjb21mb3J0YWJsZSBmaW5hbmNpYWxseS4gIENob29zZSB0aGUgcmlnaHQg TGlmZSBJbnN1cmFuY2UgcG9saWN5IHRvZGF5LjwvZm9udD4NCjxwPg0KPGZv bnQgZmFjZT0iYXJpYWwiIHNpemU9IjMiIGNvbG9yPSIjMDAwMDAwIj4NCjxp PkNsaWNrIHRoZSBsaW5rIGJlbG93IHRvIGNvbXBhcmUgdGhlIGxvd2VzdCBy YXRlcyBhbmQgc2F2ZSB1cCB0byA8Zm9udCBjb2xvcj0iI2ZmMDAwMCI+NzUl PC9mb250PjwvaT48L2I+PC9mb250PiAgDQo8cD4NCjxhIGhyZWY9Imh0dHA6 Ly82Ni4xNjMuNDAuMjA6OTk5L3NhdmU3MC81MjY0MDMvIj48Zm9udCBmYWNl PSJhcmlhbCIgc2l6ZT0iNCI+DQo8Yj5DT01QQVJFIFlPVVIgQ09WRVJBR0U8 L2I+PC9mb250PjwvYT4NCjxwPg0KPGZvbnQgZmFjZT0idGltZXMiIHNpemU9 IjUiIGNvbG9yPSIjMDAwMDAwIj4NCllvdSdsbCBiZSBhYmxlIHRvIGNvbXBh cmUgcmF0ZXMgYW5kIGdldCBhIGZyZWUgYXBwbGljYXRpb24gaW4gPGk+bGVz cyB0aGFuIGEgbWludXRlITwvaT48L2ZvbnQ+DQo8cD4NCjxmb250IGZhY2U9 ImFyaWFsIiBzaXplPSI1IiBjb2xvcj0iI2ZmMDAwMCI+DQo8Yj4qR2V0IHlv dXIgRlJFRSBpbnN0YW50IHF1b3Rlcy4uLjxicj4NCipDb21wYXJlIHRoZSBs b3dlc3QgcHJpY2VzLCB0aGVuLi4uPGJyPg0KKlNlbGVjdCBhIGNvbXBhbnkg YW5kIEFwcGx5IE9ubGluZS48L2I+PC9mb250Pg0KPHA+DQo8YSBocmVmPSJo dHRwOi8vNjYuMTYzLjQwLjIwOjk5OS9zYXZlNzAvNTI2NDAzLyI+PGZvbnQg ZmFjZT0iYXJpYWwiIHNpemU9IjUiPg0KPGI+R0VUIEEgRlJFRSBRVU9URSBO T1chPC9iPjwvZm9udD48L2E+DQo8YnI+DQo8Zm9udCBmYWNlPSJhcmlhbCIg c2l6ZT0iMiIgY29sb3I9IiMwMDAwMDAiPg0KPGk+WW91IGNhbid0IHByZWRp Y3QgdGhlIGZ1dHVyZSwgYnV0IHlvdSBjYW4gYWx3YXlzIHByZXBhcmUgZm9y IGl0LjwvaT48L2ZvbnQ+DQoNCjwvY2VudGVyPg0KPC9ib2R5Pg0KPC9odG1s Pg0KDQpbMDMxNlVtRXcxLTc1M1FpQXU2OTE0TWZacjAtMjY1SmR2bjY0MTZG WlJKOS04N0A0M10NCg== ------=_NextPart_000_00C8_42D56D1A.D4060D84-- rmail-1.1.4/test/data/transparency/message.20000644000004100000410000000147213701337101020761 0ustar www-datawww-dataSubject: Make Life Easy # 697 X-Mailer: Mozilla 4.72 [en] (Win95; I) Mime-Version: 1.0 Date: Tue, 29 Jan 2002 22:08:31 -0500 Content-Type: multipart/mixed; boundary="----=_NextPart_000_007F_01BDF6C7.FABAC1B0" Content-Transfer-Encoding: 7bit This is a MIME Message ------=_NextPart_000_007F_01BDF6C7.FABAC1B0 Content-Type: multipart/alternative; boundary="----=_NextPart_001_0080_01BDF6C7.FABAC1B0" ------=_NextPart_001_0080_01BDF6C7.FABAC1B0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable ***** This is an HTML Message ! ***** ------=_NextPart_001_0080_01BDF6C7.FABAC1B0 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable ------=_NextPart_001_0080_01BDF6C7.FABAC1B0-- ------=_NextPart_000_007F_01BDF6C7.FABAC1B0-- rmail-1.1.4/test/data/transparency/absolute.20000644000004100000410000000002613701337101021145 0ustar www-datawww-dataFrom: matt@lickey.com rmail-1.1.4/test/data/transparency/message.40000644000004100000410000000026113701337101020756 0ustar www-datawww-dataX-Face: &(.[0qo-5}TXSwe [~Je@%;XWpck*+p#5Qc(>:.x}I$&,g9$dKJZA?n"-{$h\{**<0]C"geE3sz["SDW&!j~oBb4g1!Yk& Message body. rmail-1.1.4/test/data/transparency/absolute.40000644000004100000410000000011613701337101021147 0ustar www-datawww-dataMime-Version: 1.0 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE" rmail-1.1.4/test/data/parser.rfc8220000644000004100000410000000331613701337101016764 0ustar www-datawww-dataReturn-Path: Delivered-To: matt@lickey.com Received: from localhost (localhost [127.0.0.1]) by squeaker.lickey.com (Postfix) with ESMTP id B9ED0C0E7 for ; Sun, 10 Feb 2002 14:35:43 -0700 (MST) Received: by squeaker.lickey.com (Postfix, from userid 1000) id BE69CC0D1; Sun, 10 Feb 2002 14:35:37 -0700 (MST) To: matt@lickey.com Subject: Example of a message/rfc822 part. From: Matt Armstrong Date: Sun, 10 Feb 2002 14:35:37 -0700 X-Gnus-Mail-Source: directory:~/.incoming Message-ID: <87heop10ly.fsf@squeaker.lickey.com> User-Agent: Gnus/5.090006 (Oort Gnus v0.06) Emacs/21.1 (i386-debian-linux-gnu) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Virus-Scanned: by AMaViS snapshot-20010714 Lines: 44 Xref: squeaker.lickey.com inbox:7729 --=-=-= Below is an example of a message/rfc822 MIME part. --=-=-= Content-Type: message/rfc822 Content-Disposition: inline Return-Path: Delivered-To: matt@lickey.com Received: from localhost (localhost [127.0.0.1]) by squeaker.lickey.com (Postfix) with ESMTP id 5F0B4C0E7 for ; Sun, 10 Feb 2002 14:34:31 -0700 (MST) Received: by squeaker.lickey.com (Postfix, from userid 1000) id F0DABC0D1; Sun, 10 Feb 2002 14:34:29 -0700 (MST) To: matt@lickey.com Subject: This is a short, simple message. From: Matt Armstrong Date: Sun, 10 Feb 2002 14:34:29 -0700 Message-ID: <87n0yh10nu.fsf@squeaker.lickey.com> User-Agent: Gnus/5.090006 (Oort Gnus v0.06) Emacs/21.1 (i386-debian-linux-gnu) X-Virus-Scanned: by AMaViS snapshot-20010714 Lines: 5 Xref: squeaker.lickey.com inbox:7728 MIME-Version: 1.0 This is a short, simple message. -- matt --=-=-= -- matt --=-=-=-- rmail-1.1.4/test/data/multipart/0000755000004100000410000000000013701337101016556 5ustar www-datawww-datarmail-1.1.4/test/data/multipart/data.40000644000004100000410000000003113701337101017546 0ustar www-datawww-datapreamble --aa-- epilogue rmail-1.1.4/test/data/multipart/data.80000644000004100000410000000004313701337101017555 0ustar www-datawww-datapreamble --aa part1 --aa-- epiloguermail-1.1.4/test/data/multipart/data.100000644000004100000410000000000613701337101017625 0ustar www-datawww-data--aa--rmail-1.1.4/test/data/multipart/data.50000644000004100000410000000000713701337101017552 0ustar www-datawww-data--aa-- rmail-1.1.4/test/data/multipart/data.140000644000004100000410000000002313701337101017630 0ustar www-datawww-datapreamble --aa part1rmail-1.1.4/test/data/multipart/data.120000644000004100000410000000013613701337101017633 0ustar www-datawww-datapreamble --aaZ part1 --aa notignored part2 --aa notignored part3 --aa--notignored epilogue rmail-1.1.4/test/data/multipart/data.90000644000004100000410000000001513701337101017555 0ustar www-datawww-data --aa --aa--rmail-1.1.4/test/data/multipart/data.70000644000004100000410000000002113701337101017550 0ustar www-datawww-datapreamble --aa-- rmail-1.1.4/test/data/multipart/data.10000644000004100000410000000004413701337101017547 0ustar www-datawww-datapreamble --aa part1 --aa-- epilogue rmail-1.1.4/test/data/multipart/data.130000644000004100000410000000002413701337101017630 0ustar www-datawww-datapreamble --aa part1 rmail-1.1.4/test/data/multipart/data.160000644000004100000410000000002413701337101017633 0ustar www-datawww-datapreamble line1 line2rmail-1.1.4/test/data/multipart/data.60000644000004100000410000000001013701337101017545 0ustar www-datawww-data --aa-- rmail-1.1.4/test/data/multipart/data.150000644000004100000410000000002513701337101017633 0ustar www-datawww-datapreamble line1 line2 rmail-1.1.4/test/data/multipart/data.20000644000004100000410000000001713701337101017550 0ustar www-datawww-data --aa --aa-- rmail-1.1.4/test/data/multipart/data.170000644000004100000410000000000013701337101017626 0ustar www-datawww-datarmail-1.1.4/test/data/multipart/data.30000644000004100000410000000001413701337101017546 0ustar www-datawww-data--aa --aa-- rmail-1.1.4/test/data/multipart/data.110000644000004100000410000000010113701337101017622 0ustar www-datawww-datapreamble --aa part1 --aa part2 --aa part3 --aa-- epilogue rmail-1.1.4/test/data/parser.nested-simple30000644000004100000410000000021713701337101020607 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="X" MIME-Version: 1.0 --X Content-Type: multipart/mixed; boundary="Y" --Y --Y --Y-- --X-- rmail-1.1.4/test/data/parser.badmime20000644000004100000410000000013313701337101017430 0ustar www-datawww-dataMIME-Version: 1.0 Content-Type: multipart/alternative; boundary=X preamble --X-- epilogue rmail-1.1.4/test/data/parser.badmime10000644000004100000410000000011013701337101017422 0ustar www-datawww-dataMIME-Version: 1.0 Content-Type: multipart/alternative; boundary=X --X--rmail-1.1.4/test/data/parser.nested-simple0000644000004100000410000000024613701337101020526 0ustar www-datawww-dataContent-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= Content-Type: multipart/mixed; boundary="==-=-=" --==-=-= --==-=-= --==-=-=-- --=-=-=-- rmail-1.1.4/test/data/mbox.simple0000644000004100000410000000022213701337101016711 0ustar www-datawww-dataFrom foo@bar Wed Nov 27 12:27:32 2002 message1 From foo@bar Wed Nov 27 12:27:36 2002 message2 From foo@bar Wed Nov 27 12:27:40 2002 message3 rmail-1.1.4/test/data/parser.simple-mime0000644000004100000410000000126213701337101020172 0ustar www-datawww-dataFrom: Nathaniel Borenstein To: Ned Freed Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) Subject: Sample message MIME-Version: 1.0 Content-type: multipart/mixed; boundary="simple boundary" This is the preamble. It is to be ignored, though it is a handy place for composition agents to include an explanatory note to non-MIME conformant readers. --simple boundary This is implicitly typed plain US-ASCII text. It does NOT end with a linebreak. --simple boundary Content-type: text/plain; charset=us-ascii This is explicitly typed plain US-ASCII text. It DOES end with a linebreak. --simple boundary-- This is the epilogue. It is also to be ignored. rmail-1.1.4/test/testtranspparency.rb0000644000004100000410000000705513701337101017751 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2002, 2007 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/parser' require 'rmail/serialize' class TestRMailTransparency < TestBase def do_file(file) full_name = data_filename(file) message1 = data_as_file(file) { |f| RMail::Parser.new.parse(f) } scratch_base = file.gsub(/[^\w]/, '-') scratch_name = scratch_filename(scratch_base) message2 = File.open(scratch_name, "w+") { |f| RMail::Serialize.new(f).serialize(message1) f.seek(0) RMail::Parser.new.parse(f) } if message1 != message2 puts "-" * 70 pp message1 puts "-" * 70 pp message2 puts "-" * 70 end assert(FileUtils.compare_file(full_name, scratch_name), "parse->serialize failure transparency #{file}") assert_equal(message1, message2, "parse->serialize->parse transparency failure #{file}") end # Test that all our various input files get formatted on output the # same way they came in. def test_transparency_simple_mime do_file('parser.simple-mime') end def test_transparency_rfc822 do_file('parser.rfc822') end def test_transparency_nested_multipart do_file('parser.nested-multipart') end def test_transparency_message_01 do_file("transparency/message.1") end def test_transparency_message_02 do_file("transparency/message.2") end def test_transparency_message_03 do_file("transparency/message.3") end def test_transparency_message_04 do_file("transparency/message.4") end def test_transparency_message_05 do_file("transparency/message.5") end def test_transparency_message_06 do_file("transparency/message.6") end def test_transparency_absolute_01 do_file('transparency/absolute.1') end def test_transparency_absolute_02 do_file('transparency/absolute.2') end def test_transparency_absolute_03 do_file('transparency/absolute.3') end def test_transparency_absolute_04 do_file('transparency/absolute.4') end def test_transparency_absolute_05 do_file('transparency/absolute.5') end def test_transparency_absolute_06 do_file('transparency/absolute.6') end end rmail-1.1.4/test/testparsermultipart.rb0000644000004100000410000001757213701337101020323 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2002, 2004 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/parser/multipart' class TestRMailParserMultipart < TestBase # FIXME: TODO # - test \n -vs- \r\n -vs \r end of line characters def parse_multipart(filename, boundary, chunk_size, expected_results) assembled = nil data_as_file(filename) { |f| parser = RMail::Parser::MultipartReader.new(f, boundary) parser.chunk_size = chunk_size results = [] loop { chunk = parser.read(nil) puts "test: got part #{chunk.inspect}" if $DEBUG delimiter = parser.delimiter puts "test: got delimiter #{delimiter.inspect}" if $DEBUG if chunk assembled ||= '' assembled << chunk end if delimiter assembled ||= '' assembled << delimiter end results << [ chunk, parser.preamble?, parser.epilogue?, delimiter ] unless parser.next_part break end } if expected_results != results puts "\nfailure for chunks size #{chunk_size.to_s}" pp expected_results pp results end assert_equal(expected_results, results, "\nfile #{filename}\nchunk_size #{chunk_size}\n") } filedata = data_as_file(filename) { |f| f.read(nil) } filedata = nil if filedata.empty? assert_equal(filedata, assembled, "data loss while reassembling file data") end def for_all_chunk_sizes(filename, boundary, expected_results) size = File.stat(data_filename(filename)).size (size + 10).downto(1) { |chunk_size| parse_multipart(filename, boundary, chunk_size, expected_results) } end def test_multipart_data_01 for_all_chunk_sizes('multipart/data.1', 'aa', [ [ "preamble", true, false, "\n--aa\n" ], [ "part1", false, false, "\n--aa--\n" ], [ "epilogue\n", false, true, nil ] ]) end def test_multipart_data_02 for_all_chunk_sizes('multipart/data.2', 'aa', [ [ nil, true, false, "\n--aa\n" ], [ nil, false, false, "\n--aa--\n" ], [ "\n", false, true, nil ] ]) end def test_multipart_data_03 for_all_chunk_sizes('multipart/data.3', 'aa', [ [ nil, true, false, "--aa\n" ], [ nil, false, false, "--aa--\n" ], [ "", false, true, nil ] ]) end def test_multipart_data_04 for_all_chunk_sizes('multipart/data.4', 'aa', [ [ "preamble", true, false, "\n--aa--\n" ], [ "epilogue\n", false, true, nil ] ]) end def test_multipart_data_05 for_all_chunk_sizes('multipart/data.5', 'aa', [ [ nil, true, false, "--aa--\n" ], [ "", false, true, nil ] ]) end def test_multipart_data_06 for_all_chunk_sizes('multipart/data.6', 'aa', [ [ nil, true, false, "\n--aa--\n" ], [ "", false, true, nil ] ]) end def test_multipart_data_07 for_all_chunk_sizes('multipart/data.7', 'aa', [ [ "preamble\n", true, false, "\n--aa--\n" ], [ "", false, true, nil ] ]) end def test_multipart_data_08 for_all_chunk_sizes('multipart/data.8', 'aa', [ [ "preamble", true, false, "\n--aa\n" ], [ "part1", false, false, "\n--aa--\n" ], [ "epilogue", false, true, nil ] ]) end def test_multipart_data_09 for_all_chunk_sizes('multipart/data.9', 'aa', [ [ nil, true, false, "\n--aa\n" ], [ nil, false, false, "\n--aa--" ], [ "", false, true, nil ] ]) end def test_multipart_data_10 for_all_chunk_sizes('multipart/data.10', 'aa', [ [ nil, true, false, "--aa--" ], [ "", false, true, nil ] ]) end def test_multipart_data_11 for_all_chunk_sizes('multipart/data.11', 'aa', [ [ "preamble", true, false, "\n--aa\t\n" ], [ "part1", false, false, "\n--aa \n" ], [ "part2", false, false, "\n--aa \t \t\n" ], [ "part3", false, false, "\n--aa-- \n" ], [ "epilogue\n", false, true, nil ] ]) end def test_multipart_data_12 # The following from RFC2046 indicates that a delimiter existing # as the prefix of a line is sufficient for the line to be # considered a delimiter -- even if there is stuff after the # boundary: # # NOTE TO IMPLEMENTORS: Boundary string comparisons must # compare the boundary value with the beginning of each # candidate line. An exact match of the entire candidate line # is not required; it is sufficient that the boundary appear in # its entirety following the CRLF. # # However, messages in the field do not seem to comply with this # (namely, Eudora), so we parse more strictly. for_all_chunk_sizes('multipart/data.12', 'aa', [[ "preamble\n--aaZ\npart1\n--aa notignored\npart2\n--aa \t \tnotignored\npart3\n--aa--notignored\nepilogue\n", true, false, nil ]]) end def test_multipart_data_13 for_all_chunk_sizes('multipart/data.13', 'aa', [ [ "preamble", true, false, "\n--aa\n" ], [ "part1\n", false, false, nil ] ]) end def test_multipart_data_14 for_all_chunk_sizes('multipart/data.14', 'aa', [ [ "preamble", true, false, "\n--aa\n" ], [ "part1", false, false, nil ] ]) end def test_multipart_data_15 for_all_chunk_sizes('multipart/data.15', 'aa', [ [ "preamble\nline1\nline2\n", true, false, nil ] ]) end def test_multipart_data_16 for_all_chunk_sizes('multipart/data.16', 'aa', [ [ "preamble\nline1\nline2", true, false, nil ] ]) end def test_multipart_data_17 for_all_chunk_sizes('multipart/data.17', 'aa', [ [ nil, true, false, nil ] ]) end def test_s_new data_as_file('multipart/data.1') { |f| p = RMail::Parser::MultipartReader.new(f, "foo") assert_kind_of(RMail::Parser::MultipartReader, p) } end end rmail-1.1.4/test/testparser.rb0000644000004100000410000004437313701337101016360 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2002, 2003, 2004 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/parser' class TestRMailStreamParser < TestBase class RecordingStreamHandler def initialize(history) @history = history end def method_missing(symbol, *args) @history << [ symbol ].concat(args) end end def test_stream_parser_simple string_msg = \ 'From matt@lickey.com Mon Dec 24 00:00:06 2001 From: matt@example.net To: matt@example.com Subject: test message message body has two lines ' string_as_file(string_msg) { |f| RMail::StreamParser.parse(f, RMail::StreamHandler.new) f.rewind history = [] RMail::StreamParser.parse(f, RecordingStreamHandler.new(history)) expected = [ [:mbox_from, "From matt@lickey.com Mon Dec 24 00:00:06 2001"], [:header_field, "From: matt@example.net", "From", "matt@example.net"], [:header_field, "To: matt@example.com", "To", "matt@example.com"], [:header_field, "Subject: test message", "Subject", "test message"], [:body_begin], [:body_chunk, "message body\nhas two lines\n"], [:body_end]] assert_equal(expected, history) } end def test_stream_parser_multipart string_msg = \ 'Content-Type: multipart/mixed; boundary="aa" MIME-Version: 1.0 preamble --aa Header1: hi mom body1 --aa-- epilogue ' string_as_file(string_msg) { |f| RMail::StreamParser.parse(f, RMail::StreamHandler.new) f.rewind history = [] RMail::StreamParser.parse(f, RecordingStreamHandler.new(history)) expected = [ [:header_field, "Content-Type: multipart/mixed; boundary=\"aa\"", "Content-Type", "multipart/mixed; boundary=\"aa\""], [:header_field, "MIME-Version: 1.0", "MIME-Version", "1.0"], [:multipart_body_begin], [:preamble_chunk, "preamble"], [:part_begin], [:header_field, "Header1: hi mom", "Header1", "hi mom"], [:body_begin], [:body_chunk, "body1"], [:body_end], [:part_end], [:epilogue_chunk, "epilogue\n"], [:multipart_body_end, ["\n--aa\n", "\n--aa--\n"], "aa"] ] assert_equal(expected, history) } end end class TestRMailParser < TestBase def common_test_parse(m) assert_instance_of(RMail::Message, m) assert_equal("From matt@lickey.com Mon Dec 24 00:00:06 2001", m.header.mbox_from) assert_equal("matt@example.net", m.header[0]) assert_equal("matt@example.net", m.header['from']) assert_equal("matt@example.com", m.header[1]) assert_equal("matt@example.com", m.header['to']) assert_equal("test message", m.header[2]) assert_equal("test message", m.header['subject']) assert_equal("message body\nhas two lines\n", m.body) end def test_parse p = RMail::Parser.new string_msg = <<-EOF From matt@lickey.com Mon Dec 24 00:00:06 2001 From: matt@example.net To: matt@example.com Subject: test message message body has two lines EOF m = string_as_file(string_msg) { |f| p.parse(f) } common_test_parse(m) m = p.parse(string_msg) common_test_parse(m) end def test_parse_simple_mime p = RMail::Parser.new m = data_as_file('parser.simple-mime') { |f| p.parse(f) } assert_instance_of(RMail::Message, m) assert_equal("Nathaniel Borenstein ", m.header[0]) assert_equal("Nathaniel Borenstein ", m.header['from']) assert_equal("Ned Freed ", m.header[1]) assert_equal("Ned Freed ", m.header['To']) assert_equal("Sun, 21 Mar 1993 23:56:48 -0800 (PST)", m.header[2]) assert_equal("Sun, 21 Mar 1993 23:56:48 -0800 (PST)", m.header['Date']) assert_equal("Sun, 21 Mar 1993 23:56:48 -0800 (PST)", m.header['Date']) assert_equal("Sample message", m.header[3]) assert_equal("Sample message", m.header['Subject']) assert_equal("1.0", m.header[4]) assert_equal("1.0", m.header['MIME-Version']) assert_equal("multipart/mixed; boundary=\"simple boundary\"", m.header[5]) assert_equal("multipart/mixed; boundary=\"simple boundary\"", m.header['Content-Type']) # Verify preamble assert_equal(%q{This is the preamble. It is to be ignored, though it is a handy place for composition agents to include an explanatory note to non-MIME conformant readers. }, m.preamble) # Verify the first part assert_equal(%q{This is implicitly typed plain US-ASCII text. It does NOT end with a linebreak.}, m.part(0).body) assert_equal(nil, m.part(0).header['content-type']) # Verify the second part assert_equal(%q{This is explicitly typed plain US-ASCII text. It DOES end with a linebreak. }, m.part(1).body) assert_equal("text/plain; charset=us-ascii", m.part(1).header['content-type']) # Verify the epilogue assert_equal("\nThis is the epilogue. It is also to be ignored.\n", m.epilogue) end def test_parse_nested_simple m = data_as_file('parser.nested-simple') { |f| RMail::Parser.new.parse(f) } assert_nil(m.preamble) assert_nil(m.part(0).preamble) assert_equal("", m.part(0).epilogue) assert_equal("", m.epilogue) end def test_parser_nested_simple2 m = data_as_file('parser.nested-simple2') { |f| RMail::Parser.new.parse(f) } assert_nil(m.preamble) assert_nil(m.part(0).preamble) assert_equal("", m.part(0).epilogue) assert_equal("\n", m.epilogue) end def test_parser_nested_simple3 m = data_as_file('parser.nested-simple3') { |f| RMail::Parser.new.parse(f) } assert_equal("\n", m.preamble) assert_equal("\n", m.part(0).preamble) assert_equal("\n", m.part(0).epilogue) assert_equal("\n\n", m.epilogue) end def test_parse_nested_multipart p = RMail::Parser.new m = data_as_file('parser.nested-multipart') { |f| p.parse(f) } # Verify preamble and epilogue assert_equal("This is level 1's preamble.\n", m.preamble) assert_equal("\nThis is level 1's epilogue.\n\n", m.epilogue) # Verify a smattering of headers assert_equal("multipart/mixed; boundary=\"=-=-=\"", m.header['content-type']) assert_equal("Some nested multiparts", m.header['subject']) # Verify part 0 begin part = m.part(0) assert_equal(0, part.header.length) assert_equal("Let's see here.\n", part.body) end # Verify part 1 begin part = m.part(1) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal(1, part.header.length) assert_equal("inline", part.header['content-disposition']) assert_equal("This is the first part.\n", part.body) end # Verify part 2 begin part = m.part(2) assert_equal(1, part.header.length) assert_equal("multipart/mixed; boundary=\"==-=-=\"", part.header['content-type']) assert_equal("This is level 2's preamble.\n", part.preamble) assert_equal("This is level 2's epilogue. It has no trailing end of line.", part.epilogue) # Verify part 2.0 begin part = m.part(2).part(0) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal(1, part.header.length) assert_equal("inline", part.header['content-disposition']) assert_equal("This is the first nested part.\n", part.body) end # Verify part 2.1 begin part = m.part(2).part(1) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal(1, part.header.length) assert_equal("inline", part.header['content-disposition']) assert_equal("This is the second nested part.\n", part.body) end # Verify part 2.2 begin part = m.part(2).part(2) assert_equal(1, part.header.length) assert_equal("multipart/mixed; boundary=\"===-=-=\"", part.header['content-type']) assert_equal("This is level 3's preamble.\n", part.preamble) assert_equal("This is level 3's epilogue.\n", part.epilogue) # Verify part 2.2.0 begin part = m.part(2).part(2).part(0) assert_equal(0, part.header.length) assert_equal("This is the first doubly nested part.\n", part.body) end # Verify part 2.2.1 begin part = m.part(2).part(2).part(1) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal(1, part.header.length) assert_equal("inline", part.header['content-disposition']) assert_equal("This is the second doubly nested part.\n", part.body) end end # Verify part 3 begin part = m.part(3) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal(1, part.header.length) assert_equal("inline", part.header['content-disposition']) assert_equal("This is the third part.\n", part.body) end end end def test_parse_badmime1 p = RMail::Parser.new 1.upto(File.stat(data_filename('parser.badmime1')).size + 10) { |size| m = nil data_as_file('parser.badmime1') do |f| p.chunk_size = size assert_nothing_raised("failed for chunk size #{size.to_s}") { m = p.parse(f) } end assert_equal(m, p.parse(m.to_s)) } end def test_parse_badmime2 p = RMail::Parser.new 1.upto(File.stat(data_filename('parser.badmime2')).size + 10) { |size| m = nil data_as_file('parser.badmime2') do |f| p.chunk_size = size assert_nothing_raised("failed for chunk size #{size.to_s}") { m = p.parse(f) } end assert_equal(m, p.parse(m.to_s)) } end def test_parse_multipart_01 m = data_as_file('parser/multipart.1') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_equal("epilogue\n", m.epilogue) assert_equal(1, m.body.length) begin part = m.part(0) assert_equal(1, part.header.length) assert_nil(part.body) assert_nil(part.preamble) assert_nil(part.epilogue) assert_equal("part1", part.header[0]) end delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", "\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_02 m = data_as_file('parser/multipart.2') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("\n", m.epilogue) assert_equal(1, m.body.length) begin part = m.part(0) assert_equal(0, part.header.length) assert_nil(part.body) assert_nil(part.preamble) assert_nil(part.epilogue) end delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", "\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_03 m = data_as_file('parser/multipart.3') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("", m.epilogue) assert_equal(1, m.body.length) begin part = m.part(0) assert_equal(0, part.header.length) assert_nil(part.body) assert_nil(part.preamble) assert_nil(part.epilogue) end delimiters, delimiters_boundary = m.get_delimiters assert_equal(["--aa\n", "--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_04 m = data_as_file('parser/multipart.4') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_equal("epilogue\n", m.epilogue) assert_equal([], m.body) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_05 m = data_as_file('parser/multipart.5') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("", m.epilogue) assert_equal([], m.body) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_06 m = data_as_file('parser/multipart.6') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("", m.epilogue) assert_equal([], m.body) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_07 m = data_as_file('parser/multipart.7') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble\n", m.preamble) assert_equal("", m.epilogue) assert_equal([], m.body) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_08 m = data_as_file('parser/multipart.8') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_equal("epilogue", m.epilogue) assert_equal(1, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", "\n--aa--\n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_09 m = data_as_file('parser/multipart.9') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("", m.epilogue) assert_equal(1, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", "\n--aa--"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_10 m = data_as_file('parser/multipart.10') do |f| RMail::Parser.new.parse(f) end assert_nil(m.preamble) assert_equal("", m.epilogue) assert_equal(0, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["--aa--"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_11 m = data_as_file('parser/multipart.11') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_equal("epilogue\n", m.epilogue) assert_equal(3, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\t\n", "\n--aa \n", "\n--aa \t \t\n", "\n--aa-- \n"], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_12 m = data_as_file('parser/multipart.12') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble\n--aaZ\npart1\n--aa ignored\npart2\n--aa \t \tignored\npart3\n--aa--ignored\nepilogue\n", m.preamble) assert_nil(m.epilogue) assert_equal(0, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal([""], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_13 m = data_as_file('parser/multipart.13') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_nil(m.epilogue) assert_equal(1, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", ""], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_14 m = data_as_file('parser/multipart.14') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble", m.preamble) assert_nil(m.epilogue) assert_equal(1, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal(["\n--aa\n", ""], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_15 m = data_as_file('parser/multipart.15') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble\nline1\nline2\n", m.preamble) assert_nil(m.epilogue) assert_equal(0, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal([""], delimiters) assert_equal("aa", delimiters_boundary) end def test_parse_multipart_16 m = data_as_file('parser/multipart.16') do |f| RMail::Parser.new.parse(f) end assert_equal("preamble\nline1\nline2", m.preamble) assert_nil(m.epilogue) assert_equal(0, m.body.length) delimiters, delimiters_boundary = m.get_delimiters assert_equal([""], delimiters) assert_equal("aa", delimiters_boundary) end def test_rmail_parser_s_read string_msg = <<-EOF From matt@lickey.com Mon Dec 24 00:00:06 2001 From: matt@example.net To: matt@example.com Subject: test message message body has two lines EOF m = string_as_file(string_msg) { |f| RMail::Parser.read(f) } common_test_parse(m) m = RMail::Parser.read(string_msg) common_test_parse(m) end def test_s_new p = RMail::Parser.new assert_instance_of(RMail::Parser, p) end end rmail-1.1.4/test/testbase.rb0000644000004100000410000001321213701337101015762 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2001, 2002, 2003, 2007 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Base for all the test cases, providing a default setup and teardown if ENV["COVERAGE"] || ENV["TRAVIS"] require "simplecov" SimpleCov.start do add_filter "/test/" end top = File.expand_path("../..", __FILE__) # track all ruby files under lib SimpleCov.track_files("#{top}/lib/**/*.rb") # use coveralls for on-line code coverage reporting at Travis CI if ENV["TRAVIS"] require "coveralls" SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ] end SimpleCov.start end require 'test/unit' require 'rbconfig.rb' require 'tempfile' require 'find' require 'fileutils' begin require 'pp' rescue LoadError end class TestBase < Test::Unit::TestCase include RbConfig attr_reader :scratch_dir def test_nothing assert(true) # Appease Test::Unit end # Print a string to a temporary file and return the file opened. # This lets you have some test data in a string, but access it with # a file. def string_as_file(string, strip_whitespace = true) if strip_whitespace temp = "" string.each_line { |line| temp += line.sub(/^[ \t]+/, '') } string = temp end file = Tempfile.new("ruby.string_as_file.") begin file.print(string) file.close() file.open() yield file ensure file.close(true) end end # Return true if the given file contains a line matching regexp def file_contains(filename, regexp) unless regexp.kind_of?(Regexp) regexp = Regexp.new(regexp) end detected = nil File.open(filename) { |f| detected = f.detect { |line| line =~ regexp } } ! detected.nil? end # Deletes everything in directory +dir+, including any # subdirectories def cleandir(dir) if FileTest.directory?(dir) files = [] Find.find(dir) { |f| files.push(f) } files.shift # get rid of 'dir' files.reverse_each { |f| if FileTest.directory?(f) Dir.delete(f) else File.delete(f) end } end end def setup @scratch_dir = File.join(Dir.getwd, "_scratch_" + name) @data_dir = File.join(Dir.getwd, "test", "data") @scratch_hash = {} cleandir(@scratch_dir) Dir.rmdir(@scratch_dir) if FileTest.directory?(@scratch_dir) Dir.mkdir(@scratch_dir) unless FileTest.directory?(@scratch_dir) end def ruby_program File.join(CONFIG['bindir'], CONFIG['ruby_install_name']) end def data_filename(name) File.join(@data_dir, name) end def data_as_file(name) unless name =~ %r{^/} name = data_filename(name) end File.open(name) { |f| yield f } rescue Errno::ENOENT flunk("data file #{name.inspect} does not exist") end def data_as_string(name) data_as_file(name) { |f| f.read } end def scratch_filename(name) if @scratch_hash.key?(name) temp = @scratch_hash[name] temp = temp.succ @scratch_hash[name] = name = temp else temp = name.dup temp << '.0' unless temp =~ /\.\d+$/ @scratch_hash[name] = temp end File.join(@scratch_dir, name) end def scratch_file_write(name) name = scratch_filename(name) File.open(name, 'w') { |f| yield f } end def teardown unless $! || ((defined? passed?) && !passed?) cleandir(@scratch_dir) Dir.rmdir(@scratch_dir) if FileTest.directory?(@scratch_dir) end end def call_fails(arg, &block) begin yield arg rescue Exception return true end return false end # if a random string failes, run it through this function to find the # shortest fail case def find_shortest_failure(str, &block) unless call_fails(str, &block) raise "hey, the input didn't fail!" else # Chop off stuff from the beginning and then the end # until it stops failing bad = str 0.upto(bad.length) {|index| bad.length.downto(1) {|length| begin loop { s = bad.dup s[index,length] = '' break if bad == s break unless call_fails(s, &block) bad = s } rescue IndexError break end } } raise "shortest failure is #{bad.inspect}" end end end rmail-1.1.4/test/testmessage.rb0000644000004100000410000002044713701337101016504 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2001, 2002 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'rmail/message' require 'test/testbase' class TestRMailMessage < TestBase def setup super @the_mail_file = File.join(scratch_dir, "mail_file") @the_mail = %q{From: somedude@example.com To: someotherdude@example.com Subject: this is some mail First body line. Second body line. } File.open(@the_mail_file, "w") { |file| file.print(@the_mail) } # Test reading in a mail file that has a bad header. This makes # sure we consider the message header to be everything up to the # first blank line. @the_mail_file_2 = File.join(scratch_dir, "mail_file_2") @the_mail_2 = %q{From: somedude@example.com To: someotherdude@example.com this is not a valid header Subject: this is some mail First body line Second body line } File.open(@the_mail_file_2, "w") { |file| file.print(@the_mail_2) } end def verify_message_interface(message) assert_not_nil(message) assert_kind_of(RMail::Message, message) assert_not_nil(message.header) assert_kind_of(RMail::Header, message.header) assert_kind_of(Enumerable, message.header, "RMail::Message.body should be an Enumerable") end def test_initialize # Make sure an empty message actually is empty message = RMail::Message.new verify_message_interface(message) assert_equal(message.header.length, 0) assert_nil(message.body) end def test_EQUAL m1 = RMail::Message.new m2 = RMail::Message.new assert(m1 == m2) m1.header['To'] = 'bob' assert(m1 != m2) m2.header['To'] = 'bob' assert(m1 == m2) m1.preamble = 'the preamble' assert(m1 != m2) m2.preamble = 'the preamble' assert(m1 == m2) m1.epilogue = 'the epilogue' assert(m1 != m2) m2.epilogue = 'the epilogue' assert(m1 == m2) m1.body = "the body" assert(m1 != m2) m2.body = "the body" assert(m1 == m2) m3 = RMail::Message.new m3.add_part(m1) m4 = RMail::Message.new m4.add_part(m2) assert(m3 == m4) m1.body = 'the other body' assert(m3 != m4) end def test_multipart? message = RMail::Message.new assert_equal(false, message.multipart?) message.add_part("This is a part.") assert_equal(true, message.multipart?) message.add_part("This is another part.") assert_equal(true, message.multipart?) end def test_add_part message = RMail::Message.new part_a = Object.new part_b = Object.new message.add_part(part_a) message.add_part(part_b) assert_same(part_a, message.part(0)) assert_same(part_b, message.part(1)) end def test_decode message = RMail::Message.new all_bytes = ''.force_encoding('ASCII-8BIT') 0.upto(255) do |i| all_bytes << i end # These are base64 encoded strings that hold the data we'd really # like to test. This avoids any problems with editors, # etc. stripping tabs or having screwed fontification, etc. base64_data = "CkFBRUNBd1FGQmdjSUNRb0xEQTBPRHhBUkVoTVVGUllYR0JrYUd4d2RIaDhn\nSVNJakpDVW1KeWdwS2lzc0xTNHZNREV5TXpRMQpOamM0T1RvN1BEMCtQMEJC\nUWtORVJVWkhTRWxLUzB4TlRrOVFVVkpUVkZWV1YxaFpXbHRjWFY1ZllHRmlZ\nMlJsWm1kb2FXcHIKYkcxdWIzQnhjbk4wZFhaM2VIbDZlM3g5Zm4rQWdZS0Ro\nSVdHaDRpSmlvdU1qWTZQa0pHU2s1U1ZscGVZbVpxYm5KMmVuNkNoCm9xT2tw\nYWFucUttcXE2eXRycSt3c2JLenRMVzJ0N2k1dXJ1OHZiNi93TUhDdzhURnhz\nZkl5Y3JMek0zT3o5RFIwdFBVMWRiWAoyTm5hMjl6ZDN0L2c0ZUxqNU9YbTUr\nanA2dXZzN2U3djhQSHk4L1QxOXZmNCtmcjcvUDMrL3c9PQo9MDA9MDE9MDI9\nMDM9MDQ9MDU9MDY9MDc9MDgJPTBBPTBCPTBDPTBEPTBFPTBGPTEwPTExPTEy\nPTEzPTE0PTE1PTE2PTE3PTE4PQo9MTk9MUE9MUI9MUM9MUQ9MUU9MUYgISIj\nJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0zRD4/QEFCQ0RFRkdISUpLTE1O\nT1BRUlM9Cgo=\n".unpack("m*").first qp_data = "PTAwPTAxPTAyPTAzPTA0PTA1PTA2PTA3PTA4CT0wQT0wQj0wQz0wRD0wRT0w\nRj0xMD0xMT0xMj0xMz0xND0xNT0xNj0xNz0xOD0KPTE5PTFBPTFCPTFDPTFE\nPTFFPTFGICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9M0Q+P0BBQkNE\nRUZHSElKS0xNTk9QUVJTPQpUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5v\ncHFyc3R1dnd4eXp7fH1+PTdGPTgwPTgxPTgyPTgzPTg0PTg1PTg2PTg3PTg4\nPQo9ODk9OEE9OEI9OEM9OEQ9OEU9OEY9OTA9OTE9OTI9OTM9OTQ9OTU9OTY9\nOTc9OTg9OTk9OUE9OUI9OUM9OUQ9OUU9OUY9QTA9QTE9Cj1BMj1BMz1BND1B\nNT1BNj1BNz1BOD1BOT1BQT1BQj1BQz1BRD1BRT1BRj1CMD1CMT1CMj1CMz1C\nND1CNT1CNj1CNz1COD1COT1CQT0KPUJCPUJDPUJEPUJFPUJGPUMwPUMxPUMy\nPUMzPUM0PUM1PUM2PUM3PUM4PUM5PUNBPUNCPUNDPUNEPUNFPUNGPUQwPUQx\nPUQyPUQzPQo9RDQ9RDU9RDY9RDc9RDg9RDk9REE9REI9REM9REQ9REU9REY9\nRTA9RTE9RTI9RTM9RTQ9RTU9RTY9RTc9RTg9RTk9RUE9RUI9RUM9Cj1FRD1F\nRT1FRj1GMD1GMT1GMj1GMz1GND1GNT1GNj1GNz1GOD1GOT1GQT1GQj1GQz1G\nRD1GRT1GRg==\n".unpack("m*").first base64_message = RMail::Message.new base64_message.header['Content-Transfer-Encoding'] = ' base64 ' base64_message.body = base64_data message.add_part(base64_message) qp_message = RMail::Message.new qp_message.header['Content-Transfer-Encoding'] = ' quoted-printable ' qp_message.body = qp_data message.add_part(qp_message) e = assert_raise(TypeError) { message.decode } assert_equal('Can not decode a multipart message.', e.message) assert_equal(base64_message, message.part(0)) assert_equal(qp_message, message.part(1)) assert_equal(base64_data, message.part(0).body) assert_equal(qp_data, message.part(1).body) assert_equal(all_bytes, message.part(0).decode) assert_equal(all_bytes, message.part(1).decode) end def test_part message = RMail::Message.new e = assert_raise(TypeError) { message.part(0) } assert_equal('Can not get part on a single part message.', e.message) first = RMail::Message.new message.add_part(first) second = RMail::Message.new message.add_part(second) assert_equal(first, message.part(0)) assert_equal(second, message.part(1)) end def test_preamble m = RMail::Message.new assert_nil(m.preamble) m.preamble = "hello bob" assert_equal("hello bob", m.preamble) m.preamble = "hello bob\n" assert_equal("hello bob\n", m.preamble) end def test_epilogue m = RMail::Message.new assert_nil(m.epilogue) m.epilogue = "hello bob" assert_equal("hello bob", m.epilogue) m.epilogue = "hello bob\n" assert_equal("hello bob\n", m.epilogue) end def test_to_s begin m = RMail::Message.new m.header['To'] = 'bob@example.net' m.header['From'] = 'sam@example.com' m.header['Subject'] = 'hi bob' m.body = "Just wanted to say Hi!\n" desired = %q{To: bob@example.net From: sam@example.com Subject: hi bob Just wanted to say Hi! } assert_equal(desired, m.to_s) end begin m = RMail::Message.new m.header.set_boundary('=-=-=') part1 = RMail::Message.new part1.body = "part1 body" part2 = RMail::Message.new part2.body = "part2 body" m.add_part(part1) m.add_part(part2) assert_equal(%q{Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= part1 body --=-=-= part2 body --=-=-=-- }, m.to_s) end end end rmail-1.1.4/test/testmailbox.rb0000644000004100000410000000401313701337101016502 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (c) 2002 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/mailbox' class TestRMailMailbox < TestBase def test_parse_mbox_simple expected = ["From foo@bar Wed Nov 27 12:27:32 2002\nmessage1\n", "From foo@bar Wed Nov 27 12:27:36 2002\nmessage2\n", "From foo@bar Wed Nov 27 12:27:40 2002\nmessage3\n"] data_as_file("mbox.simple") { |f| assert_equal(expected, RMail::Mailbox::parse_mbox(f)) } data_as_file("mbox.simple") { |f| messages = [] RMail::Mailbox::parse_mbox(f) { |m| messages << m } assert_equal(expected, messages) } end end rmail-1.1.4/test/testtestbase.rb0000644000004100000410000000755613701337101016700 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2001, 2002, 2007 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Test the TestBase class itself require 'test/testbase.rb' class TestTestBase < TestBase def test_cleandir Dir.mkdir("_testdir_") Dir.mkdir("_testdir_/testsubdir") File.open("_testdir_/testfile", "w") { |file| file.puts "some data" } File.open("_testdir_/testsubdir/testfile", "w") { |file| file.puts "some data" } assert(test(?e, "_testdir_")) assert(test(?e, "_testdir_/testsubdir")) assert(test(?e, "_testdir_/testsubdir/testfile")) assert(test(?e, "_testdir_/testfile")) cleandir("_testdir_") assert(test(?e, "_testdir_")) assert(!test(?e, "_testdir_/testsubdir")) assert(!test(?e, "_testdir_/testsubdir/testfile")) assert(!test(?e, "_testdir_/testfile")) assert_equal(0, Dir.delete('_testdir_')) end def test_file_contains scratch = scratch_filename("file_contains") File.open(scratch, "w") { |f| f.puts "contains AAA" f.puts "contains BBB" } assert(file_contains(scratch, /BBB/)) assert(file_contains(scratch, "AAA")) assert(file_contains(scratch, /contains AAA/)) assert(file_contains(scratch, "contains BBB")) assert_equal(false, file_contains(scratch, /contains CCC/)) assert_equal(false, file_contains(scratch, "contains CCC")) end def test_ruby_program assert_not_nil(ruby_program) assert_kind_of(String, ruby_program) end def verify_scratch_dir_name(dir) assert_match(/_scratch.*TestTestBase/, dir) end def test_name assert_match(/\btest_name\b/, name) assert_match(/\bTestTestBase\b/, name) end def test_scratch_dir assert_not_nil(scratch_dir) assert_kind_of(String, scratch_dir) verify_scratch_dir_name(scratch_dir) end def test_scratch_filename name = scratch_filename("foobar") assert_kind_of(String, name) verify_scratch_dir_name(File.dirname(name)) assert_equal("foobar", File.basename(name)) name = scratch_filename("foobar") assert_kind_of(String, name) verify_scratch_dir_name(File.dirname(name)) assert_equal("foobar.1", File.basename(name)) end def test_string_as_file string = "yo\nman\n" string_as_file(string) { |f| assert_equal(string, f.readlines.join('')) } string2 = " yo\n man\n" string_as_file(string2) { |f| assert_equal("yo\nman\n", f.readlines.join('')) } string_as_file(string2, false) { |f| assert_equal(string2, f.readlines.join('')) } end end rmail-1.1.4/test/testserialize.rb0000644000004100000410000001563613701337101017053 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/serialize' require 'rmail/message' class TestRMailSerialize < TestBase def test_serialize_empty s = RMail::Serialize.new('').serialize(RMail::Message.new) assert_equal("", s) end def test_serialize_basic m = RMail::Message.new m.header['to'] = "bob@example.net" m.header['from'] = "sally@example.net" m.body = "This is the body." s = RMail::Serialize.new('').serialize(m) assert_equal(%q{to: bob@example.net from: sally@example.net This is the body. }, s) end def test_serialize_s_write m = RMail::Message.new m.header['to'] = "bob@example.net" m.header['from'] = "sally@example.net" m.body = "This is the body." s = RMail::Serialize.write('', m) assert_equal(%q{to: bob@example.net from: sally@example.net This is the body. }, s) end def test_serialize_boundary_generation m = RMail::Message.new m.add_part(RMail::Message.new) m.add_part(RMail::Message.new) m.part(0).body = "body0\n" m.part(1).body = "body1\n" m.to_s assert_match(/^=-\d+-\d+-\d+-\d+-\d+-=$/, m.header.param('content-type', 'boundary')) end def test_serialize_boundary_override m = RMail::Message.new m.add_part(RMail::Message.new) m.header.set_boundary("a") m.part(0).add_part(RMail::Message.new) m.part(0).header.set_boundary("a") m.to_s assert_match(/^=-\d+-\d+-\d+-\d+-\d+-=$/, m.part(0).header.param('content-type', 'boundary')) assert_equal("a", m.header.param('content-type', 'boundary')) end def test_serialize_multipart_basic m = RMail::Message.new m.header['to'] = "bob@example.net" m.header['from'] = "sally@example.net" m.header.set_boundary('=-=-=') part = RMail::Message.new part.body = "This is a text/plain part." m.add_part(part) part = RMail::Message.new part.body = "This is a whacked out wacky part.\n" part.header['Content-Disposition'] = 'inline' m.add_part(part) part = RMail::Message.new part.body = "This is another whacked out wacky part.\n\n" part.header['Content-Disposition'] = 'inline' m.add_part(part) s = RMail::Serialize.new('').serialize(m) assert_equal(%q{to: bob@example.net from: sally@example.net Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= This is a text/plain part. --=-=-= Content-Disposition: inline This is a whacked out wacky part. --=-=-= Content-Disposition: inline This is another whacked out wacky part. --=-=-=-- }, s) end def test_serialize_multipart_nested m = RMail::Message.new m.header.set_boundary('=-=-=') part = RMail::Message.new m.add_part(part) m.part(0).header.set_boundary('==-=-=') part = RMail::Message.new m.part(0).add_part(RMail::Message.new) part = RMail::Message.new m.part(0).add_part(RMail::Message.new) s = RMail::Serialize.new('').serialize(m) assert_equal(%q{Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= Content-Type: multipart/mixed; boundary="==-=-=" --==-=-= --==-=-= --==-=-=-- --=-=-=-- }, s) end def test_serialize_multipart_epilogue_preamble m = RMail::Message.new m.preamble = %q{This is a multipart message in MIME format. You are not using a message reader that understands MIME format. Sucks to be you.} m.epilogue = %q{This is the end of a multipart message in MIME format. You are not using a message reader that understands MIME format. Sucks to be you. } m.header['to'] = "bob@example.net" m.header['from'] = "sally@example.net" m.header.set_boundary('=-=-=') part = RMail::Message.new part.preamble = "SHOULD NOT SHOW UP" part.body = "This is the body of the first part." part.epilogue = "SHOULD NOT SHOW UP" m.add_part(part) part = RMail::Message.new part.body = "This is the body of the second part.\n" part.header['Content-Disposition'] = 'inline' m.add_part(part) part = RMail::Message.new part.body = "This is the body of the third part.\n\n" part.header['Content-Disposition'] = "inline\n" part.header['X-Silly-Header'] = "silly value\n" m.add_part(part) s = RMail::Serialize.new('').serialize(m) assert_equal(%q{to: bob@example.net from: sally@example.net Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 This is a multipart message in MIME format. You are not using a message reader that understands MIME format. Sucks to be you. --=-=-= This is the body of the first part. --=-=-= Content-Disposition: inline This is the body of the second part. --=-=-= Content-Disposition: inline X-Silly-Header: silly value This is the body of the third part. --=-=-=-- This is the end of a multipart message in MIME format. You are not using a message reader that understands MIME format. Sucks to be you. }, s) m.epilogue = "foo\n\n" s = RMail::Serialize.new('').serialize(m) assert_equal(%q{to: bob@example.net from: sally@example.net Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 This is a multipart message in MIME format. You are not using a message reader that understands MIME format. Sucks to be you. --=-=-= This is the body of the first part. --=-=-= Content-Disposition: inline This is the body of the second part. --=-=-= Content-Disposition: inline X-Silly-Header: silly value This is the body of the third part. --=-=-=-- foo }, s) end end rmail-1.1.4/test/testaddress.rb0000644000004100000410000011243613701337101016505 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (C) 2001, 2002, 2003, 2007, 2008 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # require 'test/testbase' require 'rmail/address' class TestRMailAddress < TestBase def domain_optional # Set to true for tests that include addresses without a domain # portion. false end def method_list [:display_name, :name, :address, :comments, :format, :domain, :local] end def validate_method(object, method, *args) assert(method_list.include?(method), "#{method.inspect} not in #{method_list.inspect}") assert_respond_to(object, method) ret = nil ret = object.send(method, *args) if block_given? yield(ret) end end def validate_interface(address) assert_instance_of(RMail::Address, address) validate_method(address, :display_name) { |ret| assert_instance_of(String, ret) unless ret.nil? } validate_method(address, :name) { |ret| assert_instance_of(String, ret) unless ret.nil? } validate_method(address, :address) { |ret| assert_instance_of(String, ret) unless ret.nil? } validate_method(address, :comments) { |ret| unless ret.nil? assert_instance_of(Array, ret) ret.each { |comment| assert_instance_of(String, comment) } end } validate_method(address, :format) { |ret| assert_instance_of(String, ret) unless ret.nil? } validate_method(address, :domain) { |ret| assert_instance_of(String, ret) unless ret.nil? } validate_method(address, :local) { |ret| assert_instance_of(String, ret) unless ret.nil? } end def validate_case(testcase, debug = false) begin prev_debug = $DEBUG $DEBUG = debug results = RMail::Address.parse(testcase[0]) ensure $DEBUG = prev_debug end assert_kind_of(Array, results) assert_kind_of(RMail::Address::List, results) results.each { |address| validate_interface(address) } expected_results = testcase[1] assert_instance_of(Array, expected_results) assert_equal(expected_results.length, results.length, "results array wrong length, got #{results.inspect}") results.each_with_index { |address, i| assert_instance_of(Hash, expected_results[i]) methods = method_list expected_results[i].each { |method, expected| validate_method(address, method) { |ret| assert_equal(expected, ret, "\nstring #{testcase[0].inspect}\naddr #{address.inspect}\nmethod #{method.inspect}\n") } methods.delete(method) } assert_equal(0, methods.length, "test case did not test these methods #{methods.inspect}") } end def test_rfc_2822() # The following are from RFC2822 validate_case\ ['John Doe ', [ { :name => 'John Doe', :display_name => 'John Doe', :address => 'jdoe@machine.example', :comments => nil, :domain => 'machine.example', :local => 'jdoe', :format => 'John Doe ' } ]] validate_case\ [' Mary Smith ', [ { :name => 'Mary Smith', :display_name => 'Mary Smith', :address => 'mary@example.net', :comments => nil, :domain => 'example.net', :local => 'mary', :format => 'Mary Smith ' } ]] validate_case\ ['"Joe Q. Public" ', [ { :name => 'Joe Q. Public', :display_name => 'Joe Q. Public', :address => 'john.q.public@example.com', :comments => nil, :domain => 'example.com', :local => 'john.q.public', :format => '"Joe Q. Public" ' } ]] validate_case\ ['Mary Smith , jdoe@example.org, Who? ', [ { :name => 'Mary Smith', :display_name => 'Mary Smith', :address => 'mary@x.test', :comments => nil, :domain => 'x.test', :local => 'mary', :format => 'Mary Smith ' }, { :name => nil, :display_name => nil, :address => 'jdoe@example.org', :comments => nil, :domain => 'example.org', :local => 'jdoe', :format => 'jdoe@example.org' }, { :name => 'Who?', :display_name => 'Who?', :address => 'one@y.test', :comments => nil, :domain => 'y.test', :local => 'one', :format => 'Who? ' } ]] validate_case\ [', "Giant; \"Big\" Box" ', [ { :name => nil, :display_name => nil, :address => 'boss@nil.test', :comments => nil, :domain => 'nil.test', :local => 'boss', :format => 'boss@nil.test' }, { :name => 'Giant; "Big" Box', :display_name => 'Giant; "Big" Box', :address => 'sysservices@example.net', :comments => nil, :domain => 'example.net', :local => 'sysservices', :format => '"Giant; \"Big\" Box" ' } ] ] validate_case\ ['A Group:Chris Jones ,joe@where.test,John ;', [ { :name => 'Chris Jones', :display_name => 'Chris Jones', :address => 'c@a.test', :comments => nil, :domain => 'a.test', :local => 'c', :format => 'Chris Jones ' }, { :name => nil, :display_name => nil, :address => 'joe@where.test', :comments => nil, :domain => 'where.test', :local => 'joe', :format => 'joe@where.test' }, { :name => 'John', :display_name => 'John', :address => 'jdoe@one.test', :comments => nil, :domain => 'one.test', :local => 'jdoe', :format => 'John ' } ] ] validate_case\ ['Undisclosed recipients:;', [] ] validate_case\ ['undisclosed recipients: ;', [] ] validate_case\ ['"Mary Smith: Personal Account" ', [ { :name => 'Mary Smith: Personal Account', :display_name => 'Mary Smith: Personal Account', :address => 'smith@home.example', :comments => nil, :domain => 'home.example', :local => 'smith', :format => '"Mary Smith: Personal Account" ' } ] ] validate_case\ ['Pete(A wonderful \) chap) ', [ { :name => 'Pete', :display_name => 'Pete', :address => 'pete@silly.test', :comments => ['A wonderful ) chap', 'his account', 'his host'], :domain => 'silly.test', :local => 'pete', :format => 'Pete (A wonderful \) chap) (his account) (his host)' } ] ] validate_case\ ['A Group:a@b.c,d@e.f;', [ { :name => nil, :display_name => nil, :comments => nil, :local => 'a', :domain => 'b.c', :format => 'a@b.c', :address => 'a@b.c' }, { :name => nil, :display_name => nil, :comments => nil, :local => 'd', :domain => 'e.f', :format => 'd@e.f', :address => 'd@e.f' } ] ] validate_case\ ["A Group(Some people)\r\n :Chris Jones ,\r\n joe@example.org", [ { :name => 'Chris Jones', :display_name => 'Chris Jones', :address => 'c@public.example', :comments => ['Chris\'s host.'], :domain => 'public.example', :local => 'c', :format => 'Chris Jones (Chris\'s host.)' } ] ] validate_case\ ["A Group(Some people)\r\n :Chris Jones ,\r\n joe@example.org;", [ { :name => 'Chris Jones', :display_name => 'Chris Jones', :address => 'c@public.example', :comments => ['Chris\'s host.'], :domain => 'public.example', :local => 'c', :format => 'Chris Jones (Chris\'s host.)' }, { :name => nil, :display_name => nil, :address => 'joe@example.org', :comments => nil, :domain => 'example.org', :local => 'joe', :format => 'joe@example.org' } ] ] validate_case\ ['(Empty list)(start)Undisclosed recipients :(nobody(that I know)) ;', [] ] # Note, the space is lost after the Q. because we always convert # word . word into "word.word" for output. To preserve the space, # the guy should quote his name. # FIXME: maybe fix this behavior? validate_case\ ['Joe Q. Public ', [ { :name => 'Joe Q.Public', :display_name => 'Joe Q.Public', :address => 'john.q.public@example.com', :comments => nil, :domain => 'example.com', :local => 'john.q.public', :format => '"Joe Q.Public" ' } ] ] # FIXME: maybe expect "B.J.Major" validate_case\ ['B.J. Major ', [ { :name => 'B.J.Major', :display_name => 'B.J.Major', :address => 'bjbear71@example.net', :comments => nil, :domain => 'example.net', :local => 'bjbear71', :format => '"B.J.Major" ' } ] ] validate_case\ ['jdoe@test . example', [ { :name => nil, :display_name => nil, :address => 'jdoe@test.example', :comments => nil, :domain => 'test.example', :local => 'jdoe', :format => 'jdoe@test.example' } ] ] validate_case\ ['Mary Smith <@machine.tld:mary@example.net>, , jdoe@test . example', [ { :name => 'Mary Smith', :display_name => 'Mary Smith', :address => 'mary@example.net', :comments => nil, :domain => 'example.net', :local => 'mary', :format => 'Mary Smith ' }, { :name => nil, :display_name => nil, :address => 'jdoe@test.example', :comments => nil, :domain => 'test.example', :local => 'jdoe', :format => 'jdoe@test.example' } ] ] validate_case\ ['John Doe ', [ { :name => 'John Doe', :display_name => 'John Doe', :address => 'jdoe@machine.example', :comments => ['comment'], :domain => 'machine.example', :local => 'jdoe', :format => 'John Doe (comment)' } ] ] validate_case\ ["Mary Smith\n\r \n ", [ { :name => 'Mary Smith', :display_name => 'Mary Smith', :address => 'mary@example.net', :comments => nil, :domain => 'example.net', :local => 'mary', :format => 'Mary Smith ' } ] ] end # Tests stolen from majordomoII def test_majordomoII validate_case\ ["tibbs@uh.edu (Homey ( j (\(\() t ) Tibbs)", [ { :name => 'Homey ( j ((() t ) Tibbs', :display_name => nil, :address => 'tibbs@uh.edu', :domain => 'uh.edu', :local => 'tibbs', :comments => [ 'Homey ( j ((() t ) Tibbs' ], :format => 'tibbs@uh.edu (Homey \( j \(\(\(\) t \) Tibbs)' } ] ] validate_case\ ['"tibbs@home"@hpc.uh.edu (JLT )', [ { :name => 'JLT ', :address => 'tibbs@home@hpc.uh.edu', :display_name => nil, :domain => 'hpc.uh.edu', :local => 'tibbs@home', :comments => [ 'JLT ' ], :format => '"tibbs@home"@hpc.uh.edu (JLT )' } ] ] validate_case\ ['tibbs@[129.7.3.5]', [ { :name => nil, :display_name => nil, :domain => '[129.7.3.5]', :local => 'tibbs', :comments => nil, :address => 'tibbs@[129.7.3.5]', :format => '' } ] ] validate_case\ ['A_Foriegner%across.the.pond@relay1.uu.net', [ { :name => nil, :display_name => nil, :domain => 'relay1.uu.net', :local => 'A_Foriegner%across.the.pond', :comments => nil, :address => 'A_Foriegner%across.the.pond@relay1.uu.net', :format => 'A_Foriegner%across.the.pond@relay1.uu.net' } ] ] validate_case\ ['"Jason @ Tibbitts" ', [ { :name => 'Jason @ Tibbitts', :display_name => 'Jason @ Tibbitts', :domain => 'uh.edu', :local => 'tibbs', :comments => nil, :address => 'tibbs@uh.edu', :format => '"Jason @ Tibbitts" ' } ] ] # Majordomo II parses all of these with an error. We have deleted # some of the tests when they are actually legal. validate_case ['tibbs@uh.edu Jason Tibbitts', [] ] validate_case ['@uh.edu', [] ] # Can't start with @ validate_case ['J ', [] ] # Not FQDN validate_case ['>', [] ] # Unbalanced validate_case ['tibbs, nobody', [] ] # Multiple addresses not allowed validate_case ['tibbs@.hpc', [] ] # Illegal @. validate_case ['@c', [] ] # @ illegal in phrase validate_case ['', [] ] # No hostname validate_case ['.abc', [] ] # >. illegal validate_case [' blah ', [] ] # Two routes illegal validate_case ['@hpc.uh.edu>', [] ] # Nested routes illegal validate_case ['[]', [] ] # Enclosed in [] validate_case [' Me [blurfl] U', [] ] # Domain literals illegal in comment validate_case ['A B @ C ', [] ] # @ not legal in comment validate_case ['blah . tibbs@', [] ] # Unquoted . not legal in comment validate_case ['tibbs@hpc.uh.edu.', [] ] # Address ends with a dot validate_case ['sina.hpc.uh.edu', [] ] # No local-part@ validate_case ['tibbs@@math.uh.edu', [] ] # Two @s next to each other validate_case ['tibbs@math@uh.edu', [] ] # Two @s, not next to each other end def test_mailtools_suite() # # The following are from the Perl MailTools module version 1.40 # validate_case\ ['"Joe & J. Harvey" , JJV @ BBN', [ { :name => 'Joe & J. Harvey', :display_name => 'Joe & J. Harvey', :address => 'ddd@Org', :comments => nil, :domain => 'Org', :local => 'ddd', :format => '"Joe & J. Harvey" ' }, { :name => nil, :display_name => nil, :address => 'JJV@BBN', :comments => nil, :domain => 'BBN', :local => 'JJV', :format => 'JJV@BBN' } ] ] validate_case\ ['"spickett@tiac.net" ', [ { :name => 'spickett@tiac.net', :display_name => 'spickett@tiac.net', :address => 'Sean.Pickett@zork.tiac.net', :comments => nil, :domain => 'zork.tiac.net', :local => 'Sean.Pickett', :format => '"spickett@tiac.net" ' } ] ] validate_case\ ['rls@intgp8.ih.att.com (-Schieve,R.L.)', [ { :name => '-Schieve,R.L.', :display_name => nil, :address => 'rls@intgp8.ih.att.com', :comments => ['-Schieve,R.L.'], :domain => 'intgp8.ih.att.com', :local => 'rls', :format => 'rls@intgp8.ih.att.com (-Schieve,R.L.)' } ] ] validate_case\ ['jrh%cup.portal.com@portal.unix.portal.com', [ { :name => nil, :display_name => nil, :address => 'jrh%cup.portal.com@portal.unix.portal.com', :comments => nil, :domain => 'portal.unix.portal.com', :local => 'jrh%cup.portal.com', :format => 'jrh%cup.portal.com@portal.unix.portal.com' } ] ] validate_case\ ['astrachan@austlcm.sps.mot.com (\'paul astrachan/xvt3\')', [ { :name => '\'paul astrachan/xvt3\'', :display_name => nil, :address => 'astrachan@austlcm.sps.mot.com', :comments => ['\'paul astrachan/xvt3\''], :domain => 'austlcm.sps.mot.com', :local => 'astrachan', :format => 'astrachan@austlcm.sps.mot.com (\'paul astrachan/xvt3\')' } ] ] validate_case\ ['TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD)', [ { :name => 'JAMES R. TWINE - THE NERD', :display_name => nil, :address => 'TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU', :comments => ['JAMES R. TWINE - THE NERD'], :domain => 'SNYBUF.CS.SNYBUF.EDU', :local => 'TWINE57%SDELVB.decnet', :format => 'TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD)'} ] ] validate_case\ ['David Apfelbaum ', [ { :name => 'David Apfelbaum', :display_name => 'David Apfelbaum', :address => 'da0g+@andrew.cmu.edu', :comments => nil, :domain => 'andrew.cmu.edu', :local => 'da0g+', :format => 'David Apfelbaum ' } ] ] validate_case\ ['"JAMES R. TWINE - THE NERD" ', [ { :name => 'JAMES R. TWINE - THE NERD', :display_name => 'JAMES R. TWINE - THE NERD', :address => 'TWINE57%SDELVB%SNYDELVA.bitnet@CUNYVM.CUNY.EDU', :comments => nil, :domain => 'CUNYVM.CUNY.EDU', :local => 'TWINE57%SDELVB%SNYDELVA.bitnet', :format => '"JAMES R. TWINE - THE NERD" ' } ] ] validate_case\ ['/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk', [ { :name => nil, :display_name => nil, :address => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk', :comments => nil, :domain => 'mhs-relay.ac.uk', :local => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/', :format => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk' } ] ] validate_case\ ['"Stephen Burke, Liverpool" ', [ { :name => 'Stephen Burke, Liverpool', :display_name => 'Stephen Burke, Liverpool', :address => 'BURKE@vxdsya.desy.de', :comments => nil, :domain => 'vxdsya.desy.de', :local => 'BURKE', :format => '"Stephen Burke, Liverpool" ' } ] ] validate_case\ ['The Newcastle Info-Server ', [ { :name => 'The Newcastle Info-Server', :display_name => 'The Newcastle Info-Server', :address => 'info-admin@newcastle.ac.uk', :comments => nil, :domain => 'newcastle.ac.uk', :local => 'info-admin', :format => 'The Newcastle Info-Server ' } ] ] validate_case\ ['Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR])', [ { :name => 'Suba Peddada [CONTRACTOR]', :display_name => nil, :address => 'Suba.Peddada@eng.sun.com', :comments => ['Suba Peddada [CONTRACTOR]'], :domain => 'eng.sun.com', :local => 'Suba.Peddada', :format => 'Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR])' } ] ] validate_case\ ['Paul Manser (0032 memo) ', [ { :name => 'Paul Manser', :display_name => 'Paul Manser', :address => 'a906187@tiuk.ti.com', :comments => ['0032 memo'], :domain => 'tiuk.ti.com', :local => 'a906187', :format => 'Paul Manser (0032 memo)' } ] ] validate_case\ ['"gregg (g.) woodcock" ', [ { :name => 'gregg (g.) woodcock', :display_name => 'gregg (g.) woodcock', :address => 'woodcock@bnr.ca', :comments => nil, :domain => 'bnr.ca', :local => 'woodcock', :format => '"gregg (g.) woodcock" ' } ] ] validate_case\ ['Graham.Barr@tiuk.ti.com', [ { :name => nil, :display_name => nil, :address => 'Graham.Barr@tiuk.ti.com', :comments => nil, :domain => 'tiuk.ti.com', :local => 'Graham.Barr', :format => 'Graham.Barr@tiuk.ti.com' } ] ] if domain_optional validate_case\ ['a909937 (Graham Barr (0004 bodg))', [ { :name => 'Graham Barr (0004 bodg)', :display_name => nil, :address => 'a909937', :comments => ['Graham Barr (0004 bodg)'], :domain => nil, :local => 'a909937', :format => 'a909937 (Graham Barr \(0004 bodg\))' } ] ] end validate_case\ ['david d `zoo\' zuhn ', [ { :name => 'david d `zoo\' zuhn', :display_name => 'david d `zoo\' zuhn', :address => 'zoo@aggregate.com', :comments => nil, :domain => 'aggregate.com', :local => 'zoo', :format => 'david d `zoo\' zuhn ' } ] ] validate_case\ ['(foo@bar.com (foobar), ned@foo.com (nedfoo) ) ', [ { :name => 'foo@bar.com (foobar), ned@foo.com (nedfoo) ', :display_name => nil, :address => 'kevin@goess.org', :comments => ['foo@bar.com (foobar), ned@foo.com (nedfoo) '], :domain => 'goess.org', :local => 'kevin', :format => 'kevin@goess.org (foo@bar.com \(foobar\), ned@foo.com \(nedfoo\) )' } ]] end def test_rfc_822 validate_case\ ['":sysmail"@ Some-Group. Some-Org, Muhammed.(I am the greatest) Ali @(the)Vegas.WBA', [ { :name => nil, :display_name => nil, :address => ':sysmail@Some-Group.Some-Org', :comments => nil, :domain => 'Some-Group.Some-Org', :local => ':sysmail', :format => '":sysmail"@Some-Group.Some-Org' }, { :name => 'the', :display_name => nil, :address => 'Muhammed.Ali@Vegas.WBA', :comments => ['I am the greatest', 'the'], :domain => 'Vegas.WBA', :local => 'Muhammed.Ali', :format => 'Muhammed.Ali@Vegas.WBA (I am the greatest) (the)' } ] ] end def test_misc_addresses() # Make sure that parsing empty stuff works assert_equal([], RMail::Address.parse(nil)) assert_equal([], RMail::Address.parse("")) assert_equal([], RMail::Address.parse(" ")) assert_equal([], RMail::Address.parse("\t")) assert_equal([], RMail::Address.parse("\n")) assert_equal([], RMail::Address.parse("\r")) # From a bogus header I saw sent to ruby-talk validate_case([' ruby-talk@ruby-lang.org (ruby-talk ML), >', [ { :name => 'ruby-talk ML', :display_name => nil, :comments => [ 'ruby-talk ML' ], :domain => 'ruby-lang.org', :local => 'ruby-talk', :address => 'ruby-talk@ruby-lang.org', :format => 'ruby-talk@ruby-lang.org (ruby-talk ML)' } ] ]) # From Python address parsing bug list. This is valid according # to RFC2822. validate_case(['Amazon.com ', [ { :name => 'Amazon.com', :display_name => 'Amazon.com', :address => 'delivers-news2@amazon.com', :comments => nil, :domain => 'amazon.com', :local => 'delivers-news2', :format => '"Amazon.com" ' } ] ]) validate_case\ ["\r\n Amazon \r . \n com \t < delivers-news2@amazon.com > \n ", [ { :name => 'Amazon.com', :display_name => 'Amazon.com', :address => 'delivers-news2@amazon.com', :comments => nil, :domain => 'amazon.com', :local => 'delivers-news2', :format => '"Amazon.com" ' } ] ] # From postfix-users@postfix.org # Date: Tue, 13 Nov 2001 10:58:23 -0800 # Subject: Apparent bug in strict_rfc821_envelopes (Snapshot-20010714) validate_case\ ['"mailto:rfc"@monkeys.test', [ { :name => nil, :display_name => nil, :address => 'mailto:rfc@monkeys.test', :comments => nil, :domain => 'monkeys.test', :local => 'mailto:rfc', :format => '"mailto:rfc"@monkeys.test' } ] ] # An unquoted mailto:rfc will end up being detected as an invalid # group display name. validate_case ['mailto:rfc@monkeys.test', [] ] # From gnu.emacs.help # Date: 24 Nov 2001 15:37:23 -0500 validate_case\ ['"Stefan Monnier " ', [ { :name => 'Stefan Monnier ', :display_name => 'Stefan Monnier ', :address => 'monnier+gnu.emacs.help/news/@flint.cs.yale.edu', :comments => nil, :domain => 'flint.cs.yale.edu', :local => 'monnier+gnu.emacs.help/news/', :format => '"Stefan Monnier " ' } ] ] { :name => nil, :display_name => nil, :address => nil, :comments => nil, :domain => nil, :local => nil, :format => nil } validate_case\ ['"foo:" . bar@somewhere.test', [ { :name => nil, :display_name => nil, :address => 'foo:.bar@somewhere.test', :comments => nil, :domain => 'somewhere.test', :local => 'foo:.bar', :format => '"foo:.bar"@somewhere.test' } ] ] validate_case\ ['Some Dude <"foo:" . bar@somewhere.test>', [ { :name => 'Some Dude', :display_name => 'Some Dude', :address => 'foo:.bar@somewhere.test', :comments => nil, :domain => 'somewhere.test', :local => 'foo:.bar', :format => 'Some Dude <"foo:.bar"@somewhere.test>' } ] ] validate_case\ ['"q\uo\ted"@example.com, Luke Skywalker <"use"."the.force"@space.test>', [ { :name => nil, :display_name => nil, :address => 'quoted@example.com', :comments => nil, :domain => 'example.com', :local => 'quoted', :format => 'quoted@example.com' }, { :name => 'Luke Skywalker', :display_name => 'Luke Skywalker', :address => 'use.the.force@space.test', :comments => nil, :domain => 'space.test', :local => 'use.the.force', :format => 'Luke Skywalker ' } ] ] validate_case\ ['Erik =?ISO-8859-1?Q?B=E5gfors?= ', [ { :name => 'Erik =?ISO-8859-1?Q?B=E5gfors?=', :display_name => 'Erik =?ISO-8859-1?Q?B=E5gfors?=', :address => 'erik@example.net', :comments => nil, :domain => 'example.net', :local => 'erik', :format => 'Erik =?ISO-8859-1?Q?B=E5gfors?= ' } ] ] end def test_bug_23043 # http://rubyforge.org/tracker/?func=detail&atid=1754&aid=23043&group_id=446 validate_case\ ['=?iso-8859-1?Q?acme@example.com?= ', [ { :name => '=?iso-8859-1?Q?acme@example.com?=', :display_name => '=?iso-8859-1?Q?acme@example.com?=', :address => 'acme@example.com', :comments => nil, :domain => 'example.com', :local => 'acme', :format => '"=?iso-8859-1?Q?acme@example.com?=" ', } ] ] end def test_invalid_addresses() # The display name isn't encoded -- bad, but we parse it. validate_case\ ["\322\315\322 \312\353\363\341 ".force_encoding('ASCII-8BIT'), [ { :name => "\322\315\322 \312\353\363\341".force_encoding("ASCII-8BIT"), :display_name => "\322\315\322 \312\353\363\341".force_encoding("ASCII-8BIT"), :address => 'bar@foo.invalid', :comments => nil, :domain => 'foo.invalid', :local => 'bar', :format => "\"\322\315\322 \312\353\363\341\" ".force_encoding("ASCII-8BIT"), } ] ] end def test_domain_literal validate_case\ ['test@[domain]', [ { :name => nil, :display_name => nil, :address => 'test@[domain]', :comments => nil, :domain => '[domain]', :local => 'test', :format => '' } ] ] validate_case\ ['<@[obsdomain]:test@[domain]>', [ { :name => nil, :display_name => nil, :address => 'test@[domain]', :comments => nil, :domain => '[domain]', :local => 'test', :format => '' } ] ] validate_case\ ['<@[ob\]sd\\\\omain]:test@[dom\]ai\\\\n]>', [ { :name => nil, :display_name => nil, :address => 'test@[dom]ai\\n]', :comments => nil, :domain => '[dom]ai\\n]', :local => 'test', :format => '' } ] ] validate_case\ ["Bob \r<@machine.tld \r,\n,,, @[obsdomain],@foo\t:\ntest @ [domain]>", [ { :name => 'Bob', :display_name => 'Bob', :address => 'test@[domain]', :comments => nil, :domain => '[domain]', :local => 'test', :format => 'Bob ' } ] ] end def test_exhaustive() # We don't test every alphanumeric in atext -- assume that if a, m # and z work, they all will. atext = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + '!#$%&\'*+-/=?^_`{|}~'.split(//) #/ boring = ('b'..'l').to_a + ('n'..'o').to_a + ('p'..'y').to_a + ('B'..'L').to_a + ('N'..'O').to_a + ('P'..'Y').to_a + ('1'..'4').to_a + ('6'..'8').to_a (atext - boring).each {|ch| validate_case(["#{ch} <#{ch}@test>", [ { :name => ch, :display_name => ch, :address => "#{ch}@test", :comments => nil, :domain => 'test', :local => ch, :format => ch + ' <' + ch + '@test>' } ] ]) } validate_case([atext.join('') + ' <' + atext.join('') + '@test>', [ { :name => atext.join(''), :display_name => atext.join(''), :address => atext.join('') + '@test', :comments => nil, :domain => 'test', :local => atext.join(''), :format => atext.join('') + ' <' + atext.join('') + '@test>' } ] ]) ascii = (0..127).collect {|i| i.chr} whitespace = ["\r", "\n", ' ', "\t"] qtext = ascii - (whitespace + ['"', '\\']) ctext = ascii - (whitespace + ['(', ')', '\\']) dtext = ascii - (whitespace + ['[', ']', '\\']) (qtext - atext).each {|ch| validate_case(["\"#{ch}\" <\"#{ch}\"@test>", [ { :name => ch, :display_name => ch, :address => "#{ch}@test", :comments => nil, :domain => 'test', :local => "#{ch}", :format => "\"#{ch}\" <\"#{ch}\"@test>" } ] ]) } ['"', "\\"].each {|ch| validate_case(["\"\\#{ch}\" <\"\\#{ch}\"@test>", [ { :name => ch, :display_name => ch, :address => ch + '@test', :comments => nil, :domain => 'test', :local => ch, :format => "\"\\#{ch}\" <\"\\#{ch}\"@test>" } ] ]) } (ctext - boring).each {|ch| validate_case(["bob@test (#{ch})", [ { :name => ch, :display_name => nil, :address => 'bob@test', :comments => ["#{ch}"], :domain => 'test', :local => 'bob', :format => "bob@test (#{ch})" } ] ]) validate_case(["bob@test (\\#{ch})", [ { :name => ch, :display_name => nil, :address => 'bob@test', :comments => ["#{ch}"], :domain => 'test', :local => 'bob', :format => "bob@test (#{ch})" } ] ]) } [')', '(', '\\'].each {|ch| validate_case(["bob@test (\\#{ch})", [ { :name => ch, :display_name => nil, :address => 'bob@test', :comments => ["#{ch}"], :domain => 'test', :local => 'bob', :format => 'bob@test (\\' + ch + ')' } ] ]) } (dtext - boring).each {|ch| validate_case(["test@[\\#{ch}] (Sam)", [ { :name => "Sam", :display_name => nil, :address => 'test@[' + ch + ']', :comments => ["Sam"], :domain => '[' + ch + ']', :local => 'test', :format => " (Sam)" } ] ] ) validate_case(["Sally ", [ { :name => "Sally", :display_name => "Sally", :address => 'test@[' + ch + ']', :comments => nil, :domain => '[' + ch + ']', :local => 'test', :format => "Sally " } ] ] ) } validate_case(["test@[" + (dtext - boring).join('') + "]", [ { :name => nil, :display_name => nil, :address => 'test@[' + (dtext - boring).join('') + "]", :comments => nil, :domain => '[' + (dtext - boring).join('') + ']', :local => 'test', :format => "" } ] ]) validate_case(["Bob ", [ { :name => "Bob", :display_name => "Bob", :address => 'test@[' + (dtext - boring).join('') + "]", :comments => nil, :domain => '[' + (dtext - boring).join('') + ']', :local => 'test', :format => "Bob " } ] ]) end def test_out_of_spec() validate_case ['bodg fred@tiuk.ti.com', [] ] validate_case ['(comment) bodg fred@example.net, matt@example.com', [ { :name => nil, :display_name => nil, :comments => nil, :address => 'matt@example.com', :domain => 'example.com', :local => 'matt', :format => 'matt@example.com' } ] ] validate_case ['', [] ] validate_case ['"" ', [ { :name => nil, :display_name => nil, :address => 'bob@example.com', :comments => nil, :domain => 'example.com', :local => 'bob', :format => 'bob@example.com' } ] ] validate_case\ ['"" <""@example.com>', [ ] ] validate_case\ ['@example.com', [ ] ] if domain_optional validate_case\ ['bob', [ { :name => nil, :display_name => nil, :address => 'bob', :comments => nil, :domain => nil, :local => 'bob', :format => 'bob' } ] ] validate_case\ ['bob,sally, sam', [ { :name => nil, :display_name => nil, :address => 'bob', :comments => nil, :domain => nil, :local => 'bob', :format => 'bob' }, { :name => nil, :display_name => nil, :address => 'sally', :comments => nil, :domain => nil, :local => 'sally', :format => 'sally' }, { :name => nil, :display_name => nil, :address => 'sam', :comments => nil, :domain => nil, :local => 'sam', :format => 'sam' }] ] else validate_case [ 'bob', [] ] validate_case [ 'Bobby, sally,sam', [] ] end validate_case(['Undisclosed <>', []]) validate_case(['"Mensagem Automatica do Terra" <>', []]) validate_case(["\177", []]) validate_case(["\177\177\177", []]) end def test_random_strings # These random strings have generated exceptions before, so test # them forever. RMail::Address.parse("j732[S\031\022\000\fuh\003Ye<2psd\005#1L=Hw*c\0247\006\aE\fXJ\026;\026\032zAAgpCFq+\010") RMail::Address.parse("\016o7=\024d^\001|h<,#\026~(") assert_instance_of(RMail::Address, addr) assert_equal("Bob Smith", addr.display_name) assert_equal("Bob Smith", addr.name) assert_equal("example.com", addr.domain) assert_equal("bobs", addr.local) addr = RMail::Address.new("Bob Smith (Joe Bob) " + ", Sally Joe (Hi Babe) ") assert_instance_of(RMail::Address, addr) assert_equal("Bob Smith", addr.display_name) assert_equal("Bob Smith", addr.name) assert_equal("example.com", addr.domain) assert_equal("bobs", addr.local) addr = RMail::Address.new("\177\177") assert_instance_of(RMail::Address, addr) assert_equal(nil, addr.display_name) assert_equal(nil, addr.name) assert_equal(nil, addr.domain) assert_equal(nil, addr.local) assert_raise(ArgumentError) { RMail::Address.new(["bob"]) } assert_raise(ArgumentError) { RMail::Address.new(Object.new) } assert_raise(ArgumentError) { RMail::Address.new(Hash.new) } end def test_comments a = RMail::Address.new assert_nil(a.comments) a.comments = [ 'foo', 'bar' ] assert_equal([ 'foo', 'bar' ], a.comments) a.comments = 'foo' assert_equal([ 'foo' ], a.comments) end def test_rmail_address_compare a1 = RMail::Address.new a2 = RMail::Address.new # Make sure it works on uninitialized addresses assert_equal(0, a1 <=> a2) assert_equal(a1, a2) # Display name and comments make no difference a1.display_name = 'foo' a1.comments = 'bar' assert_equal(0, a1 <=> a2) assert_equal(a1, a2) # a1 < a2 a1 = RMail::Address.new("Zeus ") a2 = RMail::Address.new("sally@example.com") assert(a1 < a2) assert(a1 != a2) # Type coercion assert(a1 < "zorton@example.com") assert(a1 > ".") assert(a1 == "bob@example.com") end def test_rmail_address_eql? a1 = RMail::Address.new a2 = RMail::Address.new # Make sure it works on uninitialized addresses assert(a1.eql?(a2)) # Display name and comments make no difference a1.display_name = 'foo' a1.comments = 'bar' assert(a1.eql?(a2)) a1 = RMail::Address.new("Zeus ") assert_raise(TypeError) { a1.eql?("bob@eaxample.com") } end def test_rmail_address_hash assert_nothing_raised { RMail::Address.new.hash } a1 = RMail::Address.new("Zeus ") assert_equal(a1.hash, "bob@example.com".hash) end end rmail-1.1.4/README.md0000644000004100000410000000431713701337101014131 0ustar www-datawww-data# RubyMail This is RubyMail, a lightweight mail library containing various utility classes and modules that allow Ruby scripts to parse, modify, and generate MIME mail messages. ## Requirements Ruby 2.0 or newer is supported. ## Install ```bash gem install rmail ``` ## Status [![Build Status](https://travis-ci.org/terceiro/rmail.svg?branch=master)](https://travis-ci.org/terceiro/rmail) ## Tests This package has a complete unit test suite. To run it, just run `rake` under the top-level directory. When submitting patches, please make sure all the tests pass with your changes applied. ## Support If you found a bug, or even if you think you did but is not completely sure, open an issue on the [issue tracker](https://github.com/terceiro/rmail/issues). ## License Copyright (C) 2001, 2002, 2003, 2004 Matt Armstrong. Copyright (C) 2015 Antonio Terceiro. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rmail-1.1.4/guide/0000755000004100000410000000000013701337101013742 5ustar www-datawww-datarmail-1.1.4/guide/TableOfContents.txt0000644000004100000410000000072013701337101017534 0ustar www-datawww-data= RubyMail Guide Table of Contents [guide/Intro.txt] Brief introduction to RubyMail. [guide/MIME.txt] How to perform common operations on MIME messages. [guide/SMTP.txt] How to send a RubyMail message out using the standard Ruby net/smtp.rb package. [guide/POP.txt] How to parse a RubyMail message directly from a POP server using the standard net/pop.rb package. [guide/Mailbox.txt] How to read mailboxes of various formats. rmail-1.1.4/guide/MIME.txt0000644000004100000410000000024413701337101015232 0ustar www-datawww-data= MIME Support in RubyMail FIXME: write this. TODO: show how to add, delete, decode and encode parts. http://rwiki.moonwolf.com/rw-cgi.cgi?cmd=view;name=RubyMail rmail-1.1.4/guide/Intro.txt0000644000004100000410000000657513701337101015613 0ustar www-datawww-data= Introduction to RubyMail This will get you started with the basics of using RubyMail. == Turning Text into a Message Object message = RMail::Parser.read(input) The input can be a normal Ruby IO object or a string. The result is a RMail::Message object. See RMail::Parser for more information. == Turning a Message Object into Text File.open('my-message', 'w') { |f| RMail::Serialize.write(f, message) } or string = RMail::Serialize.write('', message) See RMail::Serialize for more information. A convenient shortcut when you want the message as a string is RMail::Message#to_s. string = message.to_s You can also use RMail::Message#each to process each line of the serialized message in turn. message.each { |line| puts line } == Manipulating a Message You use the methods of the RMail::Message and RMail::Header classes to manipulate the message. === Retrieve the Body You can retrieve the text of a single part message with RMail::Message#body. body = message.body But beware that if the message is a MIME multipart message, +body+ will be an Array of RMail::Message objects. If you know you have a MIME multipart message (easily tested with RMail::Message#multipart?), then you can retrieve them individually with RMail::Message#part and RMail::Message#each_part. first_part = message.part(0) message.each_part { |part| # do something with part } See guide/MIME.txt for more tips on dealing with MIME messages. === Manipulate the Message Headers The RMail::Message#header method retrieves the RMail::Header object that every message contains. You can then use RMail::Header methods to manipulate the message's header. People often confuse a "message header" with "header fields." In RubyMail a "header" always means the entire header of the message, while "field" means an individual header field (e.g. "Subject"). To append new fields, simply access assign to them like an array: message.header['To'] = 'bob@example.net' message.header['From'] = 'sally@example.net' Note that the above will always append a new header, so you must delete existing headers if you want only one. To retrieve fields, you can access the header like an array. This will return the field value. subject = message.header['Subject'] Of course, a header may have several fields with the same name. To retrieve all the values you can use RMail::Header.match. destinations = message.header.match(/^(to|cc|bcc)$/, nil) See the RMail::Header documentation for many other useful functions. == Dealing with Email Addresses The address syntax for Internet email messages (as specified in RFC2822) is complex. RubyMail contains the RMail::Address class that can be used to parse and generate these addresses in a robust way. For example, to retrieve all destination addresses from a RMail::Header object: recipients = RMail::Address.parse(header.match(/^(to|cc)/, nil)) Then print out just the address portion: recipients.each { |r| r.address } When creating an address from scratch, you typically do this: address = RMail::Address.new("Bob Smith ") Then to get the text form of the address form back: address.format TODO: addresses can be keys of a hash, sorted, etc... == More Topics This is just the beginning. See guide/TableOfContents.txt for a list of other things covered in this guide. rmail-1.1.4/Rakefile0000644000004100000410000001012413701337101014310 0ustar www-datawww-data# -*- ruby -*- # # This is a Ruby file, used by the "rake" make-like program. # require 'rubygems/package_task' require 'rdoc/task' require 'rake/testtask' require 'shellwords' # # The default task is run if rake is given no explicit arguments. # desc "Default Task" task :default => :test # # Test tasks # Rake::TestTask.new do |t| t.libs << '.' t.pattern = 'test/test*.rb' t.verbose = true end def unreleasable_reason can_release_package unreleasable_reason end def can_release_package reasons = [] unless news_is_current reasons << 'the ChangeLog file is not current' end unless defined?(Gem) reasons << 'RubyGems is not installed' end reason = if reasons.empty? "" else last = reasons.pop ("Can not release package because " + (reasons.empty? ? "#{last}." : (reasons.join(", ") + " and #{last}."))) end can_release = reason.length == 0 self.class.module_eval <<-END_OF_CODE def unreleasable_reason \"#{reason}\" end def can_release_package #{can_release.inspect} end END_OF_CODE can_release_package end # Is ChangeLog current? def news_is_current today = Time.now.strftime('%Y-%m-%d') version = Regexp.new(Regexp.quote(PKG_VERSION)) if IO.readlines('ChangeLog').first =~ /= Changes in RubyMail #{PKG_VERSION} \(released #{today}\)$/ true else false end end # # These PKG_ variables are used by Rake's package rule. # require './lib/rmail/version.rb' PKG_VERSION = RMail::VERSION PKG_FILES = FileList.new('test/**/*', 'guide/**/*', 'lib/**/*', 'ChangeLog', 'NOTES', 'README.md', 'THANKS', 'TODO', 'Rakefile') # # Teach Rake how to build the RDoc documentation for this package. # rdoc = Rake::RDocTask.new do |rdoc| rdoc.main = 'README' rdoc.rdoc_files.include("README.md", "ChangeLog", "THANKS", "TODO", "guide/*.txt", "lib/**/*.rb") rdoc.rdoc_files.exclude(/\bSCCS\b/, "lib/rubymail/parser/*") unreleased = if can_release_package "" else " (UNRELEASED!)" end rdoc.title = "RubyMail Documentation (version #{PKG_VERSION})" rdoc.options << '--exclude' << 'SCCS' end # Make sure that we don't package anything that hasn't been tagged. task :package => [ :can_release ] desc "Check if the package is in a releasable state." task :can_release do unless can_release_package puts unreleasable_reason end end # # Create a Gem::Specification right in the Rakefile, using some of the # variables we have set up above. spec = Gem::Specification.new do |s| s.name = 'rmail' s.version = PKG_VERSION + if can_release_package '' else '.666' end s.summary = 'A MIME mail parsing and generation library.' s.description = <<-EOF RMail is a lightweight mail library containing various utility classes and modules that allow ruby scripts to parse, modify, and generate MIME mail messages. EOF s.files = PKG_FILES.to_a s.required_ruby_version = Gem::Version::Requirement.new(">= 1.8.1") s.extra_rdoc_files = rdoc.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a s.rdoc_options.concat([ '--title', rdoc.title, '--main', rdoc.main, rdoc.options ].flatten) s.test_files = FileList['test/tc_*.rb'].to_a s.author = ["Matt Armstrong", "Antonio Terceiro"] s.email = "terceiro@softwarelivre.org" s.homepage = "https://github.com/terceiro/rmail" end # # Use our Gem::Specification to make some package tasks. # Gem::PackageTask.new(spec) do |pkg| pkg.need_zip = true pkg.need_tar = true end desc 'Makes a release' task :release => [:test, :package] do sh 'git', 'tag', '-s', "v#{PKG_VERSION}" sh 'git', 'push' sh 'git', 'push', '--tags' sh 'gem', 'push', "pkg/rmail-#{PKG_VERSION}.gem" end rmail-1.1.4/TODO0000644000004100000410000000770413701337101013345 0ustar www-datawww-dataIn rough priority order...but please let me know if something you need is missing. = FIXME - StreamHandler should get preamble_begin and preamble_end callbacks. Same for epilogue_begin, epilogue_end. - Add a Rakefile rule to run the tests in the super anal way (against multiple versions of Ruby). = STOLEN - Header continuation issue: https://sourceforge.net/tracker/?func=detail&atid=105470&aid=504152&group_id=5470 - Subject: =?ISO-8859-1?Q?=24=A3_Kills_IRB?= = Documentation - Make it clear that all strings the library works with are the ASCII strings that are part of the RFC2822 message -- not strings in any other charset. - Update Intro.txt, write MIME.txt and other Guides. = Features - RFC2231 http://mail.python.org/pipermail/email-sig/2003-November/000032.html - Smart handling of charset issues for text/ types. [Goal] allow setting the body part to a given piece of data, and RMail handles setting the appropriate MIME headers. This requires the data to have a known charset. Maybe this plays into the encoding solution below. - Base64 and quoted-printable encoding [Goal] allow setting the body part to a given piece of data, and RMail handles setting the appropriate MIME headers and possibly quoted-printable or base64 transcoding it if appropriate. This may be best done by introducing an RMail::TransferEncodedString class that knows whether it is transcoded as binary, quoted-printable, base64, etc. You would set RMail::Message#body to a TransferEncodedString and RMail::Message would set the appropriate MIME headers. If at serialization time the message body were set to a plain String, then RMail::Message would transcode it into base64. - An RMail::Message#attach method that takes care of setting content-disposition, etc. - Header folding in RMail::Header. - A small wrapper around Net::SMTP#send_mail that takes care of: - parsing recipients out of the various message headers - deleting the Bcc: header - sending the message - Provide a way to parse just the message headers, leaving the rest in the input stream (for use by RubyFilter). This also implies a feature that parses a message body given a RMail::Header object and an input stream. This should be easy -- read line by line until you get an empty line or EOF, then pass that off to parse_header as a string to be parsed. Parsing the body can just use a PushbackReader. - Handle different end of line terminators everywhere (for both parsing and generating). - Provide some way when parsing a message to start spooling to disk for really large messages. This would be easy to do depending on how message body transparency is implemented. - MIME charset support in header fields and message bodies. This requires m18n support in ruby, so it won't be coming soon. - Maildir parsing. - Simplify the Parser such that the class used to hold the parse result can be easily changed and so the memory used can be minimized. The idea is to support "bogofilter" style parsing where the data of the parse is immediately processed and thrown away but the structure and content of the parsed message is important. This may require extensions to header parsing, where things like the important MIME header fields can be parsed without creating a whole RMail::Header object. = Minor Features - Untainting of email addresses. See Perl's CGI::Untaint::email. - Parser for Received: headers. See Perl's Mail::Field::Received. = Documentation Tasks - A "howto" like documentation. - How to write out a base64 encoded MIME part to a file. - How to parse a unix MBOX file. - MIME operations (see RFC2047) - Character sets in message bodies - Non-textual message bodies - Multi-part message bodies - Textual header information in character sets other than US-ASCII. - Go through every class and make sure the methods are sorted in a sensible order so the RDoc output is nice. rmail-1.1.4/lib/0000755000004100000410000000000013701337101013413 5ustar www-datawww-datarmail-1.1.4/lib/rmail.rb0000644000004100000410000000411613701337101015046 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (c) 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # This module allows you to simply # require 'rmail' # in your ruby scripts and have all of the RMail module required. # This provides maximum convenience when the startup time of your # script is not crucial. # The RMail module contains all of the RubyMail classes, but has no # useful API of its own. # # See guide/Intro.txt for a general overview of RubyMail. module RMail end require 'rmail/address' require 'rmail/header' require 'rmail/mailbox' require 'rmail/message' require 'rmail/parser' require 'rmail/serialize' require 'rmail/utils' require 'rmail/mailbox/mboxreader' require 'rmail/parser/multipart' require 'rmail/parser/pushbackreader' rmail-1.1.4/lib/rmail/0000755000004100000410000000000013701337101014517 5ustar www-datawww-datarmail-1.1.4/lib/rmail/parser/0000755000004100000410000000000013701337101016013 5ustar www-datawww-datarmail-1.1.4/lib/rmail/parser/pushbackreader.rb0000644000004100000410000001375413701337101021335 0ustar www-datawww-data#-- # Copyright (c) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Parser::PushbackReader class. module RMail class Parser class Error < StandardError; end # A utility class for reading from an input source in an efficient # chunked manner. # # The idea is to read data in descent sized chunks (the default is # 16k), but provide a way to "push back" some of the chunk if we # read too much. # # This class is useful only as a base class for other readers -- # e.g. a reader that parses MIME multipart documents, or a reader # that understands one or more mailbox formats. # # The typical RubyMail user will have no interest in this class. # ;-) class PushbackReader # :nodoc: # Create a PushbackReader and have it read from a given input # source. # # The input source must either be a String or respond to the # "read" method in the same way as an IO object. def initialize(input) unless defined? input.read(1) unless input.is_a?(String) raise ArgumentError, "input object not IO or String" end @pushback = input @input = nil else @pushback = nil @input = input end @chunk_size = 16384 end # Read a chunk of input. The "size" argument is just a # suggestion, and more or fewer bytes may be returned. If # "size" is nil, then return the entire rest of the input # stream. def read(size = @chunk_size) case size when nil chunk = nil while temp = read(@chunk_size) if chunk chunk << temp else chunk = temp end end chunk when Integer read_chunk(size) else raise ArgumentError, "Read size (#{size.inspect}) must be an Integer or nil." end end # Read a chunk of a given size. Unlike #read, #read_chunk must # be passed a chunk size, and cannot be passed nil. # # This is the function that should be re-defined in subclasses # for specialized behavior. def read_chunk(size) standard_read_chunk(size) end # The standard implementation of read_chunk. This can be # convenient to call from derived classes when super() isn't # easy to use. def standard_read_chunk(size) unless size.is_a?(Integer) && size > 0 raise ArgumentError, "Read size (#{size.inspect}) must be greater than 0." end if @pushback chunk = @pushback @pushback = nil elsif ! @input.nil? chunk = @input.read(size) end return chunk end # Push a string back. This will be the next chunk of data # returned by #read. # # Because it has not been needed and would compromise # efficiency, only one chunk of data can be pushed back between # successive calls to #read. def pushback(string) raise RMail::Parser::Error, 'You have already pushed a string back.' if @pushback @pushback = string end # Retrieve the chunk size of this reader. attr_reader :chunk_size # Set the chunk size of this reader in bytes. This is useful # mainly for testing, though perhaps some operations could be # optimized by tweaking this value. The chunk size must be an # Integer greater than 0. def chunk_size=(size) unless size.is_a?(Integer) raise ArgumentError, "chunk size must be an Integer" end unless size >= 1 raise ArgumentError, "invalid size #{size.inspect} given" end @chunk_size = size end # Returns true if the next call to read_chunk will return nil. def eof @pushback.nil? and (@input.nil? or @input.eof) end # Creates a regexp that'll match the given boundary string in # its entirely anywhere in a string, or any partial prefix of # the boundary string so long as the match is anchored at the # end of the string. This is useful for various subclasses of # PushbackReader that need to know if a given input chunk might # contain (or contain just the beginning of) an interesting # string. def self.maybe_contains_re(boundary) left = Regexp.quote(boundary[0,1]) right = '' boundary[1..-1].each_byte { |ch| left << '(?:' left << Regexp.quote(ch.chr) right << '|\z)' } left + right end end end end rmail-1.1.4/lib/rmail/parser/multipart.rb0000644000004100000410000001530013701337101020360 0ustar www-datawww-data#-- # Copyright (C) 2002, 2003, 2004 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Parser::MultipartReader class. module RMail class Parser require 'rmail/parser/pushbackreader' # A simple interface to facilitate parsing a multipart message. # # The typical RubyMail user will have no use for this class. # Although it is an example of how to use a PushbackReader, the # typical RubyMail user will never use a PushbackReader either. # ;-) class MultipartReader < PushbackReader # :nodoc: # Creates a MIME multipart parser. # # +input+ is an object supporting a +read+ method that takes one # argument, a suggested number of bytes to read, and returns # either a string of bytes read or nil if there are no more # bytes to read. # # +boundary+ is the string boundary that separates the parts, # without the "--" prefix. # # This class is a suitable input source when parsing recursive # multiparts. def initialize(input, boundary) super(input) escaped = Regexp.escape(boundary) @delimiter_re = /(?:\G|\n)--#{escaped}(--)?\s*?(\n|\z)/ @might_be_delimiter_re = might_be_delimiter_re(boundary) @caryover = nil @chunks = [] @eof = false @delimiter = nil @delimiter_is_last = false @in_epilogue = false @in_preamble = true end # Returns the next chunk of data from the input stream as a # string. The chunk_size is passed down to the read method of # this object's input stream to suggest a size to it, but don't # depend on the returned data being of any particular size. # # If this method returns nil, you must call next_part to begin # reading the next MIME part in the data stream. def read_chunk(chunk_size) chunk = read_chunk_low(chunk_size) if chunk if @in_epilogue while more = read_chunk_low(chunk_size) chunk << more end end end chunk end def read_chunk_low(chunk_size) if @pushback return standard_read_chunk(chunk_size) end input_gave_nil = false loop { return nil if @eof || @delimiter unless @chunks.empty? chunk, @delimiter, @delimiter_is_last = @chunks.shift return chunk end chunk = standard_read_chunk(chunk_size) if chunk.nil? input_gave_nil = true end if @caryover if chunk @caryover << chunk end chunk = @caryover @caryover = nil end if chunk.nil? @eof = true return nil elsif @in_epilogue return chunk end start = 0 found_last_delimiter = false while !found_last_delimiter and (start < chunk.length) and (found = chunk.index(@delimiter_re, start)) if $~[2] == '' and !input_gave_nil break end delimiter = $~[0] # check if delimiter had the trailing -- if $~.begin(1) found_last_delimiter = true end temp = if found == start nil else chunk[start, found - start] end @chunks << [ temp, delimiter, found_last_delimiter ] start = $~.end(0) end chunk = chunk[start..-1] if start > 0 # If something that looks like a delimiter exists at the end # of this chunk, refrain from returning it. unless found_last_delimiter or input_gave_nil start = chunk.rindex(/\n/) || 0 if chunk.index(@might_be_delimiter_re, start) @caryover = chunk[start..-1] chunk[start..-1] = '' chunk = nil if chunk.length == 0 end end unless chunk.nil? @chunks << [ chunk, nil, false ] end chunk, @delimiter, @delimiter_is_last = @chunks.shift if chunk return chunk end } end # Start reading the next part. Returns true if there is a next # part to read, or false if we have reached the end of the file. def next_part if @eof false else if @delimiter @delimiter = nil @in_preamble = false @in_epilogue = @delimiter_is_last end true end end # Call this to determine if #read is currently returning strings # from the preamble portion of a mime multipart. def preamble? @in_preamble end # Call this to determine if #read is currently returning strings # from the epilogue portion of a mime multipart. def epilogue? @in_epilogue end # Call this to retrieve the delimiter string that terminated the # part just read. This is cleared by #next_part. def delimiter @delimiter end private def might_be_delimiter_re(boundary) s = PushbackReader.maybe_contains_re("--" + boundary) Regexp.new('(?:\A|\n)(?:' + s + '|\z)') end end end end rmail-1.1.4/lib/rmail/version.rb0000644000004100000410000000004513701337101016530 0ustar www-datawww-datamodule RMail VERSION = '1.1.4' end rmail-1.1.4/lib/rmail/serialize.rb0000644000004100000410000001427113701337101017040 0ustar www-datawww-data#-- # Copyright (C) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Serialize class. module RMail # The RMail::Serialize class writes an RMail::Message object into an # IO object or string. The result is a standard mail message in # text form. # # To do this, you pass the RMail::Message object to the # RMail::Serialize object. RMail::Serialize can write into any # object supporting the << method. # # As a convenience, RMail::Serialize.write is a class method you can # use directly: # # # Write to a file # File.open('my-message', 'w') { |f| # RMail::Serialize.write(f, message) # } # # # Write to a new string # string = RMail::Serialize.write('', message) class Serialize @@boundary_count = 0 # Initialize this Serialize object with an output stream. If # escape_from is not nil, lines with a leading From are escaped. def initialize(output, escape_from = nil) @output = output @escape_from = escape_from end # Serialize a given message into this object's output object. def serialize(message) calculate_boundaries(message) if message.multipart? serialize_low(message) end # Serialize a message into a given output object. The output # object must support the << method in the same way that an IO or # String object does. def Serialize.write(output, message) Serialize.new(output).serialize(message) end private def serialize_low(message, depth = 0) if message.multipart? delimiters, _delimiters_boundary = message.get_delimiters unless delimiters boundary = "\n--" + message.header.param('Content-Type', 'boundary') delimiters = Array.new(message.body.length + 1, boundary + "\n") delimiters[-1] = boundary + "--\n" end @output << message.header.to_s if message.body.length > 0 or message.preamble or delimiters.last.length > 0 @output << "\n" end if message.preamble @output << message.preamble end delimiter = 0 message.each_part { |part| @output << delimiters[delimiter] delimiter = delimiter.succ serialize_low(part, depth + 1) } @output << delimiters[delimiter] if message.epilogue @output << message.epilogue end else @output << message.header.to_s unless message.body.nil? @output << "\n" @output << message.body if depth == 0 and message.body.length > 0 and message.body[-1] != ?\n @output << "\n" end end end @output end # Walk the multipart tree and make sure the boundaries generated # will actually work. def calculate_boundaries(message) calculate_boundaries_low(message, []) unless message.header['MIME-Version'] message.header['MIME-Version'] = "1.0" end end def calculate_boundaries_low(part, boundaries) # First, come up with a candidate boundary for this part and # save it in our list of boundaries. boundary = make_and_set_unique_boundary(part, boundaries) # Now walk through each part and make sure the boundaries are # suitable. We dup the boundaries array before recursing since # sibling multipart can re-use boundary strings (though it isn't # a good idea). boundaries.push(boundary) part.each_part { |p| calculate_boundaries_low(p, boundaries) if p.multipart? } boundaries.pop end # Generate a random boundary def generate_boundary @@boundary_count += 1 t = Time.now sprintf("=-%d-%d-%d-%d-%d-=", t.tv_sec.to_s, t.tv_usec.to_s, Process.pid.to_s, rand(10000), @@boundary_count) end # Returns a boundary that will probably work out. Extracts any # existing boundary from the header, but will generate a default # one if the header doesn't have one set yet. def make_and_set_unique_boundary(part, boundaries) candidate = part.header.param('content-type', 'boundary') unique = make_unique_boundary(candidate || generate_boundary, boundaries) if candidate.nil? or candidate != unique part.header.set_boundary(unique) end unique end # Make the passed boundary unique among the passed boundaries and # return it. def make_unique_boundary(boundary, boundaries) continue = true while continue continue = false boundaries.each do |existing| if boundary == existing[0, boundary.length] continue = true break end end break unless continue boundary = generate_boundary end boundary end end end rmail-1.1.4/lib/rmail/address.rb0000644000004100000410000005641313701337101016502 0ustar www-datawww-data#-- # Copyright (C) 2001, 2002, 2003, 2008 Matt Armstrong. All rights # reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Address, RMail::Address::List, and # RMail::Address::Parser classes. Together, these classes allow you # to robustly parse, manipulate, and generate RFC2822 email addresses # and address lists. module RMail # This class provides the following functionality: # # * Parses RFC2822 address lists into a list of Address # objects (see #parse). # # * Format Address objects as appropriate for insertion into email # messages (see #format). # # * Allows manipulation of the various parts of the address (see # #local=, #domain=, #display_name=, #comments=). class Address ATEXT = '[\w=!#$%&\'*+-?^\`{|}~]+' # Create a new address. If the +string+ argument is not nil, it # is parsed for mail addresses and if one is found, it is used to # initialize this object. def initialize(string = nil) @local = @domain = @comments = @display_name = nil if string.kind_of?(String) addrs = Address.parse(string) if addrs.length > 0 @local = addrs[0].local @domain = addrs[0].domain @comments = addrs[0].comments @display_name = addrs[0].display_name end else raise ArgumentError unless string.nil? end end # Compare this address with another based on the email address # portion only (any display name and comments are ignored). If # the other object is not an RMail::Address, it is coerced into a # string with its to_str method and then parsed into an # RMail::Address object. def <=>(other) if !other.kind_of?(RMail::Address) other = RMail::Address.new(other.to_str) end cmp = (@local || '') <=> (other.local || '') if cmp == 0 cmp = (@domain || '') <=> (other.domain || '') end return cmp end include Comparable # Return a hash value for this address. This is based solely on # the email address portion (any display name and comments are # ignored). def hash address.hash end # Return true if the two objects are equal. Do this based solely # on the email address portion (any display name and comments are # ignored). Fails if the other object is not an RMail::Address # object. def eql?(other) raise TypeError unless other.kind_of?(RMail::Address) @local.eql?(other.local) and @domain.eql?(other.domain) end # Retrieve the local portion of the mail address. This is the # portion that precedes the @ sign. def local @local end # Assign the local portion of the mail address. This is the # portion that precedes the @ sign. def local=(l) raise ArgumentError unless l.nil? || l.kind_of?(String) @local = l end # Returns the display name of this address. The display name is # present only for "angle addr" style addresses such as: # # John Doe # # In this case, the display name will be "John Doe". In # particular this old style address has no display name: # # bobs@example.net (Bob Smith) # # See also display_name=, #name def display_name @display_name end # Assign a display name to this address. See display_name for a # definition of what this is. # # See also display_name def display_name=(str) unless str.nil? || str.kind_of?(String) raise ArgumentError, 'not a string' end @display_name = str @display_name = nil if @display_name == '' end # Returns a best guess at a display name for this email address. # This function first checks if the address has a true display # name (see display_name) and returns it if so. Otherwise, if the # address has any comments, the last comment will be returned. # # In most cases, this will behave reasonably. For example, it # will return "Bob Smith" for this address: # # bobs@example.net (Bob Smith) # # See also display_name, #comments, #comments= def name @display_name || (@comments && @comments.last) end # Returns the comments in this address as an array of strings. def comments @comments end # Set the comments for this address. The +comments+ argument can # be a string, or an array of strings. In either case, any # existing comments are replaced. # # See also #comments, #name def comments=(comments) case comments when nil @comments = comments when Array @comments = comments when String @comments = [ comments ] else raise TypeError, "Argument to RMail::Address#comments= must be " + "String, Array or nil, was #{comments.type}." end @comments.freeze end # Retrieve to the domain portion of the mail address. This is the # portion after the @ sign. def domain @domain end # Assign a domain name to this address. This is the portion after # the @ sign. Any existing domain name will be changed. def domain=(domain) @domain = if domain.nil? or domain == '' nil else raise ArgumentError unless domain.kind_of?(String) domain.strip end end # Returns the email address portion of the address (i.e. without a # display name, angle addresses, or comments). # # The string returned is not suitable for insertion into an # e-mail. RFC2822 quoting rules are not followed. The raw # address is returned instead. # # For example, if the local part requires quoting, this function # will not perform the quoting (see #format for that). So this # function can returns strings such as: # # "address with no quoting@example.net" # # See also #format def address if @domain.nil? @local else @local + '@' + @domain end end # Return this address as a String formated as appropriate for # insertion into a mail message. def format display_name = if @display_name.nil? nil elsif @display_name =~ /^[-\/\w=!#\$%&'*+?^`{|}~ ]+$/ @display_name else '"' + @display_name.gsub(/["\\]/, '\\\\\&') + '"' end local = if (@local !~ /^[-\w=!#\$%&'*+?^`{|}~\.\/]+$/ || @local =~ /^\./ || @local =~ /\.$/ || @local =~ /\.\./) '"' + @local.gsub(/["\\]/, '\\\\\&') + '"' else @local end domain = if (!@domain.nil? and (@domain !~ /^[-\w=!#\$%&'*+?^`{|}~\.\/]+$/ || @domain =~ /^\./ || @domain =~ /\.$/ || @domain =~ /\.\./)) then '[' + if @domain =~ /^\[(.*)\]$/ $1 else @domain end.gsub(/[\[\]\\]/, '\\\\\&') + ']' else @domain end address = if domain.nil? local elsif !display_name.nil? or domain[-1] == ?] '<' + local + '@' + domain + '>' else local + '@' + domain end comments = nil comments = unless @comments.nil? @comments.collect { |c| '(' + c.gsub(/[()\\]/, '\\\\\&') + ')' }.join(' ') end [display_name, address, comments].compact.join(' ') end # Addresses can be converted into strings. alias :to_str :format # This class provides a facility to parse a string containing one # or more RFC2822 addresses into an array of RMail::Address # objects. You can use it directly, but it is more conveniently # used with the RMail::Address.parse method. class Parser # Create a RMail::Address::Parser object that will parse # +string+. See also the RMail::Address.parse method. def initialize(string) @string = string end # This function attempts to extract mailing addresses from the # string passed to #new. The function returns an # RMail::Address::List of RMail::Address objects # (RMail::Address::List is a subclass of Array). A malformed # input string will not generate an exception. Instead, the # array returned will simply not contained the malformed # addresses. # # The string is expected to be in a valid format as documented # in RFC2822's mailbox-list grammar. This will work for lists # of addresses in the To:, From:, etc. headers # in email. def parse @lexemes = [] @tokens = [] @addresses = RMail::Address::List.new @errors = 0 new_address get address_list reset_errors @addresses.delete_if { |a| !a.local || !a.domain } end private SYM_ATOM = :atom SYM_ATOM_NON_ASCII = :atom_non_ascii SYM_QTEXT = :qtext SYM_COMMA = :comma SYM_LESS_THAN = :less_than SYM_GREATER_THAN = :greater_than SYM_AT_SIGN = :at_sign SYM_PERIOD = :period SYM_COLON = :colon SYM_SEMI_COLON = :semi_colon SYM_DOMAIN_LITERAL = :domain_literal def reset_errors if @errors > 0 @addresses.pop @errors = 0 end end def new_address reset_errors @addresses.push(Address.new) end # Get the text that has been saved up to this point. def get_text text = '' sep = '' @lexemes.each { |lexeme| if lexeme == '.' text << lexeme sep = '' else text << sep text << lexeme sep = ' ' end } @lexemes = [] text end # Save the current lexeme away for later retrieval with # get_text. def save_text @lexemes << @lexeme end # Parse this: # address_list = ([address] SYNC ",") {[address] SYNC "," } [address] . def address_list if @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT || @sym == SYM_LESS_THAN address end sync(SYM_COMMA) return if @sym.nil? expect(SYM_COMMA) new_address while @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT || @sym == SYM_LESS_THAN || @sym == SYM_COMMA if @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT || @sym == SYM_LESS_THAN address end sync(SYM_COMMA) return if @sym.nil? expect(SYM_COMMA) new_address end if @sym == SYM_ATOM || @sym == SYM_QTEXT || @sym == SYM_LESS_THAN address end end # Parses ahead through a local-part or display-name until no # longer looking at a word or "." and returns the next symbol. def address_lookahead lookahead = [] while @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT || @sym == SYM_PERIOD lookahead.push([@sym, @lexeme]) get end retval = @sym putback(@sym, @lexeme) putback_array(lookahead) get retval end # Parse this: # address = mailbox | group def address # At this point we could be looking at a display-name, angle # addr, or local-part. If looking at a local-part, it could # actually be a display-name, according to the following: # # local-part '@' -> it is a local part of a local-part @ domain # local-part '<' -> it is a display-name of a mailbox # local-part ':' -> it is a display-name of a group # display-name '<' -> it is a mailbox display name # display-name ':' -> it is a group display name # set lookahead to '@' '<' or ':' (or another value for # invalid input) lookahead = address_lookahead if lookahead == SYM_COLON group else mailbox(lookahead) end end # Parse this: # mailbox = angleAddr | # word {word | "."} angleAddr | # word {"." word} "@" domain . # # lookahead will be set to the return value of # address_lookahead, which will be '@' or '<' (or another value # for invalid input) def mailbox(lookahead) if @sym == SYM_LESS_THAN angle_addr elsif lookahead == SYM_LESS_THAN display_name_word while @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT || @sym == SYM_PERIOD if @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT display_name_word else save_text get end end @addresses.last.display_name = get_text angle_addr else word while @sym == SYM_PERIOD save_text get word end @addresses.last.local = get_text expect(SYM_AT_SIGN) domain if @sym == SYM_LESS_THAN # Workaround for invalid input. Treat 'foo@bar ' as if it # were '"foo@bar" '. The domain parser will eat # 'bar' but stop at '<'. At this point, we've been # parsing the display name as if it were an address, so we # throw the address into display_name and parse an # angle_addr. @addresses.last.display_name = format("%s@%s", @addresses.last.local, @addresses.last.domain) @addresses.last.local = nil @addresses.last.domain = nil angle_addr end end end # Parse this: # group = word {word | "."} SYNC ":" [mailbox_list] SYNC ";" def group word while @sym == SYM_ATOM || @sym == SYM_QTEXT || @sym == SYM_PERIOD if @sym == SYM_ATOM || @sym == SYM_QTEXT word else save_text get end end sync(SYM_COLON) expect(SYM_COLON) get_text # throw away group name @addresses.last.comments = nil if @sym == SYM_ATOM || @sym == SYM_QTEXT || @sym == SYM_COMMA || @sym == SYM_LESS_THAN mailbox_list end sync(SYM_SEMI_COLON) expect(SYM_SEMI_COLON) end # Parse this: # word = atom | atom_non_ascii | quotedString def display_name_word if @sym == SYM_ATOM || @sym == SYM_ATOM_NON_ASCII || @sym == SYM_QTEXT save_text get else error "expected word, got #{@sym.inspect}" end end # Parse this: # word = atom | quotedString def word if @sym == SYM_ATOM || @sym == SYM_QTEXT save_text get else error "expected word, got #{@sym.inspect}" end end # Parse a mailbox list. def mailbox_list mailbox(address_lookahead) while @sym == SYM_COMMA get new_address mailbox(address_lookahead) end end # Parse this: # angleAddr = SYNC "<" [obsRoute] addrSpec SYNC ">" def angle_addr expect(SYM_LESS_THAN) if @sym == SYM_AT_SIGN obs_route end addr_spec expect(SYM_GREATER_THAN) end # Parse this: # domain = domainLiteral | obsDomain def domain if @sym == SYM_DOMAIN_LITERAL save_text @addresses.last.domain = get_text get elsif @sym == SYM_ATOM obs_domain @addresses.last.domain = get_text else error "expected start of domain, got #{@sym.inspect}" end end # Parse this: # addrSpec = localPart "@" domain def addr_spec local_part expect(SYM_AT_SIGN) domain end # Parse this: # local_part = word *( "." word ) def local_part word while @sym == SYM_PERIOD save_text get word end @addresses.last.local = get_text end # Parse this: # obs_domain = atom *( "." atom ) . def obs_domain expect_save(SYM_ATOM) while @sym == SYM_PERIOD save_text get expect_save(SYM_ATOM) end end # Parse this: # obs_route = obs_domain_list ":" def obs_route obs_domain_list expect(SYM_COLON) end # Parse this: # obs_domain_list = "@" domain *( *( "," ) "@" domain ) def obs_domain_list expect(SYM_AT_SIGN) domain while @sym == SYM_COMMA || @sym == SYM_AT_SIGN while @sym == SYM_COMMA get end expect(SYM_AT_SIGN) domain end end # Put a token back into the input stream. This token will be # retrieved by the next call to get. def putback(sym, lexeme) @tokens.push([sym, lexeme]) end # Put back an array of tokens into the input stream. def putback_array(a) a.reverse_each { |e| putback(*e) } end # Get a single token from the string or from the @tokens array # if somebody used putback. def get unless @tokens.empty? @sym, @lexeme = @tokens.pop else get_tokenize end end # Get a single token from the string def get_tokenize @lexeme = nil loop { case @string when nil # the end @sym = nil break when "" # the end @sym = nil break when /\A[\r\n\t ]+/m # skip whitespace @string = $' when /\A\(/m # skip comment comment when /\A""/ # skip empty quoted text @string = $' when /\A[\w!$%&\'*+\/=?^\`{\}|~#-]+/m @string = $' @sym = SYM_ATOM break when /\A"(.*?([^\\]|\\\\))"/m @string = $' @sym = SYM_QTEXT @lexeme = $1.gsub(/\\(.)/, '\1') break when /\A/ @string = $' @sym = SYM_GREATER_THAN break when /\A@/ @string = $' @sym = SYM_AT_SIGN break when /\A,/ @string = $' @sym = SYM_COMMA break when /\A:/ @string = $' @sym = SYM_COLON break when /\A;/ @string = $' @sym = SYM_SEMI_COLON break when /\A\./ @string = $' @sym = SYM_PERIOD break when /\A(\[.*?([^\\]|\\\\)\])/m @string = $' @sym = SYM_DOMAIN_LITERAL @lexeme = $1.gsub(/(^|[^\\])[\r\n\t ]+/, '\1').gsub(/\\(.)/, '\1') break when /\A[\200-\377\w!$%&\'*+\/=?^\`{\}|~#-]+/nm # This is just like SYM_ATOM, but includes all characters # with high bits. This is so we can allow such tokens in # the display name portion of an address even though it # violates the RFCs. @string = $' @sym = SYM_ATOM_NON_ASCII break when /\A./ @string = $' # garbage error('garbage character in string') else raise "internal error, @string is #{@string.inspect}" end } if @sym @lexeme ||= $& end end def comment depth = 0 comment = '' catch(:done) { while @string =~ /\A(\(([^\(\)\\]|\\.)*)/m @string = $' comment += $1 depth += 1 while @string =~ /\A(([^\(\)\\]|\\.)*\))/m @string = $' comment += $1 depth -= 1 throw :done if depth == 0 if @string =~ /\A(([^\(\)\\]|\\.)+)/ @string = $' comment += $1 end end end } comment = comment.gsub(/[\r\n\t ]+/m, ' '). sub(/\A\((.*)\)$/m, '\1'). gsub(/\\(.)/, '\1') @addresses.last.comments = (@addresses.last.comments || []) + [comment] end def expect(token) if @sym == token get else error("expected #{token.inspect} but got #{@sym.inspect}") end end def expect_save(token) if @sym == token save_text end expect(token) end def sync(token) while @sym && @sym != token error "expected #{token.inspect} but got #{@sym.inspect}" get end end def error(s) @errors += 1 end end # Given a string, this function attempts to extract mailing # addresses from it and returns an RMail::Address::List of those # addresses (RMail::Address::List is a subclass of Array). # # This is identical to using a RMail::Address::Parser directly like # this: # # RMail::Address::Parser.new(string).parse def Address.parse(string) Parser.new(string).parse end # RMail::Address::List is a simple subclass of the Array class # that provides convenience methods for accessing the # RMail::Address objects it contains. class List < Array # Returns an array of strings -- the result of calling # RMail::Address#local on each element of the list. def locals collect { |a| a.local } end # Returns an array of strings -- the result of calling # RMail::Address#display_name on each element of the list. def display_names collect { |a| a.display_name } end # Returns an array of strings -- the result of calling # RMail::Address#name on each element of the list. def names collect { |a| a.name } end # Returns an array of strings -- the result of calling # RMail::Address#domain on each element of the list. def domains collect { |a| a.domain } end # Returns an array of strings -- the result of calling # RMail::Address#address on each element of the list. def addresses collect { |a| a.address } end # Returns an array of strings -- the result of calling # RMail::Address#format on each element of the list. def format collect { |a| a.format } end end end end if $0 == __FILE__ parser = RMail::Address::Parser.new('A Group:a@b.c,d@e.f;') p parser.parse end rmail-1.1.4/lib/rmail/message.rb0000644000004100000410000001567013701337101016501 0ustar www-datawww-data#-- # Copyright (C) 2001, 2002, 2003 Matt Armstrong. All rights # reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Message class. require 'rmail/header.rb' module RMail # The RMail::Message is an object representation of a standard # Internet email message, including MIME multipart messages. # # An RMail::Message object represents a message header (held in the # contained RMail::Header object) and a message body. The message # body may either be a single String for single part messages or an # Array of RMail::Message objects for MIME multipart messages. class Message # Create a new, empty, RMail::Message. def initialize @header = RMail::Header.new @body = nil @epilogue = nil @preamble = nil @delimiters = nil end # Test if this message is structured exactly the same as the other # message. This is useful mainly for testing. def ==(other) @preamble == other.preamble && @epilogue == other.epilogue && @header == other.header && @body == other.body end # Returns the body of the message as a String or Array. # # If #multipart? returns true, it will be an array of # RMail::Message objects. Otherwise it will be a String. # # See also #header. def body return @body end # Sets the body of the message to the given value. It should # either be a String or an Array of RMail:Message objects. def body=(s) @body = s end # Returns the RMail::Header object. # # See also #body. def header() return @header end # Return true if the message consists of multiple parts. def multipart? @body.is_a?(Array) end # Add a part to the message. After this message is called, the # #multipart? method will return true and the #body method will # #return an array of parts. def add_part(part) if @body.nil? @body = [part] elsif @body.is_a?(Array) @body.push(part) else @body = [@body, part] end end # Decode the body of this message. # # If the body of this message is encoded with # quoted-printable or base64, this function will # decode the data into its original form and return it as a # String. If the body is not encoded, it is returned unaltered. # # This only works when the message is not a multipart. The # Content-Transfer-Encoding: header field is consulted to # determine the encoding of the body part. def decode raise TypeError, "Can not decode a multipart message." if multipart? case header.fetch('content-transfer-encoding', '7bit').strip.downcase when 'quoted-printable' Utils.quoted_printable_decode(@body) when 'base64' Utils.base64_decode(@body) else @body end end # Get the indicated part from a multipart message. def part(i) raise TypeError, "Can not get part on a single part message." unless multipart? @body[i] end # Access the epilogue string for this message. The epilogue # string is relevant only for multipart messages. It is the text # that occurs after all parts of the message and is generally nil. attr_accessor :epilogue # Access the preamble string for this message. The preamble # string is relevant only for multipart messages. It is the text # that occurs just before the first part of the message, and is # generally nil or simple English text describing the nature of # the message. attr_accessor :preamble # Returns the entire message in a single string. This uses the # RMail::Serialize class. def to_s() require 'rmail/serialize' RMail::Serialize.new('').serialize(self) end # Return each part of this message # # FIXME: not tested def each_part raise TypeError, "not a multipart message" unless multipart? @body.each do |part| yield part end end # Call the supplied block for each line of the message. Each line # will contain a trailing newline (\n). def each() # FIXME: this is incredibly inefficient! The only users of this # is RMail::Deliver -- get them to use a RMail::Serialize object. to_s.each("\n") { |line| yield line } end # This is used by the RMail::Parser to set the MIME multipart # delimiter strings found in the message. These delimiters are # then used when serializing the message again. # # Normal uses of RMail::Message will never use this method, and so # it is left undocumented. def set_delimiters(delimiters, boundary) # :nodoc: raise TypeError, "not a multipart message" unless multipart? raise ArgumentError, "delimiter array wrong size" unless delimiters.length == @body.length + 1 @delimiters = delimiters.to_ary @delimiters_boundary = boundary.to_str end # This is used by the serializing functions to retrieve the MIME # multipart delimiter strings found while parsing the message. # These delimiters are then used when serializing the message # again. # # Normal uses of RMail::Message will never use this method, and so # it is left undocumented. def get_delimiters # :nodoc: unless multipart? and @delimiters and @delimiters_boundary and @delimiters.length == @body.length + 1 and header.param('content-type', 'boundary') == @delimiters_boundary @delimiters = nil @delimiters_boundary = nil end [ @delimiters, @delimiters_boundary ] end end end rmail-1.1.4/lib/rmail/header.rb0000644000004100000410000007166513701337101016313 0ustar www-datawww-data#-- # Copyright (c) 2001, 2002, 2003, 2004 Matt Armstrong. All rights # reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Header class. require 'rmail/utils' require 'rmail/address' require 'digest/md5' require 'time' module RMail # A class that supports the reading, writing and manipulation of # RFC2822 mail headers. # =Overview # # The RMail::Header class supports the creation and manipulation of # RFC2822 mail headers. # # A mail header is a little bit like a Hash. The fields are keyed # by a string field name. It is also a little bit like an Array, # since the fields are in a specific order. This class provides # many of the methods of both the Hash and Array class. It also # includes the Enumerable module. # # =Terminology # # header:: The entire header. Each RMail::Header object is one # mail header. # # field:: An element of the header. Fields have a name and a value. # For example, the field "Subject: Hi Mom!" has a name of # "Subject" and a value of "Hi Mom!" # # name:: A name of a field. For example: "Subject" or "From". # # value:: The value of a field. # # =Conventions # # The header's fields are stored in a particular order. Methods # such as #each process the headers in this order. # # When field names or values are added to the object they are # frozen. This helps prevent accidental modification to what is # stored in the object. class Header include Enumerable class Field # :nodoc: # fixme, document methadology for this (RFC2822) # accoring to RFC2822 the header field name can consist of any # ASCII char between and including 33 and 126. EXTRACT_FIELD_NAME_RE = /\A(^\w[-\w]+):\s*/o class << self def parse(field) field = field.to_str if field =~ EXTRACT_FIELD_NAME_RE [ $1, $'.chomp ] else [ "", Field.value_strip(field) ] end end end def initialize(name, value = nil) if value @name = Field.name_strip(name.to_str).freeze @value = Field.value_strip(value.to_str).freeze @raw = nil else @raw = name.to_str.freeze @name, @value = Field.parse(@raw) @name.freeze @value.freeze end end attr_reader :name, :value, :raw def ==(other) other.kind_of?(self.class) && @name.downcase == other.name.downcase && @value == other.value end def Field.name_canonicalize(name) name_strip(name.to_str).downcase end private def Field.name_strip(name) name.sub(/\s*:.*/, '') end def Field.value_strip(value) if value.frozen? value = value.dup end value.strip! value end end # Creates a new empty header object. def initialize() clear() end # Return the value of the first matching field of a field name, or # nil if none found. If passed an Integer, returns the header # indexed by the number. def [](name_or_index) if name_or_index.kind_of? Integer temp = @fields[name_or_index] temp = temp.value unless temp.nil? else name = Field.name_canonicalize(name_or_index) result = detect { |n, v| if n.downcase == name then true else false end } if result.nil? then nil else result[1] end end end # Creates a copy of this header object. A new RMail::Header is # created and the instance data is copied over. However, the new # object will still reference the same strings held in the # original object. Since these strings are frozen, this usually # won't matter. def dup h = super h.fields = @fields.dup h.mbox_from = @mbox_from h end # Creates a complete copy of this header object, including any # singleton methods and strings. The returned object will be a # complete and unrelated duplicate of the original. def clone h = super h.fields = Marshal::load(Marshal::dump(@fields)) h.mbox_from = Marshal::load(Marshal::dump(@mbox_from)) h end # Delete all fields in this object. Returns self. def clear() @fields = [] @mbox_from = nil self end # Replaces the contents of this header with that of another header # object. Returns self. def replace(other) unless other.kind_of?(RMail::Header) raise TypeError, "#{other.class.to_s} is not of type RMail::Header" end temp = other.dup @fields = temp.fields @mbox_from = temp.mbox_from self end # Return the number of fields in this object. def length @fields.length end alias size length # Return the value of the first matching field of a given name. # If there is no such field, the value returned by the supplied # block is returned. If no block is passed, the value of # +default_value+ is returned. If no +default_value+ is # specified, an IndexError exception is raised. def fetch(name, *rest) if rest.length > 1 raise ArgumentError, "wrong # of arguments(#{rest.length + 1} for 2)" end result = self[name] if result.nil? if block_given? yield name elsif rest.length == 1 rest[0] else raise IndexError, 'name not found' end else result end end # Returns the values of every field named +name+. If there are no # such fields, the value returned by the block is returned. If no # block is passed, the value of +default_value+ is returned. If # no +default_value+ is specified, an IndexError exception is # raised. def fetch_all name, *rest if rest.length > 1 raise ArgumentError, "wrong # of arguments(#{rest.length + 1} for 2)" end result = select(name) if result.nil? if block_given? yield name elsif rest.length == 1 rest[0] else raise IndexError, 'name not found' end else result.collect { |n, v| v } end end # Returns true if the message has a field named 'name'. def field?(name) ! self[name].nil? end alias member? field? alias include? field? alias has_key? field? alias key? field? # Deletes all fields with +name+. Returns self. def delete(name) name = Field.name_canonicalize(name.to_str) delete_if { |n, v| n.downcase == name } self end # Deletes the field at the specified index and returns its value. def delete_at(index) @fields.delete_at(index) self end # Deletes the field if the passed block returns true. Returns # self. def delete_if # yields: name, value @fields.delete_if { |i| yield i.name, i.value } self end # Executes block once for each field in the header, passing the # key and value as parameters. # # Returns self. def each # yields: name, value @fields.each { |i| yield [i.name, i.value] } end alias each_pair each # Executes block once for each field in the header, passing the # field's name as a parameter. # # Returns self def each_name @fields.each { |i| yield(i.name) } end alias each_key each_name # Executes block once for each field in the header, passing the # field's value as a parameter. # # Returns self def each_value @fields.each { |i| yield(i.value) } end # Returns true if the header contains no fields def empty? @fields.empty? end # Returns an array of pairs [ name, value ] for all fields with # one of the names passed. def select(*names) result = [] names.each { |name| name = Field.name_canonicalize(name) result.concat(find_all { |n, v| n.downcase == name }) } result end # Returns an array consisting of the names of every field in this # header. def names collect { |n, v| n } end alias keys names # Add a new field with +name+ and +value+. When +index+ is nil # (the default if not specified) the line is appended to the # header, otherwise it is inserted at the specified index. # E.g. an +index+ of 0 will prepend the header line. # # You can pass additional parameters for the header as a hash # table +params+. Every key of the hash will be the name of the # parameter, and every key's value the parameter value. # # E.g. # # header.add('Content-Type', 'multipart/mixed', nil, # 'boundary' => 'the boundary') # # will add this header # # Content-Type: multipart/mixed; boundary="the boundary" # # Always returns self. def add(name, value, index = nil, params = nil) value = value.to_str if params value = value.dup sep = "; " params.each do |n, v| value << sep value << n.to_s value << '=' v = v.to_s if v =~ /^\w+$/ value << v else value << '"' value << v value << '"' end end end field = Field.new(name, value) index ||= @fields.length @fields[index, 0] = field self end # Add a new field as a raw string together with a parsed # name/value. This method is used mainly by the parser and # regular programs should stick to #add. def add_raw(raw) @fields << Field.new(raw) self end # First delete any fields with +name+, then append a new field # with +name+, +value+, and +params+ as in #add. def set(name, value, params = nil) delete(name) add(name, value, nil, params) end # Append a new field with +name+ and +value+. If you want control # of where the field is inserted, see #add. # # Returns +value+. def []=(name, value) add(name, value) value end # Returns true if the two objects have the same number of fields, # in the same order, with the same values. def ==(other) return other.kind_of?(self.class) && @fields == other.fields && @mbox_from == other.mbox_from end # Returns a new array holding one [ name, value ] array per field # in the header. def to_a @fields.collect { |field| [ field.name, field.value ] } end # Converts the header to a string, including any mbox from line. # Equivalent to header.to_string(true). def to_s to_string(true) end # Converts the header to a string. If +mbox_from+ is true, then # the mbox from line is also included. def to_string(mbox_from = false) s = "" if mbox_from && ! @mbox_from.nil? s << @mbox_from s << "\n" unless @mbox_from[-1] == ?\n end @fields.each { |field| if field.raw s << field.raw else s << field.name s << ': ' s << field.value end s << "\n" unless s[-1] == ?\n } s end # Determine if there is any fields that match the given +name+ and # +value+. # # If +name+ is a String, all fields of that name are tested. If # +name+ is a Regexp the field names are matched against the # regexp (the field names are converted to lower case first). Use # the regexp // if you want to test all field names. # # If +value+ is a String, it is converted to a case insensitive # Regexp that matches the string. Otherwise, it must be a Regexp. # Note that the field value may be folded across many lines, so # you should use a multi-line Regexp. Also consider using a case # insensitive Regexp. Use the regexp // if you want to match all # possible field values. # # Returns true if there is a match, false otherwise. # # Example: # # if h.match?('x-ml-name', /ruby-dev/im) # # do something # end # # See also: #match def match?(name, value) massage_match_args(name, value) { |mname, mvalue| match = detect {|n, v| n =~ mname && v =~ mvalue } ! match.nil? } end # Find all fields that match the given +name and +value+. # # If +name+ is a String, all fields of that name are tested. If # +name+ is a Regexp, the field names are matched against the # regexp (the field names are converted to lower case first). Use # the regexp // if you want to test all field names. # # If +value+ is a String, it is converted to a case insensitive # Regexp that matches the string. Otherwise, it must be a Regexp. # Note that the field value may be folded across many lines, so # you may need to use a multi-line Regexp. Also consider using a # case insensitive Regexp. Use the regexp // if you want to match # all possible field values. # # Returns a new RMail::Header holding all matching headers. # # Examples: # # received = header.match('Received', //) # destinations = header.match(/^(to|cc|bcc)$/, //) # bigfoot_received = header.match('received', # /from.*by.*bigfoot\.com.*LiteMail/im) # # See also: #match? def match(name, value) massage_match_args(name, value) { |mname, mvalue| header = RMail::Header.new each { |n, v| if n.downcase =~ mname && mvalue =~ v header[n] = v end } header } end # Sets the "From " line commonly used in the Unix mbox mailbox # format. The +value+ supplied should be the entire "From " line. def mbox_from=(value) @mbox_from = value end # Gets the "From " line previously set with mbox_from=, or nil. def mbox_from @mbox_from end # This returns the full content type of this message converted to # lower case. # # If there is no content type header, returns the passed block is # executed and its return value is returned. If no block is passed, # the value of the +default+ argument is returned. def content_type(default = nil) content_type = self['content-type'] if content_type && content_type.length > 0 content_type.strip.split(/\s*;\s*/)[0].downcase else if block_given? yield else default end end end # This returns the main media type for this message converted to # lower case. This is the first portion of the content type. # E.g. a content type of text/plain has a media type of # text. # # If there is no content type field, returns the passed block is # executed and its return value is returned. If no block is # passed, the value of the +default+ argument is returned. def media_type(default = nil) if value = content_type value.split('/')[0] else if block_given? yield else default end end end # This returns the media subtype for this message, converted to # lower case. This is the second portion of the content type. # E.g. a content type of text/plain has a media subtype # of plain. # # If there is no content type field, returns the passed block is # executed and its return value is returned. If no block is passed, # the value of the +default+ argument is returned. def subtype(default = nil) if value = content_type value.split('/')[1] else if block_given? then yield else default end end end # This returns a hash of parameters. Each key in the hash is the # name of the parameter in lower case and each value in the hash # is the unquoted parameter value. If a parameter has no value, # its value in the hash will be +true+. # # If the field or parameter does not exist or it is malformed in a # way that makes it impossible to parse, then the passed block is # executed and its return value is returned. If no block is # passed, the value of the +default+ argument is returned. def params(field_name, default = nil) if params = params_quoted(field_name) params.each { |name, value| params[name] = value ? Utils.unquote(value) : nil } else if block_given? yield field_name else default end end end # This returns the parameter value for the given parameter in the # given field. The value returned is unquoted. # # If the field or parameter does not exist or it is malformed in a # way that makes it impossible to parse, then the passed block is # executed and its return value is returned. If no block is # passed, the value of the +default+ argument is returned. def param(field_name, param_name, default = nil) if field?(field_name) params = params_quoted(field_name) value = params[param_name] return Utils.unquote(value) if value end if block_given? yield field_name, param_name else default end end # Set the boundary parameter of this message's Content-Type: # field. def set_boundary(boundary) params = params('content-type') params ||= {} params['boundary'] = boundary content_type = content_type() content_type ||= "multipart/mixed" delete('Content-Type') add('Content-Type', content_type, nil, params) end # Return the value of the Date: field, parsed into a Time # object. Returns nil if there is no Date: field or the field # value could not be parsed. def date if value = self['date'] begin # Rely on Ruby's standard time.rb to parse the time. (Time.rfc2822(value) rescue Time.parse(value)).localtime rescue # Exceptions during time parsing just cause nil to be # returned. end end end # Deletes any existing Date: fields and appends a new one # corresponding to the given Time object. def date=(time) delete('Date') add('Date', time.rfc2822) end # Returns the value of the From: header as an Array of # RMail::Address objects. # # See #address_list_fetch for details on what is returned. # # This method does not return a single RMail::Address value # because it is legal to have multiple addresses in a From: # header. # # This method always returns at least the empty list. So if you # are always only interested in the first from address (most # likely the case), you can safely say: # # header.from.first def from address_list_fetch('from') end # Sets the From: field to the supplied address or addresses. # # See #address_list_assign for information on valid values for # +addresses+. # # Note that the From: header usually contains only one address, # but it is legal to have more than one. def from=(addresses) address_list_assign('From', addresses) end # Returns the value of the To: field as an Array of RMail::Address # objects. # # See #address_list_fetch for details on what is returned. def to address_list_fetch('to') end # Sets the To: field to the supplied address or addresses. # # See #address_list_assign for information on valid values for # +addresses+. def to=(addresses) address_list_assign('To', addresses) end # Returns the value of the Cc: field as an Array of RMail::Address # objects. # # See #address_list_fetch for details on what is returned. def cc address_list_fetch('cc') end # Sets the Cc: field to the supplied address or addresses. # # See #address_list_assign for information on valid values for # +addresses+. def cc=(addresses) address_list_assign('Cc', addresses) end # Returns the value of the Bcc: field as an Array of # RMail::Address objects. # # See #address_list_fetch for details on what is returned. def bcc address_list_fetch('bcc') end # Sets the Bcc: field to the supplied address or addresses. # # See #address_list_assign for information on valid values for # +addresses+. def bcc=(addresses) address_list_assign('Bcc', addresses) end # Returns the value of the Reply-To: header as an Array of # RMail::Address objects. def reply_to address_list_fetch('reply-to') end # Sets the Reply-To: field to the supplied address or addresses. # # See #address_list_assign for information on valid values for # +addresses+. def reply_to=(addresses) address_list_assign('Reply-To', addresses) end # Returns the value of this object's Message-Id: field. def message_id self['message-id'] end # Sets the value of this object's Message-Id: field to a new # random value. # # If you don't supply a +fqdn+ (fully qualified domain name) then # one will be randomly generated for you. If a valid address # exists in the From: field, its domain will be used as a basis. # # Part of the randomness in the header is taken from the header # itself, so it is best to call this method after adding other # fields to the header -- especially those that make it unique # (Subject:, To:, Cc:, etc). def add_message_id(fqdn = nil) # If they don't supply a fqdn, we supply one for them. # # First grab the From: field and see if we can use a domain from # there. If so, use that domain name plus the hash of the From: # field's value (this guarantees that bob@example.com and # sally@example.com will never have clashes). # # If there is no From: field, grab the current host name and use # some randomness from Ruby's random number generator. Since # Ruby's random number generator is fairly good this will # suffice so long as it is seeded corretly. # # P.S. There is no portable way to get the fully qualified # domain name of the current host. Those truly interested in # generating "correct" message-ids should pass it in. We # generate a hopefully random and unique domain name. unless fqdn unless fqdn = from.domains.first require 'socket' fqdn = sprintf("%s.invalid", Socket.gethostname) end else raise ArgumentError, "fqdn must have at least one dot" unless fqdn.index('.') end # Hash the header we have so far. md5 = Digest::MD5.new starting_digest = md5.digest @fields.each { |f| if f.raw md5.update(f.raw) else md5.update(f.name) if f.name md5.update(f.value) if f.value end } if (digest = md5.digest) == starting_digest digest = 0 end set('Message-Id', sprintf("<%s.%s.%s.rubymail@%s>", base36(Time.now.to_i), base36(rand(MESSAGE_ID_MAXRAND)), base36(digest), fqdn)) end # Return the subject of this message. def subject self['subject'] end # Set the subject of this message def subject=(string) set('Subject', string) end # Returns an RMail::Address::List array holding all the recipients # of this message. This uses the contents of the To, Cc, and Bcc # fields. Duplicate addresses are eliminated. def recipients RMail::Address::List.new([ to, cc, bcc ].flatten.uniq) end # Retrieve a given field's value as an RMail::Address::List of # RMail::Address objects. # # This method is used to implement many of the convenience methods # such as #from, #to, etc. def address_list_fetch(field_name) if values = fetch_all(field_name, nil) list = nil values.each { |value| if list list.concat(Address.parse(value)) else list = Address.parse(value) end } if list and !list.empty? list end end or RMail::Address::List.new end # Set a given field to a list of supplied +addresses+. # # The +addresses+ may be a String, RMail::Address, or Array. If a # String, it is parsed for valid email addresses and those found # are used. If an RMail::Address, the result of # RMail::Address#format is used. If an Array, each element of the # array must be either a String or RMail::Address and is treated # as above. # # This method is used to implement many of the convenience methods # such as #from=, #to=, etc. def address_list_assign(field_name, addresses) if addresses.kind_of?(Array) value = addresses.collect { |e| if e.kind_of?(RMail::Address) e.format else RMail::Address.parse(e.to_str).collect { |a| a.format } end }.flatten.join(", ") set(field_name, value) elsif addresses.kind_of?(RMail::Address) set(field_name, addresses.format) else address_list_assign(field_name, RMail::Address.parse(addresses.to_str)) end end protected attr_accessor :fields private MESSAGE_ID_MAXRAND = 0x7fffffff def string2num(string) temp = 0 string.reverse.each_byte { |b| temp <<= 8 temp |= b } return temp end BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz" def base36(number) if number.kind_of?(String) number = string2num(number) end raise ArgumentError, "need non-negative number" if number < 0 return "0" if number == 0 result = "" while number > 0 number, remainder = number.divmod(36) result << BASE36[remainder] end return result.reverse! end PARAM_SCAN_RE = %r{ ; | [^;"]*"(?:|.*?(?:[^\\]|\\\\))"\s* # fix fontification " | [^;]+ }x NAME_VALUE_SCAN_RE = %r{ = | [^="]*"(?:.*?(?:[^\\]|\\\\))" # fix fontification "\s* | [^=]+ }x def params_quoted(field_name, default = nil) if value = self[field_name] params = {} first = true value.scan(PARAM_SCAN_RE) do |param| if param != ';' unless first name, value = param.scan(NAME_VALUE_SCAN_RE).collect do |p| if p == '=' then nil else p end end.compact if name && (name = name.strip.downcase) && name.length > 0 params[name] = (value || '').strip end else first = false end end end params else if block_given? then yield field_name else default end end end def massage_match_args(name, value) case name when String name = /^#{Regexp.escape(Field.name_strip(name))}$/i when Regexp else raise ArgumentError, "name not a Regexp or String: #{name.class}:#{name.inspect}" end case value when String value = Regexp.new(Regexp.escape(value), Regexp::IGNORECASE) when Regexp else raise ArgumentError, "value not a Regexp or String" end yield(name, value) end end end rmail-1.1.4/lib/rmail/mailbox/0000755000004100000410000000000013701337101016152 5ustar www-datawww-datarmail-1.1.4/lib/rmail/mailbox/mboxreader.rb0000644000004100000410000001445613701337101020641 0ustar www-datawww-data#-- # Copyright (c) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Mailbox::MBoxReader class. require 'rmail/parser/pushbackreader' module RMail module Mailbox # Class that can parse Unix mbox style mailboxes. These mailboxes # separate individual messages with a line beginning with the # string "From ". # # Typical usage: # # File.open("file.mbox") { |file| # RMail::Mailbox::MBoxReader.new(file).each_message { |input| # message = RMail::Parser.read(input) # # do something with the message # end # } # # Or see RMail::Mailbox.parse_mbox for a more convenient # interface. # class MBoxReader < RMail::Parser::PushbackReader # Creates a new MBoxReader that reads from `input' with lines # that end with `line_separator'. # # `input' can either be an IO source (an object that responds to # the "read" method in the same way as a standard IO object) or # a String. # # `line_separator' defaults to $/, and useful values are # probably limited to "\n" (Unix) and "\r\n" (DOS/Windows). def initialize(input, line_separator = $/) super(input) @end_of_message = false @chunk_minsize = 0 @sep = line_separator @tail = nil # This regexp will match a From_ header, or some prefix. re_string = RMail::Parser::PushbackReader. maybe_contains_re("#{@sep}From ") @partial_from_re = Regexp.new(re_string) # This regexp will match an entire From_ header. @entire_from_re = /\A#{@sep}From .*?#{@sep}/ end alias_method :parent_read_chunk, :read_chunk # Reads some data from the current message and returns it. The # `size' argument is just a suggestion, and the returned string # can be larger or smaller. When `size' is nil, then the entire # message is returned. # # Once all data from the current message has been read, #read # returns nil and #next must be called to begin reading from the # next message. You can use #eof to tell if there is any more # data to be read from the input source. def read_chunk(size) chunk = read_chunk_low(size) if chunk if chunk.length > @sep.length @tail = chunk[-@sep.length .. -1] else @tail ||= '' @tail << chunk end elsif @tail if @tail[-@sep.length .. -1] != @sep chunk = @sep end @tail = nil end chunk end # Advances to the next message to be read. Call this after # #read returns nil. # # Note: Once #read returns nil, you can call #eof before or # after calling #next to tell if there actually is a next # message to read. def next @end_of_message = false @tail = nil end alias_method :parent_eof, :eof # Returns true if the next call to read_chunk will return nil. def eof parent_eof and @tail.nil? end # Yield self until eof, calling next after each yield. # # This method makes it simple to read messages successively out # of the mailbox. See the class description for a code example. def each_message while !eof yield self self.next end end private def read_chunk_low(size) return nil if @end_of_message if chunk = parent_read_chunk(size) # Read at least @chunk_minsize bytes. while chunk.length < @chunk_minsize && more = parent_read_chunk(size) chunk << more end if match = @partial_from_re.match(chunk) # We matched what might be a From_ separator. Separate # the chunk into what came before and what came after it. mbegin = match.begin(0) rest = chunk[mbegin .. -1] if @entire_from_re =~ rest # We've got a full From_ line, so set the end of message # flag and get rid of the line separator present just # before the From_. @end_of_message = true @chunk_minsize = 0 rest[0, @sep.length] = "" # painful else # Make sure that next time we read more than just the # pushback. @chunk_minsize = rest.length + 1 end # Return the whole chunk with a partially matched From_ # when there is nothing further to read. unless ! @end_of_message && parent_eof # Otherwise, push back the From_ and return the # pre-match. pushback(rest) if mbegin == 0 and @end_of_message chunk = nil else chunk = chunk[0, mbegin] end end end end return chunk end end end end rmail-1.1.4/lib/rmail/mailbox.rb0000644000004100000410000000457613701337101016513 0ustar www-datawww-data#!/usr/bin/env ruby #-- # Copyright (c) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Mailbox module. module RMail # The RMail::Mailbox module contains a few methods that are useful # for working with mailboxes. module Mailbox class << self # Parse a Unix mbox style mailbox. These mailboxes searate # individual messages with a line beginning with the string # "From ". # # If a block is given, yields to the block with the raw message # (a string), otherwise an array of raw message strings is # returned. def parse_mbox(input, line_separator = $/) require 'rmail/mailbox/mboxreader' retval = [] RMail::Mailbox::MBoxReader.new(input, line_separator).each_message { |reader| raw_message = reader.read(nil) if block_given? yield raw_message else retval << raw_message end } return block_given? ? nil : retval end end end end rmail-1.1.4/lib/rmail/utils.rb0000644000004100000410000000422113701337101016203 0ustar www-datawww-data#-- # Copyright (C) 2002, 2003 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Utils module. module RMail # The RMail::Utils module is a collection of random utility methods # that are useful for dealing with email. module Utils class << self # Return the given string unquoted if it is quoted. def unquote(str) if str =~ /\s*"(.*?([^\\]|\\\\))"/m $1.gsub(/\\(.)/, '\1') else str end end # Decode the given string as if it were a chunk of base64 data def base64_decode(str) str.unpack("m*").first end # Decode the given string as if it were a chunk of quoted # printable data def quoted_printable_decode(str) str.unpack("M*").first end end end end rmail-1.1.4/lib/rmail/parser.rb0000644000004100000410000003215413701337101016345 0ustar www-datawww-data#-- # Copyright (C) 2002, 2003, 2004 Matt Armstrong. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #++ # Implements the RMail::Parser, RMail::StreamParser and # RMail::StreamHandler classes. require 'rmail/message' require 'rmail/parser/multipart' module RMail # = Overview # # An RMail::StreamHandler documents the set of methods a # RMail::StreamParser handler must implement. See # RMail::StreamParser.parse. This is a low level interface to the # RMail message parser. # # = Order of Method Calls (Grammar) # # Calls to the methods of this class follow a specific grammar, # described informally below. The words in all caps are productions # in the grammar, while the lower case words are method calls to # this object. # # MESSAGE:: [ #mbox_from ] *( #header_field ) # ( BODY / MULTIPART_BODY ) # # BODY:: *body_begin *( #body_chunk ) #body_end # # MULTIPART_BODY:: #multipart_body_begin # *( #preamble_chunk ) # *( #part_begin MESSAGE #part_end) # *( #epilogue_chunk ) # #multipart_body_end # # = Order of Method Calls (English) # # If the grammar above is not clear, here is a description in English. # # The parser begins calling #header_field, possibly calling # #mbox_from for the first line. Then it determines if the message # was a MIME multipart message. # # If the message is a not a MIME multipart, the parser calls # #body_begin once, then #body_chunk any number of times, then # #body_end. # # If the message header is a MIME multipart message, then # #multipart_body_begin is called, followed by any number of calls # to #preamble_chunk. Then for each part parsed, #part_begin is # called, followed by a recursive set of calls described by the # "MESSAGE" production above, and then #part_end. After all parts # are parsed, any number of calls to #epilogue_chunk are followed by # a single call to #multipart_body_end. # # The recursive nature of MIME multipart messages is represented by # the recursive invocation of the "MESSAGE" production in the # grammar above. class StreamHandler # This method is called for Unix MBOX "From " lines in the message # header, it calls this method with the text. def mbox_from(line) end # This method is called when a header field is parsed. The # +field+ is the full text of the field, the +name+ is the name of # the field and the +value+ is the field's value with leading and # trailing whitespace removed. Note that both +field+ and +value+ # may be multi-line strings. def header_field(field, name, value) end # This method is called before a non-multipart message body is # about to be parsed. def body_begin end # This method is called with a string chunk of data from a # non-multipart message body. The string does not necessarily # begin or end on any particular boundary. def body_chunk(chunk) end # This method is called after all of the non-multipart message # body has been parsed. def body_end end # This method is called before a multipart message body is about # to be parsed. def multipart_body_begin end # This method is called with a chunk of data from a multipart # message body's preamble. The preamble is any text that appears # before the first part of the multipart message body. def preamble_chunk(chunk) end # This method is called when a part of a multipart body begins. def part_begin end # This method is called when a part of a multipart body ends. def part_end end # This method is called with a chunk of data from a multipart # message body's epilogue. The epilogue is any text that appears # after the last part of the multipart message body. def epilogue_chunk(chunk) end # This method is called after a multipart message body has been # completely parsed. # # The +delimiters+ is an Array of strings, one for each boundary # string found in the multipart body. The +boundary+ is the # boundary string used to delimit each part in the multipart body. # You can normally ignore both +delimiters+ and +boundary+ if you # are concerned only about message content. def multipart_body_end(delimiters, boundary) end end # The RMail::StreamParser is a low level message parsing API. It is # useful when you are interested in serially examining all message # content but are not interested in a full object representation of # the object. See StreamParser.parse. class StreamParser class << self # Parse a message from an input source. This method returns # nothing. Instead, the supplied +handler+ is expected to # implement the same methods as RMail::StreamHandler. The # message structure can be inferred from the methods called on # the +handler+. The +input+ can be any Ruby IO source or a # String. # # This is a low level parsing API. For a message parser that # returns an RMail::Message object, see the RMail::Parser class. # RMail::Parser is implemented using RMail::StreamParser. def parse(input, handler) RMail::StreamParser.new(input, handler).parse end end def initialize(input, handler) # :nodoc: @input = input @handler = handler @chunk_size = nil end def parse # :nodoc: input = RMail::Parser::PushbackReader.new(@input) input.chunk_size = @chunk_size if @chunk_size parse_low(input, 0) return nil end # Change the chunk size used to read the message. This is useful # mostly for testing, so we don't document it. attr_accessor :chunk_size # :nodoc: private def parse_low(input, depth) multipart_boundary = parse_header(input, depth) if multipart_boundary parse_multipart_body(input, depth, multipart_boundary) else parse_singlepart_body(input, depth) end end def parse_header(input, depth) data = nil header = nil boundary = nil while chunk = input.read data ||= '' data << chunk if data[0] == ?\n # A leading newline in the message is seen when parsing the # parts of a multipart message. It means there are no # headers. The body part starts directly after this # newline. rest = data[1..-1] elsif data[0] == ?\r && data[1] == ?\n rest = data[2..-1] else header, rest = data.split(/\r?\n\r?\n/, 2) end break if rest end input.pushback(rest) if header mime = false fields = header.split(/\r?\n(?!\s)/) if fields.first =~ /^From / @handler.mbox_from(fields.first) fields.shift end fields.each { |field| if field =~ /^From / @handler.mbox_from(field) else name, value = RMail::Header::Field.parse(field) case name.downcase when 'mime-version' if value =~ /\b1\.0\b/ mime = true end when 'content-type' # FIXME: would be nice to have a procedural equivalent # to RMail::Header#param. header = RMail::Header.new header['content-type'] = value boundary = header.param('content-type', 'boundary') end @handler.header_field(field, name, value) end } unless mime or depth > 0 boundary = nil end end return boundary end def parse_multipart_body(input, depth, boundary) input = RMail::Parser::MultipartReader.new(input, boundary) input.chunk_size = @chunk_size if @chunk_size @handler.multipart_body_begin # Reach each part, adding it to this entity as appropriate. delimiters = [] while input.next_part if input.preamble? while chunk = input.read @handler.preamble_chunk(chunk) end elsif input.epilogue? while chunk = input.read @handler.epilogue_chunk(chunk) end else @handler.part_begin parse_low(input, depth + 1) @handler.part_end end delimiters << (input.delimiter || "") unless input.epilogue? end @handler.multipart_body_end(delimiters, boundary) end def parse_singlepart_body(input, depth) @handler.body_begin while chunk = input.read @handler.body_chunk(chunk) end @handler.body_end end end # The RMail::Parser class creates RMail::Message objects from Ruby # IO objects or strings. # # To parse from a string: # message = RMail::Parser.read(the_string) # # To parse from an IO object: # message = File.open('my-message') { |f| # RMail::Parser.read(f) # } # # You can also parse from STDIN, etc. # message = RMail::Parser.read(STDIN) # # In all cases, the parser consumes all input. class Parser # This exception class is thrown when the parser encounters an # error. # # Note: the parser tries hard to never throw exceptions -- this # error is thrown only when the API is used incorrectly and not on # invalid input. class Error < StandardError; end # Creates a new parser. Messages of +message_class+ will be # created by the parser. By default, the parser will create # RMail::Message objects. def initialize() @chunk_size = nil end # Parse a message from the IO object +io+ and return a new # message. The +io+ object can also be a string. def parse(input) handler = RMail::Parser::Handler.new parser = RMail::StreamParser.new(input, handler) parser.chunk_size = @chunk_size if @chunk_size parser.parse return handler.message end # Change the chunk size used to read the message. This is useful # mostly for testing. attr_accessor :chunk_size # Parse a message from the IO object +io+ and return a new # message. The +io+ object can also be a string. This is just # shorthand for: # # RMail::Parser.new.parse(io) def Parser.read(input) Parser.new.parse(input) end class Handler < RMail::StreamHandler # :nodoc: def initialize @parts = [ RMail::Message.new ] @preambles = [] @epilogues = [] end def mbox_from(field) @parts.last.header.mbox_from = field end def header_field(field, name, value) @parts.last.header.add_raw(field) end def body_begin @body = nil end def body_chunk(chunk) if @body @body << chunk else @body = chunk end end def body_end @parts.last.body = @body end def multipart_body_begin @preambles.push(nil) @epilogues.push(nil) end def preamble_chunk(chunk) if @preambles.last @preambles.last << chunk else @preambles[-1] = chunk end end def epilogue_chunk(chunk) if @epilogues.last @epilogues.last << chunk else @epilogues[-1] = chunk end end def multipart_body_end(delimiters, boundary) @parts.last.preamble = @preambles.pop @parts.last.epilogue = @epilogues.pop if @parts.last.body.nil? @parts.last.body = [] end @parts.last.set_delimiters(delimiters, boundary) end def part_begin @parts << RMail::Message.new end def part_end part = @parts.pop @parts.last.add_part(part) end def message @parts.first end end end end rmail-1.1.4/THANKS0000644000004100000410000000136113701337101013561 0ustar www-datawww-data= Thanks to Python's email module for: - The idea of separating parsing and serialization from the mail classes themselves. - A lot of the MIME multipart API. = Thanks to Perl's MimeTools for: - Showing me this can all be done in the scripting language (no C extensions necessary). - Accessing MIME parts via a File like API (thereby allowing them to be stored on disk). = People: "Yoshinori K. Okuji" - Code to parse messages from strings (though since then I did it a much simpler way). - Numerous other API suggestions and improvements. Booker Bense - For prodding me to implement mbox mailbox parsing (and an initial implementation). It took me months, but I did it! rmail-1.1.4/ChangeLog0000644000004100000410000003234713701337101014430 0ustar www-datawww-data= Changes in RubyMail 1.1.4 (released 2020-07-08) - Fix 'warning: constant ::Fixnum is deprecated' since Ruby 2.5 (Martin Vidner) - Fix 'warning: instance variable @delimiters not initialized' (Martin Vidner) - Fix 'warning: shadowing outer local variable - name, value' (Martin Vidner) - Fix 'warning: optional boolean argument is obsoleted' (Martin Vidner) - Fix 'warning: character class has duplicated range' (Martin Vidner) - Fix 'warning: assigned but unused variable' (Martin Vidner) - Enable code coverage analysis (Martin Vidner) - Test Ruby 2.5 and 2.6 on Travis (Martin Vidner) - Test Ruby 2.7 (Martin Vidner) - include license text in a separate file (Dan Callaghan) - Rakefile: drop usage of deprecated rubygems feature (Antonio Terceiro) = Changes in RubyMail 1.1.3 (released 2017-07-16) - Fix tests that suddenly started failing for no apparent reason = Changes in RubyMail 1.1.2 (released 2016-01-17) - Fix crash on invalid header encodings (Github issue #7) = Changes in RubyMail 1.1.1 (released 2015-11-30) - README.md updated (and renamed from README). Thanks to Aldric Giacomoni and Zeger-Jan van de Weg - Fix crash against empty "Content-Type" header. Thanks to Matthew Bloch = Changes in RubyMail 1.1.0 (released 2015-02-10) - This version contains fixes for running on Ruby 2.0+ that were contributed by Josef Stribny, plus 7 patches that have accumulated in the Debian package of rmail over the years by Adeodato Simó, Per Andersson, and Antonio Terceiro. There are also a few fixes by the original author Matt Armstrong that never made it into a release. - This version is mostly compatible with 1.0.0, but since it wasn't explicitly tested on older RUby releases we bumping the minor version as a warning to users. - Rakefile was updated to actually run on modern Ruby versions - The tests were fixed for Ruby 2.0+ compatibility - The version number was moved to lib/rmail/version.rb = Changes in RubyMail 1.0.0 (released 2008-01-05) - This version differs *only* in the changes required for Ruby 1.9 support. It is otherwise "bug compatible" with version 0.17. Users of version 0.17 can safely upgrade to 1.0.0. You will lose the ability to run the tests if you are using Ruby 1.6 (but who is doing that these days?). - Add a Rakefile. Add a gem hosted on rubyforge.org. = Changes in RubyMail 0.17 (released 2004-04-27) - Handle parsing addresses with non-ASCII display names. We don't do anything intelligent with the non-ASCII data, but it is passed through in its original (invalid) non-encoded form. - Look for From_ lines only on the first line when parsing a message. - RMail::Header#subject= now sets the header field name to "Subject" instead of "subject" since some mail reading software is case sensitive and can't understand "subject: foo". - The RMail parser now requires only whitespace after MIME multipart boundary lines. This violates RFC2046's "NOTE TO IMPLEMENTORS" documented in testparsermultipart.rb's test_multipart_data_12, but it allows real-world messages created by Eudora to be parsed. = Changes in RubyMail 0.16 (released 2003-12-26) - Ruby 1.8.1 compatibility fixes only. = Changes in RubyMail 0.15 (released 2003-09-17) - Ruby 1.8.0 compatibility fixes only. = Changes in RubyMail 0.14 (released 2003-02-08) - Improve RDoc documentation of the library. - Add file level comments. - No longer document RMail::Parser::PushbackReader and RMail::Parser::Multipart, as they are used only internally or by those who really want to dig deep into the library. I also think they might be changing soon. - Add ability to require 'rmail' and get all of RubyMail. - Switch to the BSD license, mainly so the license of RubyMail is not confusing. = Changes in RubyMail 0.13 (released 2003-02-01) - Add convenience methods to RMail::Header that allow easy manipulation of the To, Cc, Bcc, Reply-To, Date, Subject and Message-Id fields. This includes unique Message-Id generation code and robust Date field parsing and formatting. - Add RMail::Header#set, to delete existing fields of the same name and then add a new one. - RMail::Address.parse now returns an RMail::Address::List instead of an Array (existing code still works, since RMail::Address::List inherits from Array). - Add RMail::Address#<=>, RMail::Address#hash and RMail::Address#eql? methods. This allows arrays of RMail::Address to be sorted as well as allowing RMail::Address to be placed in a hash (which in turn allows Array#uniq to work when holding RMail::Address objects). - Add an RMail::Mailbox::MBoxReader.each_message method. - Deleted the rmail/header/field.rb field and incorporated RMail::Header::Field into rmail/header.rb. - Improve uniqueness of the MIME boundary generation by including Time.now.tv_usec. - Correct broken docs for RMail::Header#match and RMail::Header#match? = Changes in RubyMail 0.12 (released 2003-01-13) - Add an install.rb script. = Changes in RubyMail 0.11 (released 2003-01-11) - parse->serialize "transparency" greatly improved. This means that when you parse a message and then serialize it back out you almost always get the exact same message back. This is true for all single part messages as well as all validly formatted multipart MIME messages (and even the most common invalidly formatted ones). The result is that RMail can now be used safely in mail filters without risking damaging cryptographic signatures in the mails. - RMail::Header#add now uses to_str instead of to_s to convert arguments to strings. This makes it behave more like standard Ruby classes. - RMail::Mailbox::MBoxReader now always makes sure the last piece of data returned for each message is the end of line terminator, even if one isn't present in the input. - RMail::Parser::PushbackReader#read now takes nil argument to mean "read all available input." Derived classes should now override the #read_chunk method instead of #read. = Changes in RubyMail 0.10 (released 2002-12-13) - Added rmail/mailbox/mboxreader.rb to the distribution. = Changes in RubyMail 0.9 (released 2002-11-30) - New RMail::Mailbox.parse_mbox method that can be used to conveniently read raw messages out of a Unix mbox mailbox. - New RMail::Mailbox::MBoxReader class in rmail/mailbox/mboxreader.rb. This class can be used to easily read messages out of a file in Unix "mbox" format. - The RMail::Parser::PushbackReader class has been documented. It has moved out of rmail/parser/multipart.rb into rmail/parser/pushbackreader.rb - Various documentation fixes. E.g. RMail::Parser.parse can take a string as well as an IO object. - The RMail::Parser::PushbackReader has a setable chunk size. This is useful mostly for testing. - Fix an uncaught exception when parsing multipart MIME messages that contain only a preamble and an epilogue but no body parts. - Fix a bug where RMail::Parser.multipart? would not return true if the multipart message actually didn't have any parts. = Changes in RubyMail 0.8 (released 2002-03-18) - The following has been removed from RubyMail and made part of the RubyFilter package: - All scripts that were in the RubyMail 0.7 bin directory. - Mail::LDA - Mail::Deliver - Mail::KeyedMailbox - Mail::MTA - Mail::AddressTagger This keeps RubyMail a small and simple mail package, and provides RubyFilter as an example of how to use RubyMail to write a mail filter. - The Mail module has been renamed to RMail. I think "Mail" should be reserved for things included in the standard distribution of Ruby. - RMail::Header#match and match? don't require the name or value arguments to be a case insensitive Regexps. Also, when the value argument is converted to a string, it is passed through Regexp::escape first. - RMail::Parser#parse can now parse from a string. - Mail::Address#comments= can now take a simpple string to set just one comment. = Changes in RubyMail 0.7 (released 2002-02-26) - A new chunked input scheme that makes parsing huge messages about 7 times faster in ruby 1.7 and 50 times faster in ruby 1.6. When parsing a huge message that has a 2 megabyte attachment, RubyMail running under ruby 1.7 is now faster than any email package for ruby, perl or python. I wrote a benchmark that reads a 2 megabyte email from a file and writes it out again, doing this 100 times. The results are: ruby 1.7.2 w/rubymail (100 times) 5.96s user 7.98s system 13.94s total ruby 1.6.6 w/rubymail (100 times) 76.91s user 8.62s system 85.53s total ruby 1.7.2 w/tmail 0.10.1 (100 times) 9.85s user 24.21s system 34.06s total ruby 1.6.6 w/tmail 0.10.0 (100 times) 201.89s user 15.75s system 217.64s total python 2.2 w/email (100 times) 76.73 user 15.16s system 91.89s total perl 5.006001 w/mimetools 5.411 (parsing on disk) (100 times) 190.11s user 25.25s system 215.36s total perl 5.006001 w/mimetools 5.411 (parsing in memory) (100 times) 962.69s user 6.77s system 969.46s total This change also paves the way for streaming large messages to disk when they start to get huge, so RAM isn't needlessly used up. - Delivery to mbox files improved. - won't sleep forever waiting for the mailbox lock (flock) - won't delivery to files that don't look like an mbox (e.g. it is not a file, not in the right format). - delivering to '/dev/null' is now a simple nop. - Use the new File#fsync method when available in all of the mail delivery functions. - Add bin/rsendmail.rb as another example of how I'm using RubyMail. - A new Mail::AddressTagger class, included for fun. Requires the hmac-sha1 class (available in the ruby-hmac package on RAA). = Changes in RubyMail 0.6 (released 2002-02-15) - Multipart MIME support. - Mail::Parser now parses arbitrarily nested multipart MIME messages. - For the sake of dealing with multipart MIME messages, add the following methods to Mail::Message: preamble, epilogue, multipart?, add_part, decode, part. - A new Mail::AddressTagger class, for tagging addresses with cryptographically verifiable extensions akin to TMDA. Requires the hmac-sha1 module from the RAA. (experimental, I don't currently use this) - A new Mail::Message#== method. - A new Mail::Serialize class that can serialize a Mail::Message. = Changes in RubyMail 0.5 (released 2002-02-02) - The rdeliver.rb script is now fully documented. - The rdeliver.rb script now evaluates the .rdeliver file in the context of a Deliver class (as opposed to simply loading it). The .rdeliver file must now define a Deliver#main method, where the simplest .rdeliver file would be: def main lda.save("INBOX") end - Add a KeyedMailbox class that can be used to implement simple mailing list style confirmations. - Add a message= method to Mail::DeliveryAgent. This lets delivery scripts change the message being delivered. - Re-wrote the RFC2822 address parser. It is now more strict about what it considers a valid address. = Changes in RubyMail 0.4 (released 2002-01-17) - The bin/rdeliver.rb script is now tested. Got rid of bin/rdeliver-wrap.rb. - Mail::DeliveryAgent::DeliveryStatus renamed to Mail::DeliveryAgent::DeliveryComplete. Mail::DeliveryAgent::DeliverFailure renamed to Mail::DeliveryAgent::DeliveryPipeFailure. - Mail::Deliver.deliver_mbox now uses File::SYNC to write the message. - Mail::Header and Mail::Message no longer understand how to parse messages. Message parsing has been moved to Mail::Parser. Mail::Parser uses the public API of Mail::Header and Mail::Message to build up the message. - The Mail::Header API has been greatly changed. It is now more like Array and Hash. - Mail::Deliver supports delivery to qmail style Maildir mailboxes. Mail::DeliveryAgent#save will now deliver to a Maildir if the folder's name ends in a slash. E.g. "/home/user/Maildir/". - Mail::DeliveryAgent no longer logs an abstract of the message being delivered. All logging is up to the users of Mail::DeliveryAgent. = Changes in RubyMail 0.3 - Add Mail::Header.length and Mail::Header.size methods. Add Mail::Header.match and Mail::Header.match? methods. - Move deliver.rb to bin/rdeliver-wrap.rb and main.rb to bin/rdeliver.rb. These are workable local delivery agent scripts (suitable for running from .forward). - New Mail::MTA module that provides constants for exit codes. - New features for Mail::DeliveryAgent. Now Mail::DeliveryAgent never calls exit, instead it throws DeliveryStatus exceptions. There is also a new Mail::DeliveryAgent.process method that allows you to use Mail::DeliveryAgent in block form. Mail:DeliveryAgent.exitcode will return the correct exit code for a given DeliveryStatus exception. = Changes in RubyMail 0.2 - HTML API documentation is now available in the doc subdirectory. - Mail::DeliveryAgent::strip_tag is now Mail::DeliveryAgent::strip_field_name. - Mail::Deliver::deliver_pipe implemented. Mail::DeliveryAgent#pipe implemented. - Mail::DeliveryAgent#pipe and Mail::DeliveryAgent#save now report success or failure with DeliverySuccess and DeliveryFailure exceptions. Mail::DeliveryAgent#reject and Mail::DeliveryAgent#defer do not yet use the exceptions (they still call exit). - Now runs clean under "ruby -w" - Now works with newer rubyunit versions. rmail-1.1.4/rmail.gemspec0000644000004100000410000001073713701337101015326 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: rmail 1.1.4 ruby lib Gem::Specification.new do |s| s.name = "rmail".freeze s.version = "1.1.4" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Matt Armstrong".freeze, "Antonio Terceiro".freeze] s.date = "2020-07-08" s.description = " RMail is a lightweight mail library containing various utility classes and\n modules that allow ruby scripts to parse, modify, and generate MIME mail\n messages.\n".freeze s.email = "terceiro@softwarelivre.org".freeze s.extra_rdoc_files = ["ChangeLog".freeze, "README.md".freeze, "THANKS".freeze, "TODO".freeze, "guide/Intro.txt".freeze, "guide/MIME.txt".freeze, "guide/TableOfContents.txt".freeze] s.files = ["ChangeLog".freeze, "NOTES".freeze, "README.md".freeze, "Rakefile".freeze, "THANKS".freeze, "TODO".freeze, "guide/Intro.txt".freeze, "guide/MIME.txt".freeze, "guide/TableOfContents.txt".freeze, "lib/rmail.rb".freeze, "lib/rmail/address.rb".freeze, "lib/rmail/header.rb".freeze, "lib/rmail/mailbox.rb".freeze, "lib/rmail/mailbox/mboxreader.rb".freeze, "lib/rmail/message.rb".freeze, "lib/rmail/parser.rb".freeze, "lib/rmail/parser/multipart.rb".freeze, "lib/rmail/parser/pushbackreader.rb".freeze, "lib/rmail/serialize.rb".freeze, "lib/rmail/utils.rb".freeze, "lib/rmail/version.rb".freeze, "test/addrgrammar.txt".freeze, "test/data/mbox.odd".freeze, "test/data/mbox.simple".freeze, "test/data/multipart/data.1".freeze, "test/data/multipart/data.10".freeze, "test/data/multipart/data.11".freeze, "test/data/multipart/data.12".freeze, "test/data/multipart/data.13".freeze, "test/data/multipart/data.14".freeze, "test/data/multipart/data.15".freeze, "test/data/multipart/data.16".freeze, "test/data/multipart/data.17".freeze, "test/data/multipart/data.2".freeze, "test/data/multipart/data.3".freeze, "test/data/multipart/data.4".freeze, "test/data/multipart/data.5".freeze, "test/data/multipart/data.6".freeze, "test/data/multipart/data.7".freeze, "test/data/multipart/data.8".freeze, "test/data/multipart/data.9".freeze, "test/data/parser.badmime1".freeze, "test/data/parser.badmime2".freeze, "test/data/parser.nested-multipart".freeze, "test/data/parser.nested-simple".freeze, "test/data/parser.nested-simple2".freeze, "test/data/parser.nested-simple3".freeze, "test/data/parser.rfc822".freeze, "test/data/parser.simple-mime".freeze, "test/data/parser/multipart.1".freeze, "test/data/parser/multipart.10".freeze, "test/data/parser/multipart.11".freeze, "test/data/parser/multipart.12".freeze, "test/data/parser/multipart.13".freeze, "test/data/parser/multipart.14".freeze, "test/data/parser/multipart.15".freeze, "test/data/parser/multipart.16".freeze, "test/data/parser/multipart.2".freeze, "test/data/parser/multipart.3".freeze, "test/data/parser/multipart.4".freeze, "test/data/parser/multipart.5".freeze, "test/data/parser/multipart.6".freeze, "test/data/parser/multipart.7".freeze, "test/data/parser/multipart.8".freeze, "test/data/parser/multipart.9".freeze, "test/data/transparency/absolute.1".freeze, "test/data/transparency/absolute.2".freeze, "test/data/transparency/absolute.3".freeze, "test/data/transparency/absolute.4".freeze, "test/data/transparency/absolute.5".freeze, "test/data/transparency/absolute.6".freeze, "test/data/transparency/message.1".freeze, "test/data/transparency/message.2".freeze, "test/data/transparency/message.3".freeze, "test/data/transparency/message.4".freeze, "test/data/transparency/message.5".freeze, "test/data/transparency/message.6".freeze, "test/runtests.rb".freeze, "test/testaddress.rb".freeze, "test/testbase.rb".freeze, "test/testheader.rb".freeze, "test/testmailbox.rb".freeze, "test/testmboxreader.rb".freeze, "test/testmessage.rb".freeze, "test/testparser.rb".freeze, "test/testparsermultipart.rb".freeze, "test/testpushbackreader.rb".freeze, "test/testserialize.rb".freeze, "test/testtestbase.rb".freeze, "test/testtranspparency.rb".freeze] s.homepage = "https://github.com/terceiro/rmail".freeze s.rdoc_options = ["--title".freeze, "RubyMail Documentation (version 1.1.4)".freeze, "--main".freeze, "README".freeze, "--exclude".freeze, "SCCS".freeze] s.required_ruby_version = Gem::Requirement.new(">= 1.8.1".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "A MIME mail parsing and generation library.".freeze end rmail-1.1.4/NOTES0000644000004100000410000000112213701337101013454 0ustar www-datawww-data-*- outline -*- * Why RubyMail can't be completely transparent RubyMail parses the MIME message into a document structure. If the MIME message is invalid, RubyMail might discard invalid portions, etc. Also, if you parse and then serialize a RMail::Message, empty lines may be deleted or inserted around a MIME boundary. For these reasons, if you require a message to be output exactly as it is put in, I suggest that you do not filter it *through* RubyMail. Instead, parse the message and decide what to do with it, and then save the original message.