Commit c0e37132f1dd78fe7331faba1c608b65c487faf8

Authored by Paul Barrett
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
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  
... ...