Fix regression in decoding mail parts FETCHed from IMAP (#9096)

This commit is contained in:
Aleksander Machniak
2023-08-13 10:51:47 +02:00
parent 93a599b105
commit 2ddbc019ae
6 changed files with 535 additions and 56 deletions

View File

@@ -11,7 +11,7 @@
- Fix regression where LDAP addressbook 'filter' option was ignored (#9061)
- Fix wrong order of a multi-folder search result when sorting by size (#9065)
- Fix so install/update scripts do not require PEAR (#9037)
- Fix handling of mail parts that are encoded with x-uuencode (#9096)
- Fix regression where some mail parts could have been decoded incorrectly, or not at all (#9096)
## Release 1.6.2

View File

@@ -2870,7 +2870,7 @@ class rcube_imap_generic
$mode = 3;
break;
default:
$mode = 0;
$mode = $formatted ? 4 : 0;
}
// Use BINARY extension when possible (and safe)
@@ -2932,15 +2932,7 @@ class rcube_imap_generic
}
if ($result !== false) {
if ($mode == 1) {
$result = base64_decode($result);
}
else if ($mode == 2) {
$result = quoted_printable_decode($result);
}
else if ($mode == 3) {
$result = convert_uudecode($result);
}
$result = $this->decodeContent($result, $mode, true);
}
}
// response with string literal
@@ -2954,67 +2946,37 @@ class rcube_imap_generic
if (!$bytes) {
$result = '';
}
// An optimal path for a case when we need the body as-is in a string
else if (!$mode && !$file && !$print) {
$result = $this->readBytes($bytes);
}
else while ($bytes > 0) {
$line = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes);
$chunk = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes);
if ($line === '') {
if ($chunk === '') {
break;
}
$len = strlen($line);
$len = strlen($chunk);
if ($len > $bytes) {
$line = substr($line, 0, $bytes);
$len = strlen($line);
$chunk = substr($chunk, 0, $bytes);
$len = strlen($chunk);
}
$bytes -= $len;
// BASE64
if ($mode == 1) {
$line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line);
// create chunks with proper length for base64 decoding
$line = $prev.$line;
$length = strlen($line);
if ($length % 4) {
$length = floor($length / 4) * 4;
$prev = substr($line, $length);
$line = substr($line, 0, $length);
}
else {
$prev = '';
}
$line = base64_decode($line);
}
// QUOTED-PRINTABLE
else if ($mode == 2) {
$line = rtrim($line, "\t\r\0\x0B");
$line = quoted_printable_decode($line);
}
// UUENCODE
else if ($mode == 3) {
$line = preg_replace(
['/\r?\n/', '/\nend$/', '/^begin\s+[0-7]{3}\s+[^\n]+\n/'],
["\n", '', ''],
$line
);
$line = convert_uudecode($line);
}
// default
else if ($formatted) {
$line = rtrim($line, "\t\r\n\0\x0B") . "\n";
}
$chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev);
if ($file) {
if (fwrite($file, $line) === false) {
if (fwrite($file, $chunk) === false) {
break;
}
}
else if ($print) {
echo $line;
echo $chunk;
}
else {
$result .= $line;
$result .= $chunk;
}
}
}
@@ -3036,6 +2998,105 @@ class rcube_imap_generic
return false;
}
/**
* Decodes a chunk of a message part content from a FETCH response.
*
* @param string $chunk Content
* @param int $mode Encoding mode
* @param bool $is_last Whether it is a last chunk of data
* @param string $prev Extra content from the previous chunk
*
* @return string Encoded string
*/
protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '')
{
// BASE64
if ($mode == 1) {
$chunk = $prev . preg_replace('|[^a-zA-Z0-9+=/]|', '', $chunk);
// create chunks with proper length for base64 decoding
$length = strlen($chunk);
if ($length % 4) {
$length = floor($length / 4) * 4;
$prev = substr($chunk, $length);
$chunk = substr($chunk, 0, $length);
}
else {
$prev = '';
}
return base64_decode($chunk);
}
// QUOTED-PRINTABLE
if ($mode == 2) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
$chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
return quoted_printable_decode($chunk);
}
// X-UUENCODE
if ($mode == 3) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
$chunk = preg_replace(
['/\r?\n/', '/(^|\n)end$/', '/^begin\s+[0-7]{3}\s+[^\n]+\n/'],
["\n", '', ''],
$chunk
);
if (!strlen($chunk)) {
return '';
}
return convert_uudecode($chunk);
}
// Plain text formatted
// TODO: Formatting should be handled outside of this class
if ($mode == 4) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
if ($is_last) {
$chunk = rtrim($chunk, "\t\r\n\0\x0B");
}
return preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
}
return $chunk;
}
/**
* A helper for a new-line aware parsing. See self::decodeContent().
*/
private static function decodeContentChunk(&$chunk, &$prev, $is_last)
{
$chunk = $prev . $chunk;
$prev = '';
if (!$is_last) {
if (($pos = strrpos($chunk, "\n")) !== false) {
$prev = substr($chunk, $pos + 1);
$chunk = substr($chunk, 0, $pos + 1);
} else {
$prev = $chunk;
return false;
}
}
return true;
}
/**
* Handler for IMAP APPEND command
*

View File

@@ -82,7 +82,7 @@ class Framework_ImapGeneric extends PHPUnit\Framework\TestCase
}
/**
* Test for uncompressMessageSet
* Test for uncompressMessageSet()
*/
function test_uncompressMessageSet()
{
@@ -100,7 +100,7 @@ class Framework_ImapGeneric extends PHPUnit\Framework\TestCase
}
/**
* Test for tokenizeResponse
* Test for tokenizeResponse()
*/
function test_tokenizeResponse()
{
@@ -121,4 +121,99 @@ class Framework_ImapGeneric extends PHPUnit\Framework\TestCase
$result = rcube_imap_generic::tokenizeResponse($response, 1);
$this->assertSame(['item1', 'item2'], $result);
}
/**
* Test for decodeContent() with no encoding
*/
function test_decode_content_plain()
{
$content = "test uuencode encoded content\ntest uuencode encoded content";
$this->runDecodeContent($content, $content, 0);
}
/**
* Test for decodeContent() with base64 encoding
*/
function test_decode_content_base64()
{
$content = "test base64 encoded content\ntest base64 encoded content";
$encoded = chunk_split(base64_encode($content), 10, "\r\n");
$this->runDecodeContent($content, $encoded, 1);
// Test some real-life example
$content = file_get_contents(TESTS_DIR . "src/test.pdf");
$encoded = file_get_contents(TESTS_DIR . "src/test.base64");
$this->runDecodeContent($content, $encoded, 1, 2000);
$this->runDecodeContent($content, $encoded, 1, 4000);
$this->runDecodeContent($content, $encoded, 1, 6000);
}
/**
* Test for decodeContent() with quoted-printable encoding
*/
function test_decode_content_qp()
{
$content = "test quoted-printable\n\n żąśźć encoded content\ntest quoted-printable żąśźć encoded content";
$encoded = Mail_mimePart::quotedPrintableEncode($content, 12);
$this->runDecodeContent($content, $encoded, 2);
}
/**
* Test for decodeContent() with x-uuencode encoding
*/
function test_decode_content_uuencode()
{
$content = "test uuencode encoded content\ntest uuencode encoded content";
$encoded = "begin 664 test.txt\r\n" . convert_uuencode($content) . "end";
$this->runDecodeContent($content, $encoded, 3);
// Test some real-life example
$content = file_get_contents(TESTS_DIR . "src/test.pdf");
$encoded = file_get_contents(TESTS_DIR . "src/test.uuencode");
$this->runDecodeContent($content, $encoded, 3, 2000);
$this->runDecodeContent($content, $encoded, 3, 4000);
}
/**
* Test for decodeContent() with no encoding, but formatted output
*/
function test_decode_content_formatted()
{
$content = "test \r\n plain text\tcontent\t\r\n test plain text content\t";
$expected = "test \n plain text\tcontent\n test plain text content";
$this->runDecodeContent($expected, $content, 4);
}
/**
* Helper to execute decodeCOntent() method in multiple variations of an input
* and assert with the expected output
*/
function runDecodeContent($expected, $encoded, $mode, $size = null)
{
$method = new ReflectionMethod('rcube_imap_generic', 'decodeContent');
$method->setAccessible(true);
// Make sure the method works with any chunk size
for ($x = 1; $x <= strlen($encoded); $x++) {
if ($size && $size != $x) {
continue;
}
$decoded = $prev = '';
$chunks = str_split($encoded, $x);
foreach ($chunks as $idx => $chunk) {
$decoded .= $method->invokeArgs(null, [$chunk, $mode, $idx == count($chunks)-1, &$prev]);
}
$this->assertSame($expected, $decoded, "Failed on chunk size of $x");
}
}
}

