/** * Uploads a part of a multipart object upload * * @param Input $input Input data. You MUST specify the UploadID and PartNumber * @param string $bucket Bucket name * @param string $uri Object URI * @param array $requestHeaders Array of request headers or content type as a string * @param int $chunkSize Size of each upload chunk, in bytes. It cannot be less than 5242880 bytes (5Mb) * * @return null|string The ETag of the upload part of null if we have ran out of parts to upload */ public function uploadMultipart(Input $input, $bucket, $uri, $requestHeaders = array(), $chunkSize = 5242880) { if ($chunkSize < 5242880) { $chunkSize = 5242880; } // We need a valid UploadID and PartNumber $UploadID = $input->getUploadID(); $PartNumber = $input->getPartNumber(); if (empty($UploadID)) { throw new CannotPutFile(__METHOD__ . '(): No UploadID specified'); } if (empty($PartNumber)) { throw new CannotPutFile(__METHOD__ . '(): No PartNumber specified'); } $UploadID = urlencode($UploadID); $PartNumber = (int) $PartNumber; $request = new Request('PUT', $bucket, $uri, $this->configuration); $request->setParameter('partNumber', $PartNumber); $request->setParameter('uploadId', $UploadID); $request->setInput($input); // Full data length $totalSize = $input->getSize(); // No Content-Type for multipart uploads $input->setType(null); // Calculate part offset $partOffset = $chunkSize * ($PartNumber - 1); if ($partOffset > $totalSize) { // This is to signify that we ran out of parts ;) return null; } // How many parts are there? $totalParts = floor($totalSize / $chunkSize); if ($totalParts * $chunkSize < $totalSize) { $totalParts++; } // Calculate Content-Length $size = $chunkSize; if ($PartNumber >= $totalParts) { $size = $totalSize - ($PartNumber - 1) * $chunkSize; } if ($size <= 0) { // This is to signify that we ran out of parts ;) return null; } $input->setSize($size); switch ($input->getInputType()) { case Input::INPUT_DATA: $input->setData(substr($input->getData(), ($PartNumber - 1) * $chunkSize, $input->getSize())); break; case Input::INPUT_FILE: case Input::INPUT_RESOURCE: $fp = $input->getFp(); fseek($fp, ($PartNumber - 1) * $chunkSize); break; } // Custom request headers (Content-Type, Content-Disposition, Content-Encoding) if (is_array($requestHeaders)) { foreach ($requestHeaders as $h => $v) { if (strtolower(substr($h, 0, 6)) == 'x-amz-') { $request->setAmzHeader(strtolower($h), $v); } else { $request->setHeader($h, $v); } } } $request->setHeader('Content-Length', $input->getSize()); $response = $request->getResponse(); if ($response->code !== 200) { if (!$response->error->isError()) { $response->error = new Error($response->code, "Unexpected HTTP status {$response->code}"); } if (is_object($response->body) && $response->body instanceof \SimpleXMLElement && strpos($input->getSize(), ',') === false) { // For some moronic reason, trying to multipart upload files on some hosts comes back with a stupid // error from Amazon that we need to set Content-Length:5242880,5242880 instead of // Content-Length:5242880 which is AGAINST Amazon's documentation. In this case we pass the header // 'workaround-braindead-error-from-amazon' and retry. Screw you too, Amazon, you bloody idiots! if (isset($response->body->CanonicalRequest)) { $amazonsCanonicalRequest = (string) $response->body->CanonicalRequest; $lines = explode("\n", $amazonsCanonicalRequest); foreach ($lines as $line) { if (substr($line, 0, 15) != 'content-length:') { continue; } list($junk, $stupidAmazonDefinedContentLength) = explode(":", $line); if (strpos($stupidAmazonDefinedContentLength, ',') !== false) { if (!isset($requestHeaders['workaround-braindead-error-from-amazon'])) { $requestHeaders['workaround-braindead-error-from-amazon'] = 'you can\'t fix stupid'; return $this->uploadMultipart($input, $bucket, $uri, $requestHeaders, $chunkSize); } } } } } throw new CannotPutFile(sprintf(__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s", $response->error->getCode(), $response->error->getMessage(), print_r($response->body, true))); } // Return the ETag header return $response->headers['hash']; }