Parser fix: single line MimeMessages are now parsed correctly

This commit is contained in:
Petr Bechyně 2017-04-06 15:51:29 +02:00
parent 564005da93
commit ab83642f06
4 changed files with 114 additions and 13 deletions

View File

@ -21,6 +21,7 @@ class MimeBoundaryAnalyser
} }
/** /**
* @todo: This method is not reliable at all
* @param string $mimeMessageLine * @param string $mimeMessageLine
* @return bool * @return bool
*/ */

View File

@ -31,23 +31,14 @@ class Parser
* *
* @param string $mimeMessage Mime message string * @param string $mimeMessage Mime message string
* @param string[] $headers array(string=>string) of header elements (e.g. coming from http request) * @param string[] $headers array(string=>string) of header elements (e.g. coming from http request)
* * @return MultiPart
* @return \BeSimple\SoapCommon\Mime\MultiPart
*/ */
public static function parseMimeMessage($mimeMessage, array $headers = []) public static function parseMimeMessage($mimeMessage, array $headers = [])
{ {
$multiPart = new MultiPart(); $multiPart = new MultiPart();
$mimeMessageLines = explode("\n", $mimeMessage); $mimeMessageLines = explode("\n", $mimeMessage);
$mimeMessageLineCount = count($mimeMessageLines); $mimeMessageLineCount = count($mimeMessageLines);
if ($mimeMessageLineCount <= 1) {
throw new Exception(
sprintf(
'Cannot process message of %d characters: got unexpectable low number of lines: %s',
mb_strlen($mimeMessage),
(string)$mimeMessageLineCount
)
);
}
// add given headers, e.g. coming from HTTP headers // add given headers, e.g. coming from HTTP headers
if (count($headers) > 0) { if (count($headers) > 0) {
self::setMultiPartHeaders($multiPart, $headers); self::setMultiPartHeaders($multiPart, $headers);
@ -56,6 +47,15 @@ class Parser
$hasHttpRequestHeaders = ParsedPartsGetter::HAS_NO_HTTP_REQUEST_HEADERS; $hasHttpRequestHeaders = ParsedPartsGetter::HAS_NO_HTTP_REQUEST_HEADERS;
} }
if (MimeBoundaryAnalyser::hasMessageBoundary($mimeMessageLines) === true) { if (MimeBoundaryAnalyser::hasMessageBoundary($mimeMessageLines) === true) {
if ($mimeMessageLineCount <= 1) {
throw new Exception(
sprintf(
'Cannot parse MultiPart message of %d characters: got unexpectable low number of lines: %s',
mb_strlen($mimeMessage),
(string)$mimeMessageLineCount
)
);
}
$parsedPartList = ParsedPartsGetter::getPartsFromMimeMessageLines( $parsedPartList = ParsedPartsGetter::getPartsFromMimeMessageLines(
$multiPart, $multiPart,
$mimeMessageLines, $mimeMessageLines,

View File

@ -0,0 +1,99 @@
<?php
namespace BeSimple\SoapCommon\Mime;
use Exception;
use PHPUnit_Framework_TestCase;
class ParserTest extends PHPUnit_Framework_TestCase
{
const TEST_CASE_SHOULD_FAIL = true;
const TEST_CASE_SHOULD_NOT_FAIL = false;
/**
* @dataProvider provideMimeMessages
* @param string $mimeMessage
* @param string[] $headers
* @param bool $testCaseShouldFail
* @param string|null $failedTestCaseFailMessage
*/
public function testParseMimeMessage(
$mimeMessage,
array $headers,
$testCaseShouldFail,
$failedTestCaseFailMessage = null
) {
if ($testCaseShouldFail === true) {
$this->setExpectedException(Exception::class, $failedTestCaseFailMessage);
}
$mimeMessage = Parser::parseMimeMessage($mimeMessage, $headers);
if ($testCaseShouldFail === false) {
self::assertInstanceOf(MultiPart::class, $mimeMessage);
self::assertInstanceOf(Part::class, $mimeMessage->getMainPart());
}
}
public function provideMimeMessages()
{
return [
'ParseRequest' => [
$this->getMimeMessageFromFile(__DIR__.'/../../../Fixtures/Message/Request/dummyServiceMethod.message.request'),
[],
self::TEST_CASE_SHOULD_NOT_FAIL
],
'ParseRequestOneLiner' => [
$this->getMimeMessageFromFile(__DIR__.'/../../../Fixtures/Message/Request/dummyServiceMethod.oneliner.message.request'),
[],
self::TEST_CASE_SHOULD_NOT_FAIL
],
'ParseSwaResponseWith2FilesAnd1BinaryFile' => [
$this->getMimeMessageFromFile(__DIR__.'/../../../Fixtures/Message/Response/dummyServiceMethodWithOutgoingLargeSwa.response.mimepart.message'),
[
'Content-Type' => 'multipart/related;'.
' type="application/soap+xml"; charset=utf-8;'.
' boundary=Part_13_58e3bc35f3743.58e3bc35f376f;'.
' start="<part-424dbe68-e2da-450f-9a82-cc3e82742503@response.info>"'
],
self::TEST_CASE_SHOULD_NOT_FAIL
],
'ParseSwaResponseWith2FilesAnd1BinaryFileShouldFailWithNoHeaders' => [
$this->getMimeMessageFromFile(__DIR__.'/../../../Fixtures/Message/Response/dummyServiceMethodWithOutgoingLargeSwa.response.mimepart.message'),
[],
self::TEST_CASE_SHOULD_FAIL,
'Unable to get Content-Type boundary'
],
'ParseSwaResponseWith2FilesAnd1BinaryFileShouldFailWithWrongHeaders' => [
$this->getMimeMessageFromFile(__DIR__.'/../../../Fixtures/Message/Response/dummyServiceMethodWithOutgoingLargeSwa.response.mimepart.message'),
[
'Content-Type' => 'multipart/related; type="application/soap+xml"; charset=utf-8; boundary=DOES_NOT_EXIST; start="<non-existing>"'
],
self::TEST_CASE_SHOULD_FAIL,
'cannot parse headers before hitting the first boundary'
],
'ParseSwaRequestWith2Files' => [
$this->getMimeMessageFromFile(__DIR__ . '/../../../Fixtures/Message/Request/dummyServiceMethodWithAttachments.request.mimepart.message'),
[
'Content-Type' => 'multipart/related; type="application/soap+xml"; charset=utf-8; boundary=----=_Part_6_2094841787.1482231370463; start="<rootpart@soapui.org>"'
],
self::TEST_CASE_SHOULD_NOT_FAIL
],
'ParseSwaRequestWith2FilesShouldFailWithNoHeaders' => [
$this->getMimeMessageFromFile(__DIR__ . '/../../../Fixtures/Message/Request/dummyServiceMethodWithAttachments.request.mimepart.message'),
[],
self::TEST_CASE_SHOULD_FAIL,
'Unable to get Content-Type boundary'
],
];
}
private function getMimeMessageFromFile($filePath)
{
if (file_exists($filePath) === false) {
self::fail('Please, update tests data provider - file not found: '.$filePath);
}
return file_get_contents($filePath);
}
}

View File

@ -0,0 +1 @@
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://schema.testcase"><soapenv:Header><sch:SoapHeader><user>admin</user></sch:SoapHeader></soapenv:Header><soapenv:Body><sch:dummyServiceMethod><request><dummyAttribute>1</dummyAttribute></request></sch:dummyServiceMethod></soapenv:Body></soapenv:Envelope>