diff options
author | Louis Chemineau <louis@chmn.me> | 2021-10-15 11:57:39 +0200 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2021-10-16 09:42:07 +0200 |
commit | def983dc7ea11b9f8e449d56019f934ce89d9490 (patch) | |
tree | 4b99dfe4c8b29e5d234d27a1b15867f45650619b /apps/dav/tests | |
parent | dd938dadefcbfa09fece30efcdaf09538f01d9e3 (diff) | |
download | nextcloud-server-def983dc7ea11b9f8e449d56019f934ce89d9490.tar.gz nextcloud-server-def983dc7ea11b9f8e449d56019f934ce89d9490.zip |
Clean BulkUpload plugin
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps/dav/tests')
-rwxr-xr-x | apps/dav/tests/benchmarks/benchmark.sh (renamed from apps/dav/tests/temporary/benchmark.sh) | 10 | ||||
-rwxr-xr-x | apps/dav/tests/benchmarks/bulk_upload.sh (renamed from apps/dav/tests/temporary/bundle_upload.sh) | 22 | ||||
-rwxr-xr-x | apps/dav/tests/benchmarks/single_upload.sh (renamed from apps/dav/tests/temporary/single_upload.sh) | 13 | ||||
-rw-r--r-- | apps/dav/tests/unit/CapabilitiesTest.php | 1 | ||||
-rw-r--r-- | apps/dav/tests/unit/Files/BundlePluginTest.php | 711 | ||||
-rw-r--r-- | apps/dav/tests/unit/Files/BundledFileTest.php | 220 | ||||
-rw-r--r-- | apps/dav/tests/unit/Files/MultipartContentsParserTest.php | 416 | ||||
-rw-r--r-- | apps/dav/tests/unit/Files/MultipartRequestParserTest.php | 281 |
8 files changed, 313 insertions, 1361 deletions
diff --git a/apps/dav/tests/temporary/benchmark.sh b/apps/dav/tests/benchmarks/benchmark.sh index 4a2f283e320..27d7c4ecbc7 100755 --- a/apps/dav/tests/temporary/benchmark.sh +++ b/apps/dav/tests/benchmarks/benchmark.sh @@ -2,6 +2,8 @@ set -eu +# benchmark.sh + export KB=1000 export MB=$((KB*1000)) @@ -32,10 +34,10 @@ do echo "- Upload of $nb tiny file of ${size}B" echo " - Bundled" start=$(date +%s) - echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./bundle_upload.sh "$nb" "$size" + echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./bulk_upload.sh "$nb" "$size" end=$(date +%s) - bundle_exec_time=$((end-start)) - echo "${bundle_exec_time}s" + bulk_exec_time=$((end-start)) + echo "${bulk_exec_time}s" echo " - Single" start=$(date +%s) @@ -44,7 +46,7 @@ do single_exec_time=$((end-start)) echo "${single_exec_time}s" - md_output+="| $nb | $size | $bundle_exec_time | $single_exec_time |\n" + md_output+="| $nb | $size | $bulk_exec_time | $single_exec_time |\n" done echo -en "$md_output"
\ No newline at end of file diff --git a/apps/dav/tests/temporary/bundle_upload.sh b/apps/dav/tests/benchmarks/bulk_upload.sh index 9d2b9c6f200..862ddfe461f 100755 --- a/apps/dav/tests/temporary/bundle_upload.sh +++ b/apps/dav/tests/benchmarks/bulk_upload.sh @@ -2,6 +2,8 @@ set -eu +# bulk_upload.sh <nb-of-files> <size-of-files> + KB=${KB:-100} MB=${MB:-$((KB*1000))} @@ -14,10 +16,10 @@ BANDWIDTH=${BANDWIDTH:-$((100*MB/CONCURRENCY))} USER="admin" PASS="password" SERVER="nextcloud.test" -UPLOAD_PATH="/tmp/bundle_upload_request_$(openssl rand --hex 8).txt" +UPLOAD_PATH="/tmp/bulk_upload_request_$(openssl rand --hex 8).txt" BOUNDARY="boundary_$(openssl rand --hex 8)" -LOCAL_FOLDER="/tmp/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" -REMOTE_FOLDER="/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" +LOCAL_FOLDER="/tmp/bulk_upload/${BOUNDARY}_${NB}_${SIZE}" +REMOTE_FOLDER="/bulk_upload/${BOUNDARY}_${NB}_${SIZE}" mkdir --parent "$LOCAL_FOLDER" @@ -48,7 +50,13 @@ done echo -en "--$BOUNDARY--\r\n" >> "$UPLOAD_PATH" -echo "Creating folder /${BOUNDARY}_${NB}_${SIZE}" +echo "Creating folder /bulk_upload" +curl \ + -X MKCOL \ + -k \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/bulk_upload" > /dev/null + +echo "Creating folder $REMOTE_FOLDER" curl \ -X MKCOL \ -k \ @@ -56,15 +64,15 @@ curl \ echo "Uploading $NB files with total size: $(du -sh "$UPLOAD_PATH" | cut -d ' ' -f1)" echo "Local file is: $UPLOAD_PATH" -blackfire curl \ +curl \ -X POST \ -k \ --progress-bar \ --limit-rate "${BANDWIDTH}k" \ - --cookie "XDEBUG_PROFILE=MROW4A;path=/;" \ + --cookie "XDEBUG_PROFILE=true;path=/;" \ -H "Content-Type: multipart/related; boundary=$BOUNDARY" \ --data-binary "@$UPLOAD_PATH" \ - "https://$USER:$PASS@$SERVER/remote.php/dav/files/bundle" + "https://$USER:$PASS@$SERVER/remote.php/dav/bulk" rm -rf "${LOCAL_FOLDER:?}" rm "$UPLOAD_PATH" diff --git a/apps/dav/tests/temporary/single_upload.sh b/apps/dav/tests/benchmarks/single_upload.sh index da8e414be60..ec57e66668d 100755 --- a/apps/dav/tests/temporary/single_upload.sh +++ b/apps/dav/tests/benchmarks/single_upload.sh @@ -2,6 +2,8 @@ set -eu +# single_upload.sh <nb-of-files> <size-of-files> + export KB=${KB:-100} export MB=${MB:-$((KB*1000))} @@ -15,15 +17,20 @@ export USER="admin" export PASS="password" export SERVER="nextcloud.test" export UPLOAD_ID="single_$(openssl rand --hex 8)" -export LOCAL_FOLDER="/tmp/bundle_upload/${UPLOAD_ID}_${NB}_${SIZE}" -export REMOTE_FOLDER="/bundle_upload/${UPLOAD_ID}_${NB}_${SIZE}" +export LOCAL_FOLDER="/tmp/single_upload/${UPLOAD_ID}_${NB}_${SIZE}" +export REMOTE_FOLDER="/single_upload/${UPLOAD_ID}_${NB}_${SIZE}" mkdir --parent "$LOCAL_FOLDER" curl \ -X MKCOL \ -k \ - --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/bulk_upload" > /dev/null + +curl \ + -X MKCOL \ + -k \ + --cookie "XDEBUG_SESSION=true;path=/;" \ "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/$REMOTE_FOLDER" upload_file() { diff --git a/apps/dav/tests/unit/CapabilitiesTest.php b/apps/dav/tests/unit/CapabilitiesTest.php index 719b62115d9..399467f6ed8 100644 --- a/apps/dav/tests/unit/CapabilitiesTest.php +++ b/apps/dav/tests/unit/CapabilitiesTest.php @@ -35,6 +35,7 @@ class CapabilitiesTest extends TestCase { $expected = [ 'dav' => [ 'chunking' => '1.0', + 'bulkupload' => '1.0', ], ]; $this->assertSame($expected, $capabilities->getCapabilities()); diff --git a/apps/dav/tests/unit/Files/BundlePluginTest.php b/apps/dav/tests/unit/Files/BundlePluginTest.php deleted file mode 100644 index 92309fe5021..00000000000 --- a/apps/dav/tests/unit/Files/BundlePluginTest.php +++ /dev/null @@ -1,711 +0,0 @@ -<?php -/** - * @author Piotr Mrowczynski <Piotr.Mrowczynski@owncloud.com> - * @author Louis Chemineau <louis@chmn.me> - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ -namespace OCA\DAV\Files; - -use OC\Files\FileInfo; -use OC\Files\Storage\Local; -use Sabre\HTTP\RequestInterface; -use Test\TestCase; -use OC\Files\View; -use OCP\Files\Storage; -use Sabre\DAV\Exception; -use OC\Files\Filesystem; -use OCP\Files\StorageNotAvailableException; - -/** - * Class BundlingPlugin - * - * @group DB - * - * @package OCA\DAV\Tests\unit\Files - */ -class BundlingPluginTest extends TestCase { - - /** - * @var string - */ - private $user; - - /** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */ - private $view; - - /** @var \OC\Files\FileInfo | \PHPUnit_Framework_MockObject_MockObject */ - private $info; - - /** - * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject - */ - private $server; - - /** - * @var FilesPlugin - */ - private $plugin; - - /** - * @var \Sabre\HTTP\RequestInterface | \PHPUnit_Framework_MockObject_MockObject - */ - private $request; - /** - * @var \Sabre\HTTP\ResponseInterface | \PHPUnit_Framework_MockObject_MockObject - */ - private $response; - - /** - * @var MultipartContentsParser | \PHPUnit_Framework_MockObject_MockObject - */ - private $contentHandler; - - const BOUNDRARY = 'test_boundrary'; - - public function setUp() { - parent::setUp(); -// $this->server = new \Sabre\DAV\Server(); - - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') - ->setConstructorArgs(array()) - ->setMethods(array('emit')) - ->getMock(); - - $this->server->tree = $this->getMockBuilder('\Sabre\DAV\Tree') - ->disableOriginalConstructor() - ->getMock(); - - // setup - $storage = $this->getMockBuilder(Local::class) - ->setMethods(["fopen","moveFromStorage","file_exists"]) - ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) - ->getMock(); - $storage->method('fopen') - ->will($this->returnCallback( - function ($path,$mode) { - $bodyStream = fopen('php://temp', 'r+'); - return $bodyStream; - } - )); - $storage->method('moveFromStorage') - ->will($this->returnValue(true)); - $storage->method('file_exists') - ->will($this->returnValue(true)); - - \OC_Hook::clear(); - - $this->user = $this->getUniqueID('user_'); - $userManager = \OC::$server->getUserManager(); - $userManager->createUser($this->user, 'pass'); - - $this->loginAsUser($this->user); - - Filesystem::mount($storage, [], $this->user . '/'); - - $this->view = $this->getMockBuilder(View::class) - ->setMethods(['resolvePath', 'touch', 'file_exists', 'getFileInfo']) - ->setConstructorArgs([]) - ->getMock(); - - $this->view->method('touch') - ->will($this->returnValue(true)); - - $this->view - ->method('resolvePath') - ->will($this->returnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - )); - - $this->view - ->method('getFileInfo') - ->will($this->returnCallback( - function ($path) { - $props = array(); - $props['checksum'] = null; - $props['etag'] = $path; - $props['fileid'] = $path; - $info = new FileInfo($path, null, null, $props, null); - return $info; - } - )); - - $this->info = $this->createMock('OC\Files\FileInfo', [], [], '', false); - - $this->request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->response = new \Sabre\HTTP\Response(); - - $this->plugin = new BundlingPlugin( - $this->view - ); - - $this->plugin->initialize($this->server); - } - - /*TESTS*/ - - /** - * This test checks that if url endpoint is wrong, plugin with return exception - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage URL endpoint has to be instance of \OCA\DAV\Files\FilesHome - */ - public function testHandleBundleNotHomeCollection() { - - $this->request - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('notFilesHome.xml')); - - $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') - ->disableOriginalConstructor() - ->getMock(); - - $this->server->tree->expects($this->once()) - ->method('getNodeForPath') - ->with('notFilesHome.xml') - ->will($this->returnValue($node)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate NULL request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type header is needed - */ - public function testHandleBundleNoHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue(null)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate empty request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type header must not be empty - */ - public function testHandleBundleEmptyHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header without boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Improper Content-type format. Boundary may be missing - */ - public function testHandleBundleNoBoundraryHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with wrong boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Boundary is not set - */ - public function testHandleBundleWrongBoundraryHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related;thisIsNotBoundrary")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with wrong boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type must be multipart/related - */ - public function testHandleBundleWrongContentTypeHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/mixed; boundary=".self::BOUNDRARY)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with alternative correct boundrary specification request header - * - * Request with user out of quota - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage beforeWriteBundle preconditions failed - */ - public function testHandleAlternativeBoundraryPlusBundleOutOfQuota() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related; boundary=\"".self::BOUNDRARY."\"")); - - $this->server - ->expects($this->once()) - ->method('emit') - ->will($this->returnValue(false)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Request without request body - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Unable to get request content - */ - public function testHandleBundleWithNullBody() { - $this->setupServerTillHeader(); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test empty request body. This will pass getPartHeader, but exception will be raised after we ready headers - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing - */ - public function testHandleBundleWithEmptyBody() { - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody(""); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Expected boundary delimiter in content part - this is not a multipart request - */ - public function testHandleBundleWithWrongBody() { - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody("WrongBody"); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing no charset - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing - */ - public function testHandleMetadataNoCharsetType(){ - $bodyContent = 'I am wrong metadata not in utf-8'; - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing wrong content-type - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type must be text/xml - */ - public function testHandleMetadataWrongContentType(){ - $bodyContent = 'I am wrong metadata content type'; - $headers['content-type'] = 'text/plain; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing wrong content-type - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata header does not contain Content-Length. Unable to parse whole bundle request - */ - public function testHandleMetadataNoContentLength(){ - $bodyContent = 'I am wrong metadata content type'; - //$headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - //."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Try to parse body which is not xml - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request - */ - public function testHandleWrongMetadataNoXML(){ - $bodyContent = "I am not xml"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Try to parse body which has xml d:multipart element which - * has not been declared <d:multipart xmlns:d='DAV:'> - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata does not contain d:multipart children elements - */ - public function testHandleWrongMetadataWrongXMLdElement(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?><d:multipart></d:multipart>"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * This test checks that exception is raised for - * parsed XML which contains empty(without d:part elements) d:multipart section in metadata XML - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata does not contain d:multipart/d:part/d:prop children elements - */ - public function testHandleEmptyMultipartMetadataSection(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?><d:multipart xmlns:d='DAV:'></d:multipart>"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Metadata contains part properties not containing obligatory field will raise exception - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Undefined index: oc-id - */ - public function testHandleWrongMetadataNoPartID(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?> - <d:multipart xmlns:d='DAV:'> - <d:part> - <d:prop> - </d:prop> - </d:part> - </d:multipart>"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * In the request, insert two files with the same Content-ID - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage One or more files have the same Content-ID 1. Unable to parse whole bundle request - */ - public function testHandleWrongMetadataMultipleIDs(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?> - <d:multipart xmlns:d='DAV:'> - <d:part> - <d:prop> - <d:oc-path>/test/zombie1.jpg</d:oc-path>\n - <d:oc-mtime>1476393386</d:oc-mtime>\n - <d:oc-id>1</d:oc-id>\n - <d:oc-total-length>6</d:oc-total-length>\n - </d:prop> - </d:part> - <d:part> - <d:prop> - <d:oc-path>/test/zombie2.jpg</d:oc-path>\n - <d:oc-mtime>1476393386</d:oc-mtime>\n - <d:oc-id>1</d:oc-id>\n - <d:oc-total-length>6</d:oc-total-length>\n - </d:prop> - </d:part> - </d:multipart>"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Specify metadata part without corresponding binary content - * - */ - public function testHandleWithoutBinaryContent(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?> - <d:multipart xmlns:d='DAV:'> - <d:part> - <d:prop> - <d:oc-path>/test/zombie1.jpg</d:oc-path>\n - <d:oc-mtime>1476393386</d:oc-mtime>\n - <d:oc-id>1</d:oc-id>\n - <d:oc-total-length>6</d:oc-total-length>\n - </d:prop> - </d:part> - </d:multipart>"; - - $this->setupServerTillMetadata($bodyContent); - $this->plugin->handleBundle($this->request, $this->response); - $return = $this->response->getBody(); - $this->assertTrue(false != $return); - $xml = simplexml_load_string($return); - $this->assertTrue(false != $xml); - $xml->registerXPathNamespace('d','urn:DAV'); - $xml->registerXPathNamespace('s','http://sabredav.org/ns'); - - $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(1, count($fileMetadataObjectXML)); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(1, count($fileMetadataObjectXML)); - $this->assertEquals("File parsing error", (string) $fileMetadataObjectXML[0]); - } - - /** - * This test will simulate success and failure in putFile class. - * - */ - public function testHandlePutFile(){ - $this->setupServerTillData(); - - $this->view - ->method('file_exists') - ->will($this->onConsecutiveCalls(true, false, $this->throwException(new StorageNotAvailableException()))); - - $this->plugin->handleBundle($this->request, $this->response); - - $return = $this->response->getBody(); - $this->assertTrue(false != $return); - $xml = simplexml_load_string($return); - $this->assertTrue(false != $xml); - $xml->registerXPathNamespace('d','urn:DAV'); - $xml->registerXPathNamespace('s','http://sabredav.org/ns'); - - $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(3, count($fileMetadataObjectXML)); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); - $this->assertEquals("HTTP/1.1 200 OK", (string) $fileMetadataObjectXML[1]); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[2]); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(2, count($fileMetadataObjectXML)); - $this->assertEquals("Bundling not supported for already existing files", (string) $fileMetadataObjectXML[0]); - $this->assertEquals("StorageNotAvailableException raised", (string) $fileMetadataObjectXML[1]); - } - - /*UTILITIES*/ - - private function setupServerTillData(){ - $bodyContent = "<?xml version='1.0' encoding='UTF-8'?> - <d:multipart xmlns:d='DAV:'> - <d:part> - <d:prop> - <d:oc-path>/test/zombie1.jpg</d:oc-path>\n - <d:oc-mtime>1476393386</d:oc-mtime>\n - <d:oc-id>0</d:oc-id>\n - <d:oc-total-length>7</d:oc-total-length>\n - </d:prop> - </d:part> - <d:part> - <d:prop> - <d:oc-path>/test/zombie2.jpg</d:oc-path>\n - <d:oc-mtime>1476393386</d:oc-mtime>\n - <d:oc-id>1</d:oc-id>\n - <d:oc-total-length>7</d:oc-total-length>\n - </d:prop> - </d:part> - <d:part> - <d:prop> - <d:oc-path>zombie3.jpg</d:oc-path>\n - <d:oc-mtime>1476393232</d:oc-mtime>\n - <d:oc-id>2</d:oc-id>\n - <d:oc-total-length>7</d:oc-total-length>\n - </d:prop> - </d:part> - </d:multipart>"; - - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 0" - ."\r\n\r\n" - ."zombie1" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 1" - ."\r\n\r\n" - ."zombie2" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 2" - ."\r\n\r\n" - ."zombie3" - ."\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - } - - private function setupServerTillMetadata($bodyContent){ - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - } - - private function setupServerTillHeader(){ - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related; boundary=".self::BOUNDRARY)); - - $this->server - ->expects($this->once()) - ->method('emit') - ->will($this->returnValue(true)); - } - - private function setupServerTillFilesHome(){ - $this->request - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('files/admin')); - - $node = $this->getMockBuilder('\OCA\DAV\Files\FilesHome') - ->disableOriginalConstructor() - ->getMock(); - - $this->server->tree->expects($this->once()) - ->method('getNodeForPath') - ->with('files/admin') - ->will($this->returnValue($node)); - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - - $this->request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - } - - public function tearDown() { - $userManager = \OC::$server->getUserManager(); - $userManager->get($this->user)->delete(); - unset($_SERVER['HTTP_OC_CHUNKED']); - - parent::tearDown(); - } -} diff --git a/apps/dav/tests/unit/Files/BundledFileTest.php b/apps/dav/tests/unit/Files/BundledFileTest.php deleted file mode 100644 index e1a65459b60..00000000000 --- a/apps/dav/tests/unit/Files/BundledFileTest.php +++ /dev/null @@ -1,220 +0,0 @@ -<?php -/** - * @author Piotr Mrowczynski <Piotr.Mrowczynski@owncloud.com> - * @author Louis Chemineau <louis@chmn.me> - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\DAV\Files; - -use OCP\Lock\ILockingProvider; - -/** - * Class File - * - * @group DB - * - * @package OCA\DAV\Tests\unit\Connector\Sabre - */ -class BundledFileTest extends \Test\TestCase { - - /** - * @var string - */ - private $user; - - /* BASICS */ - - public function setUp() { - parent::setUp(); - - \OC_Hook::clear(); - - $this->user = $this->getUniqueID('user_'); - $userManager = \OC::$server->getUserManager(); - $userManager->createUser($this->user, 'pass'); - - $this->loginAsUser($this->user); - } - - /* TESTS */ - - /** - * Test basic successful bundled file PutFile - */ - public function testPutFile() { - $bodyContent = 'blabla'; - $headers['oc-total-length'] = 6; - $headers['oc-path'] = '/foo.txt'; - $headers['oc-mtime'] = '1473336321'; - $headers['response'] = null; - - //this part will have some arbitrary, correct headers - $bodyFull = "$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->doPutFIle($headers, $multipartContentsParser); - } - - /** - * Test basic successful bundled file PutFile - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage File requires oc-total-length header to be read - */ - public function testPutFileNoLength() { - $bodyContent = 'blabla'; - $headers['oc-path'] = '/foo.txt'; - $headers['oc-mtime'] = '1473336321'; - $headers['response'] = null; - - //this part will have some arbitrary, correct headers - $bodyFull = "$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->doPutFIle($headers, $multipartContentsParser); - } - - /** - * Test putting a single file - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage PUT method not supported for bundling - */ - public function testThrowIfPut() { - $fileContents = $this->getStream('test data'); - $this->doPut('/foo.txt', $fileContents); - } - - /* UTILITIES */ - - private function getMockStorage() { - $storage = $this->getMockBuilder('\OCP\Files\Storage') - ->getMock(); - $storage->expects($this->any()) - ->method('getId') - ->will($this->returnValue('home::someuser')); - return $storage; - } - - public function tearDown() { - $userManager = \OC::$server->getUserManager(); - $userManager->get($this->user)->delete(); - unset($_SERVER['HTTP_OC_CHUNKED']); - - parent::tearDown(); - } - - /** - * @param string $string - */ - private function getStream($string) { - $stream = fopen('php://temp', 'r+'); - fwrite($stream, $string); - fseek($stream, 0); - return $stream; - } - - /** - * Do basic put for single bundled file - */ - private function doPutFIle($fileMetadata, $contentHandler, $view = null, $viewRoot = null) { - $path = $fileMetadata['oc-path']; - - if(is_null($view)){ - $view = \OC\Files\Filesystem::getView(); - } - if (!is_null($viewRoot)) { - $view = new \OC\Files\View($viewRoot); - } else { - $viewRoot = '/' . $this->user . '/files'; - } - - $info = new \OC\Files\FileInfo( - $viewRoot . '/' . ltrim($path, '/'), - $this->getMockStorage(), - null, - ['permissions' => \OCP\Constants::PERMISSION_ALL], - null - ); - - $file = new BundledFile($view, $info, $contentHandler); - - // beforeMethod locks - $view->lockFile($path, ILockingProvider::LOCK_SHARED); - - $result = $file->putFile($fileMetadata); - - // afterMethod unlocks - $view->unlockFile($path, ILockingProvider::LOCK_SHARED); - - return $result; - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return $mcp; - } - - /** - * Simulate putting a file to the given path. - * - * @param string $path path to put the file into - * @param string $viewRoot root to use for the view - * - * @return null|string of the PUT operaiton which is usually the etag - */ - private function doPut($path, $fileContents, $viewRoot = null) { - $view = \OC\Files\Filesystem::getView(); - if (!is_null($viewRoot)) { - $view = new \OC\Files\View($viewRoot); - } else { - $viewRoot = '/' . $this->user . '/files'; - } - - $info = new \OC\Files\FileInfo( - $viewRoot . '/' . ltrim($path, '/'), - $this->getMockStorage(), - null, - ['permissions' => \OCP\Constants::PERMISSION_ALL], - null - ); - - $file = new BundledFile($view, $info, null); - - // beforeMethod locks - $view->lockFile($path, ILockingProvider::LOCK_SHARED); - - $result = $file->put($fileContents); - - // afterMethod unlocks - $view->unlockFile($path, ILockingProvider::LOCK_SHARED); - - return $result; - } -} diff --git a/apps/dav/tests/unit/Files/MultipartContentsParserTest.php b/apps/dav/tests/unit/Files/MultipartContentsParserTest.php deleted file mode 100644 index 6d23fe296d2..00000000000 --- a/apps/dav/tests/unit/Files/MultipartContentsParserTest.php +++ /dev/null @@ -1,416 +0,0 @@ -<?php -/** - * @author Piotr Mrowczynski <Piotr.Mrowczynski@owncloud.com> - * @author Louis Chemineau <louis@chmn.me> - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\DAV\Tests\unit\DAV; - -use Test\TestCase; - -class MultipartContentsParserTest extends TestCase { - private $boundrary; - - protected function setUp() { - parent::setUp(); - - $this->boundrary = 'boundary'; - - } - - /*TESTS*/ - - /** - * Test basic gets() functionality, that if passed string instead of resource, it should fail - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Unable to get request content - */ - public function testGetsThrowWrongContents() { - //TODO - $bodyStream = "I am not a stream, but pretend to be"; - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - - $mcp->gets(); - } - - /** - * Test function readHeaders(), so if passed empty string, it will return null - * - */ - public function testReadHeadersThrowEmptyHeader() { - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $mcp->readHeaders(''); - $this->assertEquals(null, $mcp->readHeaders('')); - } - - /** - * streamRead function with incorrect parameter - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead cannot read contents with negative length - */ - public function testStreamReadToStringThrowNegativeLength() { - $bodyContent = 'blabla'; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); - //give negative length - $multipartContentsParser->streamReadToString(-1); - } - - /** - * streamRead function with incorrect parameter - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead cannot read contents with negative length - */ - public function testStreamReadToStreamThrowNegativeLength() { - $target = fopen('php://temp', 'r+'); - $bodyContent = 'blabla'; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); - //give negative length - $multipartContentsParser->streamReadToStream($target,-1); - } - - public function testStreamReadToString() { - $length = 0; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 1000; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 8192; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 20000; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - } - - public function testStreamReadToStream() { - $length = 0; - $this->streamReadToStreamBuilder($length); - - $length = 1000; - $this->streamReadToStreamBuilder($length); - - $length = 8192; - $this->streamReadToStreamBuilder($length); - - $length = 20000; - $this->streamReadToStreamBuilder($length); - } - - private function streamReadToStreamBuilder($length) { - $target = fopen('php://temp', 'r+'); - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals(true, $multipartContentsParser->streamReadToStream($target,$length)); - rewind($target); - $this->assertEquals($bodyString, stream_get_contents($target)); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage An error appears while reading and parsing header of content part using fgets - */ - public function testGetPartThrowFailfgets() { - $bodyStream = fopen('php://temp', 'r+'); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = $this->getMockBuilder('OCA\DAV\Files\MultipartContentsParser') - ->setConstructorArgs(array($request)) - ->setMethods(array('gets')) - ->getMock(); - - $mcp->expects($this->any()) - ->method('gets') - ->will($this->onConsecutiveCalls("--boundary\r\n", "Content-ID: 0\r\n", false)); - - $mcp->getPartHeaders($this->boundrary); - } - - /** - * If one one the content parts does not contain boundrary, means that received wrong request - * - * @expectedException \Exception - * @expectedExceptionMessage Expected boundary delimiter in content part - */ - public function testGetPartThrowNoBoundraryFound() { - // Calling multipletimes getPart on parts without contents should return null,null and signal immedietaly that endDelimiter was reached - $bodyFull = "--boundary_wrong\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $multipartContentsParser->getPartHeaders($this->boundrary); - } - - /** - * Reading from request which method getBody returns false - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Unable to get request content - */ - public function testStreamReadThrowWrongBody() { - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn(false); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $mcp->getPartHeaders($this->boundrary); - } - - /** - * Reading from request which method getBody returns false - * - */ - public function testMultipartContentSeekToContentLength() { - $bodyStream = fopen('php://temp', 'r+'); - $bodyString = ''; - $length = 1000; - for ($x = 0; $x < $length; $x++) { - $bodyString .= 'k'; - } - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $this->assertEquals(true,$mcp->multipartContentSeekToContentLength($length)); - } - - /** - * Test cases with wrong or incomplete boundraries - * - */ - public function testGetPartHeadersWrongBoundaryCases() { - // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached - $bodyFull = "--boundary\r\n--boundary_wrong\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Test empty content - $bodyFull = "--boundary\r\n"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Test empty content - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody(''); - $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached - // endDelimiter should be signaled after first getPart since it will read --boundrary till it finds contents. - $bodyFull = "--boundary\r\n--boundary\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check if we can correctly parse headers and content using streamReadToString - * - */ - public function testReadHeaderBodyCorrect() { - //multipart part will have some content bodyContent and some headers - $bodyContent = 'blabla'; - $headers['content-length'] = '6'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse it - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $bodyParsed = $multipartContentsParser->streamReadToString(6); - - //check if end delimiter is not reached, since we just read 6 bytes, and stopped at \r\n - $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); - - //check that we parsed correct headers - $this->assertEquals($bodyContent, $bodyParsed); - $this->assertEquals($headers, $headersParsed); - - //parse further to check if there is new part. There is no, so headers are null and delimiter reached - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null,$headersParsed); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check parsing incorrect headers and content using streamReadToString - * - */ - public function testReadHeaderBodyIncorrect() { - - //multipart part will have some content bodyContent and some headers - $bodyContent = 'blabla'; - $headers['content-length'] = '6'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will one correct and one incorrect header - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length" - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse it and expect null, since contains incorrect headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null, $headersParsed); - $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); - - //parse further to check if next call with not read headers again - //this should return null again and get to end of delimiter - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null,$headersParsed); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check reading error in StreamReadToString - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead read 20 expeceted 60 - */ - public function testReadBodyIncorrect() { - //multipart part will have some content bodyContent and content-length header will specify to big value - //this - $bodyContent = 'blabla'; - $headers['content-length'] = '60'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals($headers, $headersParsed); - - $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); - $multipartContentsParser->streamReadToString($headersParsed['content-length']); - } - - /** - * Test will check reading error in StreamReadToString return false - * - */ - public function testReadBodyStreamIncorrect() { - //multipart part will have some content bodyContent and content-length header will specify to big value - //this - $bodyContent = 'blabla'; - $headers['content-length'] = '60'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals($headers, $headersParsed); - - $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); - $target = fopen('php://temp', 'r+'); - $bodyParsed = $multipartContentsParser->streamReadToStream($target, $headersParsed['content-length']); - $this->assertEquals(false, $bodyParsed); - } - - /*UTILITIES*/ - - private function fillMultipartContentsParserStreamWithChars($length){ - $bodyStream = fopen('php://temp', 'r+'); - $bodyString = ''; - for ($x = 0; $x < $length; $x++) { - $bodyString .= 'k'; - } - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return array($mcp, $bodyString); - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return $mcp; - } -} diff --git a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php new file mode 100644 index 00000000000..ec9e2d0a383 --- /dev/null +++ b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php @@ -0,0 +1,281 @@ +<?php +/** + * @copyright Copyright (c) 2021, Louis Chemineau <louis@chmn.me> + * + * @author Louis Chemineau <louis@chmn.me> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Tests\unit\DAV; + +use Test\TestCase; +use \OCA\DAV\BulkUpload\MultipartRequestParser; + +class MultipartRequestParserTest extends TestCase { + private function getValidBodyObject() { + return [ + [ + "headers" => [ + "Content-Length" => 7, + "X-File-MD5" => "4f2377b4d911f7ec46325fe603c3af03", + "X-File-Path" => "/coucou.txt" + ], + "content" => "Coucou\n" + ] + ]; + } + + private function getMultipartParser(array $parts, array $headers = [], string $boundary = "boundary_azertyuiop"): MultipartRequestParser { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $headers = array_merge(['Content-Type' => 'multipart/related; boundary='.$boundary], $headers); + $request->expects($this->any()) + ->method('getHeader') + ->willReturnCallback(function (string $key) use (&$headers) { + return $headers[$key]; + }); + + $body = ""; + foreach ($parts as $part) { + $body .= '--'.$boundary."\r\n"; + + foreach ($part['headers'] as $headerKey => $headerPart) { + $body .= $headerKey.": ".$headerPart."\r\n"; + } + + $body .= "\r\n"; + $body .= $part['content']."\r\n"; + } + + $body .= '--'.$boundary."--"; + + $stream = fopen('php://temp','r+'); + fwrite($stream, $body); + rewind($stream); + + $request->expects($this->any()) + ->method('getBody') + ->willReturn($stream); + + return new MultipartRequestParser($request); + } + + + /** + * Test validation of the request's body type + */ + public function testBodyTypeValidation() { + $bodyStream = "I am not a stream, but pretend to be"; + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $this->expectExceptionMessage('Body should be of type resource'); + new MultipartRequestParser($request); + } + + /** + * Test with valid request. + * - valid boundary + * - valid md5 hash + * - valid content-length + * - valid file content + * - valid file path + */ + public function testValidRequest() { + $multipartParser = $this->getMultipartParser( + $this->getValidBodyObject() + ); + + [$headers, $content] = $multipartParser->parseNextPart(); + + $this->assertSame((int)$headers["content-length"], 7, "Content-Length header should be the same as provided."); + $this->assertSame($headers["x-file-md5"], "4f2377b4d911f7ec46325fe603c3af03", "X-File-MD5 header should be the same as provided."); + $this->assertSame($headers["x-file-path"], "/coucou.txt", "X-File-Path header should be the same as provided."); + + $this->assertSame($content, "Coucou\n", "Content should be the same"); + } + + /** + * Test with invalid md5 hash. + */ + public function testInvalidMd5Hash() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["X-File-MD5"] = "f2377b4d911f7ec46325fe603c3af03"; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a null md5 hash. + */ + public function testNullMd5Hash() { + $bodyObject = $this->getValidBodyObject(); + unset($bodyObject["0"]["headers"]["X-File-MD5"]); + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('The X-File-MD5 header must not be null.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a null Content-Length. + */ + public function testNullContentLength() { + $bodyObject = $this->getValidBodyObject(); + unset($bodyObject["0"]["headers"]["Content-Length"]); + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('The Content-Length header must not be null.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a lower Content-Length. + */ + public function testLowerContentLength() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["Content-Length"] = 6; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a higher Content-Length. + */ + public function testHigherContentLength() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["Content-Length"] = 8; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with wrong boundary in body. + */ + public function testWrongBoundary() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary=boundary_poiuytreza'] + ); + + $this->expectExceptionMessage('Boundary not found where it should be.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with no boundary in request headers. + */ + public function testNoBoundaryInHeader() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Error while parsing boundary in Content-Type header.'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related'] + ); + } + + /** + * Test with no boundary in the request's headers. + */ + public function testNoBoundaryInBody() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary=boundary_azertyuiop'], + '' + ); + + $this->expectExceptionMessage('Boundary not found where it should be.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a boundary with quotes in the request's headers. + */ + public function testBoundaryWithQuotes() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary="boundary_azertyuiop"'], + ); + + $multipartParser->parseNextPart(); + + // Dummy assertion, we just want to test that the parsing works. + $this->assertTrue(true); + } + + /** + * Test with a wrong Content-Type in the request's headers. + */ + public function testWrongContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Content-Type must be multipart/related'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/form-data; boundary="boundary_azertyuiop"'], + ); + } + + /** + * Test with a wrong key after the content type in the request's headers. + */ + public function testWrongKeyInContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Boundary is invalid'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; wrongkey="boundary_azertyuiop"'], + ); + } + + /** + * Test with a null Content-Type in the request's headers. + */ + public function testNullContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Content-Type can not be null'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => null], + + ); + } +} |