54
tests/src/test.base64 Normal file
View File

@@ -0,0 +1,54 @@
JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YWxvZw0KL091dGxpbmVz
IDIgMCBSDQovUGFnZXMgMyAwIFINCj4+DQplbmRvYmoNCg0KMiAwIG9iag0KPDwNCi9UeXBlIC9P
dXRsaW5lcw0KL0NvdW50IDANCj4+DQplbmRvYmoNCg0KMyAwIG9iag0KPDwNCi9UeXBlIC9QYWdl
cw0KL0NvdW50IDINCi9LaWRzIFsgNCAwIFIgNiAwIFIgXSANCj4+DQplbmRvYmoNCg0KNCAwIG9i
ag0KPDwNCi9UeXBlIC9QYWdlDQovUGFyZW50IDMgMCBSDQovUmVzb3VyY2VzIDw8DQovRm9udCA8
PA0KL0YxIDkgMCBSIA0KPj4NCi9Qcm9jU2V0IDggMCBSDQo+Pg0KL01lZGlhQm94IFswIDAgNjEy
LjAwMDAgNzkyLjAwMDBdDQovQ29udGVudHMgNSAwIFINCj4+DQplbmRvYmoNCg0KNSAwIG9iag0K
PDwgL0xlbmd0aCAxMDc0ID4+DQpzdHJlYW0NCjIgSg0KQlQNCjAgMCAwIHJnDQovRjEgMDAyNyBU
Zg0KNTcuMzc1MCA3MjIuMjgwMCBUZA0KKCBBIFNpbXBsZSBQREYgRmlsZSApIFRqDQpFVA0KQlQN
Ci9GMSAwMDEwIFRmDQo2OS4yNTAwIDY4OC42MDgwIFRkDQooIFRoaXMgaXMgYSBzbWFsbCBkZW1v
bnN0cmF0aW9uIC5wZGYgZmlsZSAtICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAg
NjY0LjcwNDAgVGQNCigganVzdCBmb3IgdXNlIGluIHRoZSBWaXJ0dWFsIE1lY2hhbmljcyB0dXRv
cmlhbHMuIE1vcmUgdGV4dC4gQW5kIG1vcmUgKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjku
MjUwMCA2NTIuNzUyMCBUZA0KKCB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBB
bmQgbW9yZSB0ZXh0LiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDYyOC44NDgw
IFRkDQooIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuIEFuZCBt
b3JlIHRleHQuIEFuZCBtb3JlICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNjE2
Ljg5NjAgVGQNCiggdGV4dC4gQW5kIG1vcmUgdGV4dC4gQm9yaW5nLCB6enp6ei4gQW5kIG1vcmUg
dGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1
MDAgNjA0Ljk0NDAgVGQNCiggbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0
LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRm
DQo2OS4yNTAwIDU5Mi45OTIwIFRkDQooIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuICkg
VGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNTY5LjA4ODAgVGQNCiggQW5kIG1vcmUg
dGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1v
cmUgKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA1NTcuMTM2MCBUZA0KKCB0ZXh0
LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBFdmVuIG1vcmUuIENvbnRpbnVlZCBvbiBw
YWdlIDIgLi4uKSBUag0KRVQNCmVuZHN0cmVhbQ0KZW5kb2JqDQoNCjYgMCBvYmoNCjw8DQovVHlw
ZSAvUGFnZQ0KL1BhcmVudCAzIDAgUg0KL1Jlc291cmNlcyA8PA0KL0ZvbnQgPDwNCi9GMSA5IDAg
UiANCj4+DQovUHJvY1NldCA4IDAgUg0KPj4NCi9NZWRpYUJveCBbMCAwIDYxMi4wMDAwIDc5Mi4w
MDAwXQ0KL0NvbnRlbnRzIDcgMCBSDQo+Pg0KZW5kb2JqDQoNCjcgMCBvYmoNCjw8IC9MZW5ndGgg
Njc2ID4+DQpzdHJlYW0NCjIgSg0KQlQNCjAgMCAwIHJnDQovRjEgMDAyNyBUZg0KNTcuMzc1MCA3
MjIuMjgwMCBUZA0KKCBTaW1wbGUgUERGIEZpbGUgMiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRm
DQo2OS4yNTAwIDY4OC42MDgwIFRkDQooIC4uLmNvbnRpbnVlZCBmcm9tIHBhZ2UgMS4gWWV0IG1v
cmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gKSBUag0KRVQNCkJUDQovRjEg
MDAxMCBUZg0KNjkuMjUwMCA2NzYuNjU2MCBUZA0KKCBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0
ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSApIFRqDQpFVA0KQlQN
Ci9GMSAwMDEwIFRmDQo2OS4yNTAwIDY2NC43MDQwIFRkDQooIHRleHQuIE9oLCBob3cgYm9yaW5n
IHR5cGluZyB0aGlzIHN0dWZmLiBCdXQgbm90IGFzIGJvcmluZyBhcyB3YXRjaGluZyApIFRqDQpF
VA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDY1Mi43NTIwIFRkDQooIHBhaW50IGRyeS4gQW5k
IG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4g
KSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA2NDAuODAwMCBUZA0KKCBCb3Jpbmcu
ICBNb3JlLCBhIGxpdHRsZSBtb3JlIHRleHQuIFRoZSBlbmQsIGFuZCBqdXN0IGFzIHdlbGwuICkg
VGoNCkVUDQplbmRzdHJlYW0NCmVuZG9iag0KDQo4IDAgb2JqDQpbL1BERiAvVGV4dF0NCmVuZG9i
ag0KDQo5IDAgb2JqDQo8PA0KL1R5cGUgL0ZvbnQNCi9TdWJ0eXBlIC9UeXBlMQ0KL05hbWUgL0Yx
DQovQmFzZUZvbnQgL0hlbHZldGljYQ0KL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcNCj4+DQpl
bmRvYmoNCg0KMTAgMCBvYmoNCjw8DQovQ3JlYXRvciAoUmF2ZSBcKGh0dHA6Ly93d3cubmV2cm9u
YS5jb20vcmF2ZVwpKQ0KL1Byb2R1Y2VyIChOZXZyb25hIERlc2lnbnMpDQovQ3JlYXRpb25EYXRl
IChEOjIwMDYwMzAxMDcyODI2KQ0KPj4NCmVuZG9iag0KDQp4cmVmDQowIDExDQowMDAwMDAwMDAw
IDY1NTM1IGYNCjAwMDAwMDAwMTkgMDAwMDAgbg0KMDAwMDAwMDA5MyAwMDAwMCBuDQowMDAwMDAw
MTQ3IDAwMDAwIG4NCjAwMDAwMDAyMjIgMDAwMDAgbg0KMDAwMDAwMDM5MCAwMDAwMCBuDQowMDAw
MDAxNTIyIDAwMDAwIG4NCjAwMDAwMDE2OTAgMDAwMDAgbg0KMDAwMDAwMjQyMyAwMDAwMCBuDQow
MDAwMDAyNDU2IDAwMDAwIG4NCjAwMDAwMDI1NzQgMDAwMDAgbg0KDQp0cmFpbGVyDQo8PA0KL1Np
emUgMTENCi9Sb290IDEgMCBSDQovSW5mbyAxMCAwIFINCj4+DQoNCnN0YXJ0eHJlZg0KMjcxNA0K
JSVFT0YNCg==

