Commit c0e37132f1dd78fe7331faba1c608b65c487faf8
1 parent
2a0d453f
KTS-4359. Added support for decoding of individual base64 encoded chunks concate…
…nated as a single string. Also added a file write function to the upload manager code which accepts an already decoded stream as input The CMISSpaces client appears to send document content in an unusual way, which needs to be catered for Fixed Committed by: Paul Barrett Reviewed by: Mark Holtzhausen
Showing
5 changed files
with
167 additions
and
25 deletions
ktwebservice/KTUploadManager.inc.php
| ... | ... | @@ -134,6 +134,36 @@ class KTUploadManager |
| 134 | 134 | |
| 135 | 135 | return $tempfilename; |
| 136 | 136 | } |
| 137 | + | |
| 138 | + /** | |
| 139 | + * | |
| 140 | + * @param string $content file content NOT base64 encoded (may be string, may be binary) | |
| 141 | + * @param string $prefix [optional] | |
| 142 | + * @return $tempfilename the name of the temporary file created | |
| 143 | + */ | |
| 144 | + function store_file($content, $prefix= 'sa_') | |
| 145 | + { | |
| 146 | + $tempfilename = $this->get_temp_filename($prefix); | |
| 147 | + if (!is_writable($tempfilename)) | |
| 148 | + { | |
| 149 | + return new PEAR_Error("Cannot write to file: $tempfilename"); | |
| 150 | + } | |
| 151 | + | |
| 152 | + if (!$this->is_valid_temporary_file($tempfilename)) | |
| 153 | + { | |
| 154 | + return new PEAR_Error("Invalid temporary file: $tempfilename. There is a problem with the temporary storage path: $this->temp_dir."); | |
| 155 | + } | |
| 156 | + | |
| 157 | + $fp=fopen($tempfilename, 'wb'); | |
| 158 | + if ($fp === false) | |
| 159 | + { | |
| 160 | + return new PEAR_Error("Cannot write content to temporary file: $tempfilename."); | |
| 161 | + } | |
| 162 | + fwrite($fp, $content); | |
| 163 | + fclose($fp); | |
| 164 | + | |
| 165 | + return $tempfilename; | |
| 166 | + } | |
| 137 | 167 | |
| 138 | 168 | /** |
| 139 | 169 | * This tells the manager to manage a file that has been uploaded. | ... | ... |
lib/api/ktcmis/services/CMISObjectService.inc.php
| ... | ... | @@ -80,16 +80,16 @@ class CMISObjectService { |
| 80 | 80 | * @param array $properties Array of properties which must be applied to the created document object |
| 81 | 81 | * @param string $folderId The id of the folder which will be the parent of the created document object |
| 82 | 82 | * This parameter is optional IF unfilingCapability is supported |
| 83 | - * @param contentStream $contentStream optional content stream data | |
| 83 | + * @param string $contentStream optional content stream data - expected as a base64 encoded string | |
| 84 | 84 | * @param string $versioningState optional version state value: checkedout/major/minor |
| 85 | 85 | * @return string $objectId The id of the created folder object |
| 86 | 86 | */ |
| 87 | 87 | // TODO throw ConstraintViolationException if: |
| 88 | 88 | // value of any of the properties violates the min/max/required/length constraints |
| 89 | - // specified in the property definition in the Object-Type. | |
| 89 | + // specified in the property definition in the Object-Type. | |
| 90 | 90 | function createDocument($repositoryId, $typeId, $properties, $folderId = null, |
| 91 | 91 | $contentStream = null, $versioningState = null) |
| 92 | - { | |
| 92 | + { | |
| 93 | 93 | $objectId = null; |
| 94 | 94 | |
| 95 | 95 | // fetch type definition of supplied type and check for base type "document", if not true throw exception |
| ... | ... | @@ -196,11 +196,17 @@ class CMISObjectService { |
| 196 | 196 | // this check isn't strictly necessary; however it is needed for a repository which does not support content streams |
| 197 | 197 | if (!empty($contentStream)) |
| 198 | 198 | { |
| 199 | + // TODO consider checking whether content is encoded (currently we expect encoded) | |
| 200 | + // TODO choose between this and the alternative decode function (see CMISUtil class) | |
| 201 | + // The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) | |
| 202 | + $contentStream = CMISUtil::decodeChunkedContentStream($contentStream); | |
| 203 | + | |
| 199 | 204 | // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists |
| 200 | 205 | // and has more functionality which could come in useful at some point I decided to go with that instead |
| 201 | 206 | // (did not know this existed when I wrote the CMISUtil function) |
| 202 | 207 | $uploadManager = new KTUploadManager(); |
| 203 | - $tempfilename = $uploadManager->store_base64_file($contentStream, 'cmis_'); | |
| 208 | + // assumes already decoded from base64, should use store_base64_file if not | |
| 209 | + $tempfilename = $uploadManager->store_file($contentStream, 'cmis_'); | |
| 204 | 210 | |
| 205 | 211 | // metadata |
| 206 | 212 | $metadata = array(); | ... | ... |
lib/api/ktcmis/util/CMISUtil.inc.php
| ... | ... | @@ -298,10 +298,6 @@ class CMISUtil { |
| 298 | 298 | $object['properties']['ObjectId'] = array('type' => $properties->getFieldType('ObjectId'), |
| 299 | 299 | 'value' => $properties->getValue('ObjectId')); |
| 300 | 300 | |
| 301 | - | |
| 302 | - | |
| 303 | - | |
| 304 | - | |
| 305 | 301 | if (strtolower($properties->getValue('ObjectTypeId')) == 'document') |
| 306 | 302 | { |
| 307 | 303 | |
| ... | ... | @@ -400,6 +396,117 @@ class CMISUtil { |
| 400 | 396 | |
| 401 | 397 | return $temp; |
| 402 | 398 | } |
| 399 | + | |
| 400 | + // TODO run evaluations on each of the following two functions and determine which | |
| 401 | + // is generally more efficienct | |
| 402 | + | |
| 403 | + /** | |
| 404 | + * Alternative function for decoding chunked streams, this will decode in blocks of 4. | |
| 405 | + * Not sure which method is more efficient, this or the function below (this does not | |
| 406 | + * re-encode but I am working on removing that step for the other function.) | |
| 407 | + * | |
| 408 | + * NOTE The current one appears to be much slower (14/4/57 vs 1/0/3 on respective test files) | |
| 409 | + * | |
| 410 | + * @param string $contentStream the base64 encoded content stream | |
| 411 | + * @return string $decoded the decoded content stream | |
| 412 | + */ | |
| 413 | + static public function decodeContentStream($contentStream) | |
| 414 | + { | |
| 415 | + $decoded = ''; | |
| 416 | + | |
| 417 | + $contentStream = preg_replace('/\r?\n+/', '', $contentStream); | |
| 418 | + | |
| 419 | + // decode in chunks or 4 chars at a time | |
| 420 | + for($i = 0, $len = strlen($contentStream); $i < $len; $i += 4) { | |
| 421 | + $decoded .= base64_decode(substr($contentStream, $i, 4)); | |
| 422 | + } | |
| 423 | + | |
| 424 | + return $decoded; | |
| 425 | + } | |
| 426 | + | |
| 427 | + /** | |
| 428 | + * Checks the contentStream and ensures that it is a correct base64 string; | |
| 429 | + * This is purely for clients such as CMISSpaces breaking the content into | |
| 430 | + * chunks before base64 encoding. | |
| 431 | + * | |
| 432 | + * If the stream is chunked, it is decoded in chunks and sent back as a single stream. | |
| 433 | + * If it is not chunked it is decoded as is and sent back as a single stream. | |
| 434 | + * | |
| 435 | + * NOTE this function and the above need to be checked for efficiency. | |
| 436 | + * The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) | |
| 437 | + * | |
| 438 | + * @param object $contentStream | |
| 439 | + * @return string decoded | |
| 440 | + */ | |
| 441 | + static public function decodeChunkedContentStream($contentStream) | |
| 442 | + { | |
| 443 | + // check the content stream for any lines of unusual length (except the last line, which can be any length) | |
| 444 | + $count = -1; | |
| 445 | + $length = 0; | |
| 446 | + $b64String = ''; | |
| 447 | + $outputStream = ''; | |
| 448 | + $decode = array(); | |
| 449 | + $chunks = 1; | |
| 450 | + $decoded = ''; | |
| 451 | + $chunked = ''; | |
| 452 | + | |
| 453 | + $splitStream = explode("\n", $contentStream); | |
| 454 | + foreach ($splitStream as $line) | |
| 455 | + { | |
| 456 | + $curlen = strlen($line); | |
| 457 | + | |
| 458 | + if ($length == 0) { | |
| 459 | + $length = $curlen; | |
| 460 | + } | |
| 461 | + | |
| 462 | + // if we find one we know that we must split the line here and end the previous base64 string | |
| 463 | + if ($curlen > $length) | |
| 464 | + { | |
| 465 | + // check for a new chunk | |
| 466 | + // either we have an equals sign (or two) | |
| 467 | + if (preg_match('/([^=]*={0,2})(.*)/', $line, $matches)) | |
| 468 | + { | |
| 469 | + $lastChunk = $matches[1]; | |
| 470 | + $nextChunk = $matches[2]; | |
| 471 | + } | |
| 472 | + // or we need to try by line length | |
| 473 | + else { | |
| 474 | + $lastChunk = substr($line, 0, $curlen - $length); | |
| 475 | + $nextChunk = substr($line, $curlen - $length); | |
| 476 | + } | |
| 477 | + | |
| 478 | + $decode[++$count] = $b64String . $lastChunk; | |
| 479 | + | |
| 480 | + $b64String = $nextChunk . "\n"; | |
| 481 | + $length = strlen($nextChunk); | |
| 482 | + | |
| 483 | + ++$chunks; | |
| 484 | + } | |
| 485 | + else { | |
| 486 | + $b64String .= $line . "\n"; | |
| 487 | + } | |
| 488 | + } | |
| 489 | + | |
| 490 | + // anything left over | |
| 491 | + if (!empty($b64String)) { | |
| 492 | + $decode[] = $b64String; | |
| 493 | + } | |
| 494 | + | |
| 495 | + if ($chunks > 1) | |
| 496 | + { | |
| 497 | + foreach($decode as $code) { | |
| 498 | + // decode, append to output to be re-encoded | |
| 499 | + $chunked .= base64_decode($code); | |
| 500 | + } | |
| 501 | + | |
| 502 | + $decoded = $chunked; | |
| 503 | + } | |
| 504 | + else { | |
| 505 | + $decoded = base64_decode($decode[0]); | |
| 506 | + } | |
| 507 | + | |
| 508 | + return $decoded; | |
| 509 | + } | |
| 403 | 510 | |
| 404 | 511 | } |
| 405 | 512 | ... | ... |
webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php
| ... | ... | @@ -103,10 +103,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { |
| 103 | 103 | // determine whether this is a folder or a document create |
| 104 | 104 | // document create will have a content tag <atom:content> or <content> containing base64 encoding of the document |
| 105 | 105 | $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); |
| 106 | - if (is_null($content)) | |
| 106 | + | |
| 107 | + // check content for weird chars | |
| 108 | + $matches = array(); | |
| 109 | + preg_match('/[^\w\d\/\+\n]*/', $content, $matches); | |
| 110 | + | |
| 111 | + if (is_null($content)) { | |
| 107 | 112 | $type = 'folder'; |
| 108 | - else | |
| 113 | + } | |
| 114 | + else { | |
| 109 | 115 | $type = 'document'; |
| 116 | + } | |
| 110 | 117 | |
| 111 | 118 | $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object'] |
| 112 | 119 | [0]['@children']['cmis:properties'] |
| ... | ... | @@ -124,20 +131,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { |
| 124 | 131 | |
| 125 | 132 | if ($typeId != 'Unknown') |
| 126 | 133 | { |
| 134 | + /*$f = fopen('c:\kt-stuff\here.txt', 'w'); | |
| 135 | + fwrite($f, 'fgfgfgfg'); | |
| 136 | + fclose($f);*/ | |
| 127 | 137 | $this->setStatus(self::STATUS_CREATED); |
| 128 | -// if ($type == 'folder') | |
| 129 | -// { | |
| 130 | - $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); | |
| 131 | -// } | |
| 132 | -// else | |
| 133 | -// { | |
| 134 | -// $NavigationService = new NavigationService(KT_cmis_atom_service_helper::getKt()); | |
| 135 | -// $cmisEntry = $ObjectService->getProperties($repositoryId, $folderId, false, false); | |
| 136 | -// $feed = $this->getFolderChildrenFeed($NavigationService, $repositoryId, $folderId, $cmisEntry['properties']['Name']['value'], 'POST'); | |
| 137 | -// } | |
| 138 | + $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); | |
| 138 | 139 | } |
| 139 | 140 | else |
| 140 | 141 | { |
| 142 | + /*$f = fopen('c:\kt-stuff\failed.txt', 'w'); | |
| 143 | + fwrite($f, 'fgfgfgfg'); | |
| 144 | + fclose($f);*/ | |
| 141 | 145 | $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $newObjectId['message']); |
| 142 | 146 | } |
| 143 | 147 | ... | ... |
webservice/atompub/cmis/index.php
| ... | ... | @@ -72,11 +72,6 @@ if(!KT_atom_HTTPauth::isLoggedIn()) { |
| 72 | 72 | KT_atom_HTTPauth::login('KnowledgeTree DMS', 'You must authenticate to enter this realm'); |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | -//$username = $_SERVER['PHP_AUTH_USER']; | |
| 76 | -//$password = $_SERVER['PHP_AUTH_PW']; | |
| 77 | -//// fetch user name and password (check auth include for proper method) | |
| 78 | -//KT_cmis_atom_service_helper::login($username, $password); | |
| 79 | - | |
| 80 | 75 | //Start the AtomPubProtocol Routing Engine |
| 81 | 76 | $APP = new KT_cmis_atom_server(); |
| 82 | 77 | ... | ... |