198
tests/src/test.pdf Normal file
View File

@@ -0,0 +1,198 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R
>>
endobj
2 0 obj
<<
/Type /Outlines
/Count 0
>>
endobj
3 0 obj
<<
/Type /Pages
/Count 2
/Kids [ 4 0 R 6 0 R ]
>>
endobj
4 0 obj
<<
/Type /Page
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
>>
/ProcSet 8 0 R
>>
/MediaBox [0 0 612.0000 792.0000]
/Contents 5 0 R
>>
endobj
5 0 obj
<< /Length 1074 >>
stream
2 J
BT
0 0 0 rg
/F1 0027 Tf
57.3750 722.2800 Td
( A Simple PDF File ) Tj
ET
BT
/F1 0010 Tf
69.2500 688.6080 Td
( This is a small demonstration .pdf file - ) Tj
ET
BT
/F1 0010 Tf
69.2500 664.7040 Td
( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 652.7520 Td
( text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 628.8480 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 616.8960 Td
( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj
ET
BT
/F1 0010 Tf
69.2500 604.9440 Td
( more text. And more text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 592.9920 Td
( And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 569.0880 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 557.1360 Td
( text. And more text. And more text. Even more. Continued on page 2 ...) Tj
ET
endstream
endobj
6 0 obj
<<
/Type /Page
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
>>
/ProcSet 8 0 R
>>
/MediaBox [0 0 612.0000 792.0000]
/Contents 7 0 R
>>
endobj
7 0 obj
<< /Length 676 >>
stream
2 J
BT
0 0 0 rg
/F1 0027 Tf
57.3750 722.2800 Td
( Simple PDF File 2 ) Tj
ET
BT
/F1 0010 Tf
69.2500 688.6080 Td
( ...continued from page 1. Yet more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 676.6560 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 664.7040 Td
( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj
ET
BT
/F1 0010 Tf
69.2500 652.7520 Td
( paint dry. And more text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 640.8000 Td
( Boring. More, a little more text. The end, and just as well. ) Tj
ET
endstream
endobj
8 0 obj
[/PDF /Text]
endobj
9 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
10 0 obj
<<
/Creator (Rave \(http://www.nevrona.com/rave\))
/Producer (Nevrona Designs)
/CreationDate (D:20060301072826)
>>
endobj
xref
0 11
0000000000 65535 f
0000000019 00000 n
0000000093 00000 n
0000000147 00000 n
0000000222 00000 n
0000000390 00000 n
0000001522 00000 n
0000001690 00000 n
0000002423 00000 n
0000002456 00000 n
0000002574 00000 n
trailer
<<
/Size 11
/Root 1 0 R
/Info 10 0 R
>>
startxref
2714
%%EOF

71
tests/src/test.uuencode Normal file
View File

@@ -0,0 +1,71 @@
begin 664 test.pdf
M)5!$1BTQ+C,-"B7BX\_3#0H-"C$@,"!O8FH-"CP\#0HO5'EP92`O0V%T86QO
M9PT*+T]U=&QI;F5S(#(@,"!2#0HO4&%G97,@,R`P(%(-"CX^#0IE;F1O8FH-
M"@T*,B`P(&]B:@T*/#P-"B]4>7!E("]/=71L:6YE<PT*+T-O=6YT(#`-"CX^
M#0IE;F1O8FH-"@T*,R`P(&]B:@T*/#P-"B]4>7!E("]086=E<PT*+T-O=6YT
M(#(-"B]+:61S(%L@-"`P(%(@-B`P(%(@72`-"CX^#0IE;F1O8FH-"@T*-"`P
M(&]B:@T*/#P-"B]4>7!E("]086=E#0HO4&%R96YT(#,@,"!2#0HO4F5S;W5R
M8V5S(#P\#0HO1F]N="`\/`T*+T8Q(#D@,"!2(`T*/CX-"B]0<F]C4V5T(#@@
M,"!2#0H^/@T*+TUE9&EA0F]X(%LP(#`@-C$R+C`P,#`@-SDR+C`P,#!=#0HO
M0V]N=&5N=',@-2`P(%(-"CX^#0IE;F1O8FH-"@T*-2`P(&]B:@T*/#P@+TQE
M;F=T:"`Q,#<T(#X^#0IS=')E86T-"C(@2@T*0E0-"C`@,"`P(')G#0HO1C$@
M,#`R-R!49@T*-3<N,S<U,"`W,C(N,C@P,"!49`T**"!!(%-I;7!L92!01$8@
M1FEL92`I(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR-3`P(#8X."XV
M,#@P(%1D#0HH(%1H:7,@:7,@82!S;6%L;"!D96UO;G-T<F%T:6]N("YP9&8@
M9FEL92`M("D@5&H-"D54#0I"5`T*+T8Q(#`P,3`@5&8-"C8Y+C(U,#`@-C8T
M+C<P-#`@5&0-"B@@:G5S="!F;W(@=7-E(&EN('1H92!6:7)T=6%L($UE8VAA
M;FEC<R!T=71O<FEA;',N($UO<F4@=&5X="X@06YD(&UO<F4@*2!4:@T*150-
M"D)4#0HO1C$@,#`Q,"!49@T*-CDN,C4P,"`V-3(N-S4R,"!49`T**"!T97AT
M+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T97AT
M+B`I(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR-3`P(#8R."XX-#@P
M(%1D#0HH($%N9"!M;W)E('1E>'0N($%N9"!M;W)E('1E>'0N($%N9"!M;W)E
M('1E>'0N($%N9"!M;W)E('1E>'0N($%N9"!M;W)E("D@5&H-"D54#0I"5`T*
M+T8Q(#`P,3`@5&8-"C8Y+C(U,#`@-C$V+C@Y-C`@5&0-"B@@=&5X="X@06YD
M(&UO<F4@=&5X="X@0F]R:6YG+"!Z>GIZ>BX@06YD(&UO<F4@=&5X="X@06YD
M(&UO<F4@=&5X="X@06YD("D@5&H-"D54#0I"5`T*+T8Q(#`P,3`@5&8-"C8Y
M+C(U,#`@-C`T+CDT-#`@5&0-"B@@;6]R92!T97AT+B!!;F0@;6]R92!T97AT
M+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T97AT
M+B`I(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR-3`P(#4Y,BXY.3(P
M(%1D#0HH($%N9"!M;W)E('1E>'0N($%N9"!M;W)E('1E>'0N("D@5&H-"D54
M#0I"5`T*+T8Q(#`P,3`@5&8-"C8Y+C(U,#`@-38Y+C`X.#`@5&0-"B@@06YD
M(&UO<F4@=&5X="X@06YD(&UO<F4@=&5X="X@06YD(&UO<F4@=&5X="X@06YD
M(&UO<F4@=&5X="X@06YD(&UO<F4@*2!4:@T*150-"D)4#0HO1C$@,#`Q,"!4
M9@T*-CDN,C4P,"`U-3<N,3,V,"!49`T**"!T97AT+B!!;F0@;6]R92!T97AT
M+B!!;F0@;6]R92!T97AT+B!%=F5N(&UO<F4N($-O;G1I;G5E9"!O;B!P86=E
M(#(@+BXN*2!4:@T*150-"F5N9'-T<F5A;0T*96YD;V)J#0H-"C8@,"!O8FH-
M"CP\#0HO5'EP92`O4&%G90T*+U!A<F5N="`S(#`@4@T*+U)E<V]U<F-E<R`\
M/`T*+T9O;G0@/#P-"B]&,2`Y(#`@4B`-"CX^#0HO4')O8U-E="`X(#`@4@T*
M/CX-"B]-961I84)O>"!;,"`P(#8Q,BXP,#`P(#<Y,BXP,#`P70T*+T-O;G1E
M;G1S(#<@,"!2#0H^/@T*96YD;V)J#0H-"C<@,"!O8FH-"CP\("],96YG=&@@
M-C<V(#X^#0IS=')E86T-"C(@2@T*0E0-"C`@,"`P(')G#0HO1C$@,#`R-R!4
M9@T*-3<N,S<U,"`W,C(N,C@P,"!49`T**"!3:6UP;&4@4$1&($9I;&4@,B`I
M(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR-3`P(#8X."XV,#@P(%1D
M#0HH("XN+F-O;G1I;G5E9"!F<F]M('!A9V4@,2X@665T(&UO<F4@=&5X="X@
M06YD(&UO<F4@=&5X="X@06YD(&UO<F4@=&5X="X@*2!4:@T*150-"D)4#0HO
M1C$@,#`Q,"!49@T*-CDN,C4P,"`V-S8N-C4V,"!49`T**"!!;F0@;6]R92!T
M97AT+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T97AT+B!!;F0@;6]R92!T
M97AT+B!!;F0@;6]R92`I(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR
M-3`P(#8V-"XW,#0P(%1D#0HH('1E>'0N($]H+"!H;W<@8F]R:6YG('1Y<&EN
M9R!T:&ES('-T=69F+B!"=70@;F]T(&%S(&)O<FEN9R!A<R!W871C:&EN9R`I
M(%1J#0I%5`T*0E0-"B]&,2`P,#$P(%1F#0HV.2XR-3`P(#8U,BXW-3(P(%1D
M#0HH('!A:6YT(&1R>2X@06YD(&UO<F4@=&5X="X@06YD(&UO<F4@=&5X="X@
M06YD(&UO<F4@=&5X="X@06YD(&UO<F4@=&5X="X@*2!4:@T*150-"D)4#0HO
M1C$@,#`Q,"!49@T*-CDN,C4P,"`V-#`N.#`P,"!49`T**"!";W)I;F<N("!-
M;W)E+"!A(&QI='1L92!M;W)E('1E>'0N(%1H92!E;F0L(&%N9"!J=7-T(&%S
M('=E;&PN("D@5&H-"D54#0IE;F1S=')E86T-"F5N9&]B:@T*#0HX(#`@;V)J
M#0I;+U!$1B`O5&5X=%T-"F5N9&]B:@T*#0HY(#`@;V)J#0H\/`T*+U1Y<&4@
M+T9O;G0-"B]3=6)T>7!E("]4>7!E,0T*+TYA;64@+T8Q#0HO0F%S949O;G0@
M+TAE;'9E=&EC80T*+T5N8V]D:6YG("]7:6Y!;G-I16YC;V1I;F<-"CX^#0IE
M;F1O8FH-"@T*,3`@,"!O8FH-"CP\#0HO0W)E871O<B`H4F%V92!<*&AT='`Z
M+R]W=W<N;F5V<F]N82YC;VTO<F%V95PI*0T*+U!R;V1U8V5R("A.979R;VYA
M($1E<VEG;G,I#0HO0W)E871I;VY$871E("A$.C(P,#8P,S`Q,#<R.#(V*0T*
M/CX-"F5N9&]B:@T*#0IX<F5F#0HP(#$Q#0HP,#`P,#`P,#`P(#8U-3,U(&8-
M"C`P,#`P,#`P,3D@,#`P,#`@;@T*,#`P,#`P,#`Y,R`P,#`P,"!N#0HP,#`P
M,#`P,30W(#`P,#`P(&X-"C`P,#`P,#`R,C(@,#`P,#`@;@T*,#`P,#`P,#,Y
M,"`P,#`P,"!N#0HP,#`P,#`Q-3(R(#`P,#`P(&X-"C`P,#`P,#$V.3`@,#`P
M,#`@;@T*,#`P,#`P,C0R,R`P,#`P,"!N#0HP,#`P,#`R-#4V(#`P,#`P(&X-
M"C`P,#`P,#(U-S0@,#`P,#`@;@T*#0IT<F%I;&5R#0H\/`T*+U-I>F4@,3$-
M"B]2;V]T(#$@,"!2#0HO26YF;R`Q,"`P(%(-"CX^#0H-"G-T87)T>')E9@T*
-,C<Q-`T*)25%3T8-"@``
`
end