├── Lib └── AmazonS3.php ├── README.md └── Test ├── Case └── Lib │ └── AmazonS3Test.php └── test_app └── webroot └── files ├── avatars └── copify.png ├── csv_iso8859.csv └── dots.csv /Lib/AmazonS3.php: -------------------------------------------------------------------------------- 1 | accessKey, $this->secretKey, $this->bucket) = $config; 108 | // Set current date 109 | $this->setDate(); 110 | } 111 | 112 | /** 113 | * Put a local file in an S3 bucket 114 | * 115 | * @return void 116 | * @author Rob Mcvey 117 | **/ 118 | public function put($localPath , $remoteDir = null) { 119 | 120 | // Base filename 121 | $file = basename($localPath); 122 | 123 | // File remote/local files 124 | $this->checkLocalPath($localPath); 125 | $this->checkFile($file); 126 | $this->checkRemoteDir($remoteDir); 127 | 128 | // Signature 129 | $stringToSign = $this->stringToSign('put'); 130 | 131 | // Build the HTTP request 132 | $request = array( 133 | 'method' => 'PUT', 134 | 'uri' => array( 135 | 'scheme' => 'https', 136 | 'host' => $this->bucket . '.' . $this->endpoint, 137 | 'path' => $this->file, 138 | ), 139 | 'header' => array( 140 | 'Accept' => '*/*', 141 | 'User-Agent' => 'CakePHP', 142 | 'Date' => $this->date, 143 | 'Authorization' => 'AWS ' . $this->accessKey . ':' . $this->signature($stringToSign), 144 | 'Content-MD5' => $this->contentMd5, 145 | 'Content-Type' => $this->contentType, 146 | 'Content-Length' => $this->File->size() 147 | ), 148 | 'body' => $this->File->read() 149 | ); 150 | 151 | // Any addional Amazon headers to add? 152 | $request = $this->addAmazonHeadersToRequest($request); 153 | 154 | // Make the HTTP request 155 | $response = $this->handleRequest($request); 156 | 157 | // Handle response errors if any 158 | $this->handleResponse($response); 159 | } 160 | 161 | /** 162 | * Fetch a remote file from S3 and save locally 163 | * 164 | * @return void 165 | * @author Rob Mcvey 166 | **/ 167 | public function get($file , $localPath) { 168 | // File remote/local files 169 | $this->checkFile($file); 170 | $this->checkLocalPath($localPath); 171 | 172 | // Signature 173 | $stringToSign = $this->stringToSign('get'); 174 | 175 | $request = array( 176 | 'method' => 'GET', 177 | 'uri' => array( 178 | 'scheme' => 'https', 179 | 'host' => $this->bucket . '.' . $this->endpoint, 180 | 'path' => $this->file, 181 | ), 182 | 'header' => array( 183 | 'Accept' => '*/*', 184 | 'User-Agent' => 'CakePHP', 185 | 'Date' => $this->date, 186 | 'Authorization' => 'AWS ' . $this->accessKey . ':' . $this->signature($stringToSign) 187 | ) 188 | ); 189 | 190 | // Make the HTTP request 191 | $response = $this->handleRequest($request); 192 | 193 | // Handle response errors if any 194 | $this->handleResponse($response); 195 | 196 | // Write file locally 197 | $this->File = new File($this->localPath . DS . $this->file, true); 198 | $this->File->write($response->body); 199 | } 200 | 201 | /** 202 | * Delete a remote file from S3 203 | * 204 | * @return void 205 | * @author Rob Mcvey 206 | **/ 207 | public function delete($file) { 208 | // File remote/local files 209 | $this->checkFile($file); 210 | 211 | // Signature 212 | $stringToSign = $this->stringToSign('delete'); 213 | 214 | $request = array( 215 | 'method' => 'DELETE', 216 | 'uri' => array( 217 | 'scheme' => 'https', 218 | 'host' => $this->bucket . '.' . $this->endpoint, 219 | 'path' => $this->file, 220 | ), 221 | 'header' => array( 222 | 'Accept' => '*/*', 223 | 'User-Agent' => 'CakePHP', 224 | 'Date' => $this->date, 225 | 'Authorization' => 'AWS ' . $this->accessKey . ':' . $this->signature($stringToSign) 226 | ) 227 | ); 228 | 229 | // Make the HTTP request 230 | $response = $this->handleRequest($request); 231 | 232 | // Handle response errors if any 233 | $this->handleResponse($response); 234 | } 235 | 236 | /** 237 | * Sets the date we're working with. Used in both HTTP request and signature. 238 | * 239 | * @return void 240 | * @author Rob Mcvey 241 | **/ 242 | public function setDate($date = null) { 243 | if (!$date) { 244 | $this->date = gmdate('D, d M Y H:i:s \G\M\T'); 245 | } else { 246 | $this->date = $date; 247 | } 248 | } 249 | 250 | /** 251 | * Returns the public URL of a file 252 | * 253 | * @return string 254 | * @author Rob Mcvey 255 | **/ 256 | public function publicUrl($file, $ssl = false) { 257 | $scheme = 'http'; 258 | if ($ssl) { 259 | $scheme .= 's'; 260 | } 261 | // Replace any preceeding slashes 262 | $file = preg_replace("/^\//" , '', $file); 263 | return sprintf('%s://%s.s3.amazonaws.com/%s' , $scheme , $this->bucket, $file); 264 | } 265 | 266 | /** 267 | * Makes the HTTP Rest request 268 | * 269 | * @return void 270 | * @author Rob Mcvey 271 | **/ 272 | public function handleRequest($request) { 273 | // HttpSocket. 274 | if (!$this->HttpSocket) { 275 | $this->HttpSocket = new HttpSocket(); 276 | $this->HttpSocket->quirksMode = true; // Amazon returns sucky XML 277 | } 278 | 279 | // Make request 280 | try { 281 | return $this->HttpSocket->request($request); 282 | } catch (SocketException $e) { 283 | // If error Amazon returns garbage XML and 284 | // throws HttpSocket::_decodeChunkedBody - Could not parse malformed chunk ??? 285 | throw new AmazonS3Exception(__('Could not complete the HTTP request')); 286 | } 287 | } 288 | 289 | /** 290 | * Handles the HttpSocket response object and checks for any errors 291 | * 292 | * @return void 293 | * @author Rob Mcvey 294 | **/ 295 | public function handleResponse($response) { 296 | if (!property_exists($response , 'code')) { 297 | throw new InvalidArgumentException(__('The response from Amazon S3 is invalid')); 298 | } 299 | // All good 300 | if (in_array($response->code, array(200, 204))) { 301 | return $response->code; 302 | } else { 303 | $headers = $response->headers; 304 | $genericMessage = __('There was an error communicating with AWS'); 305 | if (isset($headers['Content-Type']) && $headers['Content-Type'] == 'application/xml') { 306 | $xml = $response->body; 307 | $Xml = Xml::build($xml); 308 | if (property_exists($Xml, 'Message')) { 309 | throw new AmazonS3Exception($Xml->Message); 310 | } else { 311 | throw new AmazonS3Exception($genericMessage); 312 | } 313 | } else { 314 | throw new AmazonS3Exception($genericMessage); 315 | } 316 | } 317 | } 318 | 319 | /** 320 | * Check we have a file string 321 | * 322 | * @return void 323 | * @author Rob Mcvey 324 | **/ 325 | public function checkFile($file) { 326 | $this->file = $file; 327 | // Set the target and local path to where we're saving 328 | if (empty($this->file)) { 329 | throw new InvalidArgumentException(__('You must specify the file you are fetching (e.g remote_dir/file.txt)')); 330 | } 331 | } 332 | 333 | /** 334 | * Check our local path 335 | * 336 | * @return void 337 | * @author Rob Mcvey 338 | **/ 339 | public function checkLocalPath($localPath) { 340 | $this->localPath = $localPath; 341 | if (empty($this->localPath)) { 342 | throw new InvalidArgumentException(__('You must set a localPath (i.e where to save the file)')); 343 | } 344 | if (!file_exists($this->localPath)) { 345 | throw new InvalidArgumentException(__('The localPath you set does not exist')); 346 | } 347 | } 348 | 349 | /** 350 | * Removes preceeding/trailing slashes from a remote dir target and builds $this->file again 351 | * 352 | * @return void 353 | * @author Rob Mcvey 354 | **/ 355 | public function checkRemoteDir($remoteDir) { 356 | // Is the target a directory? Remove preceending and trailing / 357 | if ($remoteDir) { 358 | $remoteDir = preg_replace(array("/^\//", "/\/$/") , "", $remoteDir); 359 | $this->file = $remoteDir . '/' . $this->file; 360 | } 361 | } 362 | 363 | /** 364 | * Creates the Authorization header string to sign 365 | * 366 | * @return void 367 | * @author Rob Mcvey 368 | **/ 369 | public function stringToSign($method = 'get') { 370 | 371 | // PUT object, need hash and content type 372 | if (strtoupper($method) == 'PUT') { 373 | $this->getLocalFileInfo(); 374 | $this->contentType = $this->info['mime']; 375 | $this->contentMd5 = $this->getContentMd5(); 376 | } 377 | 378 | // Add any additional Amazon specific headers if present 379 | $this->buildAmazonHeaders(); 380 | 381 | // stringToSign 382 | $toSign = strtoupper($method) . "\n"; 383 | $toSign .= $this->contentMd5 . "\n"; 384 | $toSign .= $this->contentType . "\n"; 385 | $toSign .= $this->date . "\n"; 386 | $toSign .= $this->canonicalizedAmzHeaders; 387 | $toSign .= '/' . $this->bucket . '/' . $this->file; 388 | return $toSign; 389 | } 390 | 391 | /** 392 | * Takes the request array pre-put and adds any additional amazon headers 393 | * 394 | * @return void 395 | * @author Rob Mcvey 396 | **/ 397 | public function addAmazonHeadersToRequest($request) { 398 | if (!empty($this->amazonHeaders) && is_array($this->amazonHeaders)) { 399 | foreach ($this->amazonHeaders as $k => $header) { 400 | $request['header'][$k] =$header; 401 | } 402 | } 403 | return $request; 404 | } 405 | 406 | /** 407 | * Add any additional Amazon specific headers if present 408 | * 409 | * @return void 410 | * @author Rob Mcvey 411 | **/ 412 | public function buildAmazonHeaders() { 413 | if (!empty($this->amazonHeaders) && is_array($this->amazonHeaders)) { 414 | $this->canonicalizedAmzHeaders = ''; 415 | $this->sortLexicographically(); 416 | foreach ($this->amazonHeaders as $k => $header) { 417 | $this->canonicalizedAmzHeaders .= strtolower($k) . ":" . $header . "\n"; 418 | } 419 | } 420 | } 421 | 422 | /** 423 | * Sort the collection of headers lexicographically by header name. 424 | * 425 | * @return void 426 | * @author Rob Mcvey 427 | **/ 428 | public function sortLexicographically() { 429 | ksort($this->amazonHeaders, SORT_FLAG_CASE | SORT_NATURAL); 430 | } 431 | 432 | /** 433 | * Create signature for the Authorization header. 434 | * Format: Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ); 435 | * @param string the header string to sign 436 | * @return string base64_encode encoded string 437 | * @link http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html 438 | */ 439 | public function signature($stringToSign) { 440 | return base64_encode(hash_hmac('sha1', $stringToSign, $this->secretKey, true)); 441 | } 442 | 443 | /** 444 | * Get local file info (uses CakePHP Utility/File class) 445 | * 446 | * @return array 447 | * @author Rob Mcvey 448 | **/ 449 | public function getLocalFileInfo() { 450 | $this->File = new File($this->localPath); 451 | $this->info = $this->File->info(); 452 | return $this->info; 453 | } 454 | 455 | /** 456 | * Return base64 encoded file checksum 457 | * 458 | * @return string 459 | * @author Rob Mcvey 460 | **/ 461 | public function getContentMd5() { 462 | return base64_encode(md5_file($this->localPath , true)); 463 | } 464 | 465 | } 466 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon S3 Plugin for CakePHP 2.x 2 | 3 | A CakePHP Plugin to interact with Amazon Web Services S3 objects. This plugin provides a simple and robust methods that can be added to any CakePHP project to complete the following: 4 | 5 | * Retrieve a remote file from an S3 bucket and save locally 6 | * Save a local file in an S3 bucket 7 | * Delete a file in an S3 bucket 8 | 9 | ### Requirements 10 | 11 | * CakePHP 2.x 12 | * An Amazon Web Services account (http://aws.amazon.com/s3/) 13 | * Your AWS access key and secret key 14 | 15 | ### Installation 16 | 17 | _[Manual]_ 18 | 19 | * Download this: [http://github.com/robmcvey/cakephp-amazon-s3/zipball/master](http://github.com/robmcvey/cakephp-amazon-s3/zipball/master) 20 | * Unzip that download. 21 | * Copy the resulting folder to `app/Plugin` 22 | * Rename the folder you just copied to `AmazonS3` 23 | 24 | _[GIT Submodule]_ 25 | 26 | In your app directory type: 27 | 28 | ```shell 29 | git submodule add -b master git://github.com/robmcvey/cakephp-amazon-s3.git Plugin/AmazonS3 30 | git submodule init 31 | git submodule update 32 | ``` 33 | 34 | _[GIT Clone]_ 35 | 36 | In your `Plugin` directory type: 37 | 38 | ```shell 39 | git clone -b master git://github.com/robmcvey/cakephp-amazon-s3.git AmazonS3 40 | ``` 41 | 42 | ### Usuage examples 43 | 44 | Remember to add `CakePlugin::load('AmazonS3');` to your app's bootstrap file. 45 | 46 | Then, simply initialise the plugin with your AWS Access key, secret key and the bucket name you wish to work with. 47 | 48 | ```php 49 | App::uses('AmazonS3', 'AmazonS3.Lib'); 50 | $AmazonS3 = new AmazonS3(array('{access key}', '{secret key}', '{bucket name}')); 51 | ``` 52 | 53 | ### GET 54 | 55 | The `get` method retrieves a remote file and saves it locally. So let's say there is the file `foo.jpg` on S3 and you want to save it locally in `/home/me/stuff/photos` you'd use the following. 56 | 57 | ```php 58 | $AmazonS3->get('foo.jpg' , '/home/me/stuff/photos'); 59 | ``` 60 | 61 | ### PUT 62 | 63 | The `put` method does the reverse of `get`, and saves a local file to S3. 64 | 65 | ```php 66 | $AmazonS3->put('/home/me/stuff/photos/foo.jpg'); 67 | ``` 68 | 69 | You can optionally specifiy a remote directory within the bucket to save the file in. Be sure not to include a starting / in the remote directory string. 70 | 71 | ```php 72 | $AmazonS3->put('/home/me/stuff/photos/foo.jpg' , 'some/folder'); 73 | ``` 74 | 75 | To add any additional AWS headers to a `put`, example to set the file as "public", they can be passed as an array to the `amazonHeaders` property. 76 | 77 | ```php 78 | $AmazonS3->amazonHeaders = array( 79 | 'x-amz-acl' => 'public-read', 80 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz' 81 | ); 82 | $AmazonS3->put('/home/me/stuff/photos/foo.jpg' , 'some/folder'); 83 | ``` 84 | 85 | ### DELETE 86 | 87 | Deletes a file from S3. 88 | 89 | ```php 90 | $AmazonS3->delete('foo.jpg'); 91 | ``` 92 | 93 | Or delete from within a directory in the bucket. Be sure not to include a starting / in the string or you will receive a SignatureDoesNotMatch error: 94 | 95 | ```php 96 | $AmazonS3->delete('some/folder/foo.jpg'); 97 | ``` 98 | -------------------------------------------------------------------------------- /Test/Case/Lib/AmazonS3Test.php: -------------------------------------------------------------------------------- 1 | AmazonS3 = new AmazonS3(array('foo' , 'bar', 'bucket')); 34 | } 35 | 36 | /** 37 | * tearDown method 38 | * 39 | * @return void 40 | */ 41 | public function tearDown() { 42 | parent::tearDown(); 43 | unset($this->AmazonS3); 44 | } 45 | 46 | /** 47 | * testConstructor 48 | * 49 | * @return void 50 | * @author Rob Mcvey 51 | **/ 52 | public function testConstructor() { 53 | $this->assertEqual('foo' , $this->AmazonS3->accessKey); 54 | $this->assertEqual('bar' , $this->AmazonS3->secretKey); 55 | $this->assertEqual('bucket' , $this->AmazonS3->bucket); 56 | $this->assertEqual(date('Y') , date('Y' , strtotime($this->AmazonS3->date))); 57 | } 58 | 59 | /** 60 | * testHandleResponse 61 | * 62 | * @return void 63 | * @author Rob Mcvey 64 | **/ 65 | public function testHandleResponse() { 66 | // Mock the HttpSocket response 67 | $HttpSocketResponse = new stdClass(); 68 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 69 | $HttpSocketResponse->code = 200; 70 | $HttpSocketResponse->reasonPhrase = 'OK'; 71 | $HttpSocketResponse->headers = array( 72 | 'x-amz-id-2' => '4589328529385938', 73 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 74 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 75 | 'Last-Modified' => 'Tue, 29 Nov 2011 10:30:03 GMT', 76 | 'ETag' => '24562346dgdgsdgf2352"', 77 | 'Accept-Ranges' => 'bytes', 78 | 'Content-Type' => 'image/jpeg', 79 | 'Content-Length' => 36410, 80 | 'Connection' => 'close', 81 | 'Server' => 'AmazonS3' 82 | ); 83 | $result = $this->AmazonS3->handleResponse($HttpSocketResponse); 84 | $this->assertEqual(200 , $result); 85 | } 86 | 87 | /** 88 | * testHandleResponseBadFormat 89 | * 90 | * @return void 91 | * @author Rob Mcvey 92 | * @expectedException InvalidArgumentException 93 | * @expectedExceptionMessage The response from Amazon S3 is invalid 94 | **/ 95 | public function testHandleResponseBadFormat() { 96 | // Mock the HttpSocket response 97 | $HttpSocketResponse = new stdClass(); 98 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 99 | $HttpSocketResponse->reasonPhrase = 'Foo'; 100 | $HttpSocketResponse->headers = array( 101 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 102 | ); 103 | $result = $this->AmazonS3->handleResponse($HttpSocketResponse); 104 | } 105 | 106 | /** 107 | * testPut 108 | * 109 | * @return void 110 | * @author Rob Mcvey 111 | **/ 112 | public function testPut() { 113 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'dots.csv'; 114 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 115 | 116 | // Mock the built request 117 | $expectedRequest = array( 118 | 'method' => 'PUT', 119 | 'uri' => array( 120 | 'host' => 'bucket.s3.amazonaws.com', 121 | 'scheme' => 'https', 122 | 'path' => 'dots.csv' 123 | ), 124 | 'header' => array( 125 | 'Accept' => '*/*', 126 | 'User-Agent' => 'CakePHP', 127 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 128 | 'Authorization' => 'AWS foo:9Lsir9m2y16ffUi6v+KlRe0pGdA=', 129 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 130 | 'Content-Type' => 'text/plain', 131 | 'Content-Length' => 3 132 | ), 133 | 'body' => '...' 134 | ); 135 | 136 | // Mock the HttpSocket response 137 | $HttpSocketResponse = new stdClass(); 138 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 139 | $HttpSocketResponse->code = 200; 140 | $HttpSocketResponse->reasonPhrase = 'OK'; 141 | $HttpSocketResponse->headers = array( 142 | 'x-amz-id-2' => '4589328529385938', 143 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 144 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 145 | 'Last-Modified' => 'Tue, 29 Nov 2011 10:30:03 GMT', 146 | 'ETag' => '24562346dgdgsdgf2352"', 147 | 'Accept-Ranges' => 'bytes', 148 | 'Content-Type' => 'image/jpeg', 149 | 'Content-Length' => 0, 150 | 'Connection' => 'close', 151 | 'Server' => 'AmazonS3' 152 | ); 153 | 154 | // Mock the HttpSocket class 155 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 156 | $this->AmazonS3->HttpSocket->expects($this->once()) 157 | ->method('request') 158 | ->with($expectedRequest) 159 | ->will($this->returnValue($HttpSocketResponse)); 160 | 161 | $this->AmazonS3->put($file_path); 162 | } 163 | 164 | /** 165 | * testPutWithDir 166 | * 167 | * @return void 168 | * @author Rob Mcvey 169 | **/ 170 | public function testPutWithDir() { 171 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'dots.csv'; 172 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 173 | 174 | // Mock the built request 175 | $expectedRequest = array( 176 | 'method' => 'PUT', 177 | 'uri' => array( 178 | 'host' => 'bucket.s3.amazonaws.com', 179 | 'scheme' => 'https', 180 | 'path' => 'some/dir/in/the/bucket/dots.csv' 181 | ), 182 | 'header' => array( 183 | 'Accept' => '*/*', 184 | 'User-Agent' => 'CakePHP', 185 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 186 | 'Authorization' => 'AWS foo:mCE9RQ8UJYRItzike6XZFd7XjcI=', 187 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 188 | 'Content-Type' => 'text/plain', 189 | 'Content-Length' => 3 190 | ), 191 | 'body' => '...' 192 | ); 193 | 194 | // Mock the HttpSocket response 195 | $HttpSocketResponse = new stdClass(); 196 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 197 | $HttpSocketResponse->code = 200; 198 | $HttpSocketResponse->reasonPhrase = 'OK'; 199 | $HttpSocketResponse->headers = array( 200 | 'x-amz-id-2' => '4589328529385938', 201 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 202 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 203 | 'Last-Modified' => 'Tue, 29 Nov 2011 10:30:03 GMT', 204 | 'ETag' => '24562346dgdgsdgf2352"', 205 | 'Accept-Ranges' => 'bytes', 206 | 'Content-Type' => 'image/jpeg', 207 | 'Content-Length' => 0, 208 | 'Connection' => 'close', 209 | 'Server' => 'AmazonS3' 210 | ); 211 | 212 | // Mock the HttpSocket class 213 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 214 | $this->AmazonS3->HttpSocket->expects($this->once()) 215 | ->method('request') 216 | ->with($expectedRequest) 217 | ->will($this->returnValue($HttpSocketResponse)); 218 | 219 | $this->AmazonS3->put($file_path , '/some/dir/in/the/bucket/'); 220 | } 221 | 222 | /** 223 | * testPutWithMoreHeaders 224 | * 225 | * @return void 226 | * @author Rob Mcvey 227 | **/ 228 | public function testPutWithMoreHeaders() { 229 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'dots.csv'; 230 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 231 | 232 | // Mock the built request 233 | $expectedRequest = array( 234 | 'method' => 'PUT', 235 | 'uri' => array( 236 | 'host' => 'bucket.s3.amazonaws.com', 237 | 'scheme' => 'https', 238 | 'path' => 'some/dir/in/the/bucket/dots.csv' 239 | ), 240 | 'header' => array( 241 | 'Accept' => '*/*', 242 | 'User-Agent' => 'CakePHP', 243 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 244 | 'Authorization' => 'AWS foo:WxdnOvuaK37BwO72xShLSFu80LI=', 245 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 246 | 'Content-Type' => 'text/plain', 247 | 'Content-Length' => 3, 248 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 249 | 'x-amz-acl' => 'public-read', 250 | ), 251 | 'body' => '...' 252 | ); 253 | 254 | // Mock the HttpSocket response 255 | $HttpSocketResponse = new stdClass(); 256 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 257 | $HttpSocketResponse->code = 200; 258 | $HttpSocketResponse->reasonPhrase = 'OK'; 259 | $HttpSocketResponse->headers = array( 260 | 'x-amz-id-2' => '4589328529385938', 261 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 262 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 263 | 'Last-Modified' => 'Tue, 29 Nov 2011 10:30:03 GMT', 264 | 'ETag' => '24562346dgdgsdgf2352"', 265 | 'Accept-Ranges' => 'bytes', 266 | 'Content-Type' => 'image/jpeg', 267 | 'Content-Length' => 0, 268 | 'Connection' => 'close', 269 | 'Server' => 'AmazonS3' 270 | ); 271 | 272 | // Mock the HttpSocket class 273 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 274 | $this->AmazonS3->HttpSocket->expects($this->once()) 275 | ->method('request') 276 | ->with($expectedRequest) 277 | ->will($this->returnValue($HttpSocketResponse)); 278 | 279 | $this->AmazonS3->amazonHeaders = array( 280 | 'x-amz-acl' => 'public-read', 281 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 282 | ); 283 | 284 | $this->AmazonS3->put($file_path , '/some/dir/in/the/bucket/'); 285 | } 286 | 287 | /** 288 | * testGet 289 | * 290 | * @return void 291 | * @author Rob Mcvey 292 | **/ 293 | public function testGet() { 294 | $localPath = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'; 295 | $file = 'lolcat.jpg'; 296 | $fullpath = $localPath . DS . $file; 297 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 298 | 299 | // Mock the built request 300 | $expectedRequest = array( 301 | 'method' => 'GET', 302 | 'uri' => array( 303 | 'host' => 'bucket.s3.amazonaws.com', 304 | 'scheme' => 'https', 305 | 'path' => 'lolcat.jpg' 306 | ), 307 | 'header' => array( 308 | 'Accept' => '*/*', 309 | 'User-Agent' => 'CakePHP', 310 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 311 | 'Authorization' => 'AWS foo:diD8K7IEVSvuywBxAoERbEu1rXM=', 312 | ) 313 | ); 314 | 315 | // Mock the HttpSocket response 316 | $HttpSocketResponse = new stdClass(); 317 | $HttpSocketResponse->body = '????JFIFdd??Duckya??Adobed??????????'; 318 | $HttpSocketResponse->code = 200; 319 | $HttpSocketResponse->reasonPhrase = 'OK'; 320 | $HttpSocketResponse->headers = array( 321 | 'x-amz-id-2' => '4589328529385938', 322 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 323 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 324 | 'Last-Modified' => 'Tue, 29 Nov 2011 10:30:03 GMT', 325 | 'ETag' => '24562346dgdgsdgf2352"', 326 | 'Accept-Ranges' => 'bytes', 327 | 'Content-Type' => 'image/jpeg', 328 | 'Content-Length' => '36410', 329 | 'Connection' => 'close', 330 | 'Server' => 'AmazonS3' 331 | ); 332 | 333 | // Mock the HttpSocket class 334 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 335 | $this->AmazonS3->HttpSocket->expects($this->once()) 336 | ->method('request') 337 | ->with($expectedRequest) 338 | ->will($this->returnValue($HttpSocketResponse)); 339 | 340 | $this->AmazonS3->get($file , $localPath); 341 | $this->assertFileExists($fullpath); 342 | 343 | $this->assertEquals('????JFIFdd??Duckya??Adobed??????????' , file_get_contents($fullpath)); 344 | unlink($fullpath); // Bye bye 345 | } 346 | 347 | /** 348 | * testDelete 349 | * 350 | * @return void 351 | * @author Rob Mcvey 352 | **/ 353 | public function testDelete() { 354 | $file = 'public/dots.csv'; 355 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 356 | 357 | // Mock the built request 358 | $expectedRequest = array( 359 | 'method' => 'DELETE', 360 | 'uri' => array( 361 | 'host' => 'bucket.s3.amazonaws.com', 362 | 'scheme' => 'https', 363 | 'path' => 'public/dots.csv' 364 | ), 365 | 'header' => array( 366 | 'Accept' => '*/*', 367 | 'User-Agent' => 'CakePHP', 368 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 369 | 'Authorization' => 'AWS foo:+UdJAQGhwJf06KRWWIXumX/5V4Y=', 370 | ) 371 | ); 372 | 373 | // Mock the HttpSocket response 374 | $HttpSocketResponse = new stdClass(); 375 | $HttpSocketResponse->body = ''; 376 | $HttpSocketResponse->code = 204; 377 | $HttpSocketResponse->reasonPhrase = 'No Content'; 378 | $HttpSocketResponse->headers = array( 379 | 'x-amz-id-2' => '4589328529385938', 380 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 381 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 382 | 'Connection' => 'close', 383 | 'Server' => 'AmazonS3' 384 | ); 385 | 386 | // Mock the HttpSocket class 387 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 388 | $this->AmazonS3->HttpSocket->expects($this->once()) 389 | ->method('request') 390 | ->with($expectedRequest) 391 | ->will($this->returnValue($HttpSocketResponse)); 392 | 393 | $this->AmazonS3->delete($file); 394 | } 395 | 396 | /** 397 | * testGetException 398 | * 399 | * @return void 400 | * @author Rob Mcvey 401 | * @expectedException AmazonS3Exception 402 | * @expectedExceptionMessage The AWS Access Key Id you provided does not exist in our records. 403 | **/ 404 | public function testGetException() { 405 | $localPath = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'; 406 | $file = 'lolcat.jpg'; 407 | $fullpath = $localPath . DS . $file; 408 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 409 | 410 | // Mock the built request 411 | $expectedRequest = array( 412 | 'method' => 'GET', 413 | 'uri' => array( 414 | 'host' => 'bucket.s3.amazonaws.com', 415 | 'scheme' => 'https', 416 | 'path' => 'lolcat.jpg' 417 | ), 418 | 'header' => array( 419 | 'Accept' => '*/*', 420 | 'User-Agent' => 'CakePHP', 421 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 422 | 'Authorization' => 'AWS foo:diD8K7IEVSvuywBxAoERbEu1rXM=', 423 | ) 424 | ); 425 | 426 | // Mock the HttpSocket response 427 | $HttpSocketResponse = new stdClass(); 428 | $HttpSocketResponse->body = 'InvalidAccessKeyIdThe AWS Access Key Id you provided does not exist in our records.452345DFSG3425foo'; 429 | $HttpSocketResponse->code = 403; 430 | $HttpSocketResponse->headers = array( 431 | 'x-amz-id-2' => '4589328529385938', 432 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 433 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 434 | 'Accept-Ranges' => 'bytes', 435 | 'Content-Type' => 'application/xml', 436 | 'Transfer-Encoding' => 'chunked', 437 | 'Connection' => 'close', 438 | 'Server' => 'AmazonS3' 439 | ); 440 | 441 | // Mock the HttpSocket class 442 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 443 | $this->AmazonS3->HttpSocket->expects($this->once()) 444 | ->method('request') 445 | ->with($expectedRequest) 446 | ->will($this->returnValue($HttpSocketResponse)); 447 | 448 | $this->AmazonS3->get($file, $localPath); 449 | $this->assertFalse(file_exists($fullpath)); 450 | unlink($fullpath); // Bye bye just in case 451 | } 452 | 453 | /** 454 | * testGetGenericException 455 | * 456 | * @return void 457 | * @author Rob Mcvey 458 | * @expectedException AmazonS3Exception 459 | * @expectedExceptionMessage There was an error communicating with AWS 460 | **/ 461 | public function testGetGenericException() { 462 | $localPath = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'; 463 | $file = 'lolcat.jpg'; 464 | $fullpath = $localPath . DS . $file; 465 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 466 | 467 | // Mock the built request 468 | $expectedRequest = array( 469 | 'method' => 'GET', 470 | 'uri' => array( 471 | 'host' => 'bucket.s3.amazonaws.com', 472 | 'scheme' => 'https', 473 | 'path' => 'lolcat.jpg' 474 | ), 475 | 'header' => array( 476 | 'Accept' => '*/*', 477 | 'User-Agent' => 'CakePHP', 478 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 479 | 'Authorization' => 'AWS foo:diD8K7IEVSvuywBxAoERbEu1rXM=', 480 | ) 481 | ); 482 | 483 | // Mock the HttpSocket response 484 | $HttpSocketResponse = new stdClass(); 485 | $HttpSocketResponse->body = 'Woops2342134123DFHGDHGDHDFGHfoo'; 486 | $HttpSocketResponse->code = 403; 487 | $HttpSocketResponse->headers = array( 488 | 'x-amz-id-2' => '4589328529385938', 489 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 490 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 491 | 'Accept-Ranges' => 'bytes', 492 | 'Content-Type' => 'application/xml', 493 | 'Transfer-Encoding' => 'chunked', 494 | 'Connection' => 'close', 495 | 'Server' => 'AmazonS3' 496 | ); 497 | 498 | // Mock the HttpSocket class 499 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 500 | $this->AmazonS3->HttpSocket->expects($this->once()) 501 | ->method('request') 502 | ->with($expectedRequest) 503 | ->will($this->returnValue($HttpSocketResponse)); 504 | 505 | $this->AmazonS3->get($file, $localPath); 506 | $this->assertFalse(file_exists($fullpath)); 507 | unlink($fullpath); // Bye bye just in case 508 | } 509 | 510 | /** 511 | * testGetBuggyXmlException 512 | * 513 | * @return void 514 | * @author Rob Mcvey 515 | * @expectedException AmazonS3Exception 516 | * @expectedExceptionMessage There was an error communicating with AWS 517 | **/ 518 | public function testGetBuggyXmlException() { 519 | $localPath = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'; 520 | $file = 'lolcat.jpg'; 521 | $fullpath = $localPath . DS . $file; 522 | $this->AmazonS3->setDate('Mon, 23 Sep 2013 08:46:05 GMT'); 523 | 524 | // Mock the built request 525 | $expectedRequest = array( 526 | 'method' => 'GET', 527 | 'uri' => array( 528 | 'host' => 'bucket.s3.amazonaws.com', 529 | 'scheme' => 'https', 530 | 'path' => 'lolcat.jpg' 531 | ), 532 | 'header' => array( 533 | 'Accept' => '*/*', 534 | 'User-Agent' => 'CakePHP', 535 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 536 | 'Authorization' => 'AWS foo:+UdJAQGhwJf06KRWWIXumX/5V4Y=', 537 | ) 538 | ); 539 | 540 | 541 | // Mock the HttpSocket response 542 | $HttpSocketResponse = new stdClass(); 543 | $HttpSocketResponse->body = 'Woops2342134123DFHGDHGDHDFGHfoo'; 544 | $HttpSocketResponse->code = 403; 545 | $HttpSocketResponse->headers = array( 546 | 'x-amz-id-2' => '4589328529385938', 547 | 'x-amz-request-id' => 'GSDFGt45egdfsC', 548 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 549 | 'Accept-Ranges' => 'bytes', 550 | 'Content-Type' => 'application/xml', 551 | 'Transfer-Encoding' => 'chunked', 552 | 'Connection' => 'close', 553 | 'Server' => 'AmazonS3' 554 | ); 555 | 556 | // Mock the HttpSocket class 557 | $this->AmazonS3->HttpSocket = $this->getMock('HttpSocket'); 558 | $this->AmazonS3->HttpSocket->expects($this->once()) 559 | ->method('request') 560 | ->with() 561 | ->will($this->returnValue($HttpSocketResponse)); 562 | 563 | $this->AmazonS3->get($file, $localPath); 564 | $this->assertFalse(file_exists($fullpath)); 565 | unlink($fullpath); // Bye bye just in case 566 | } 567 | 568 | /** 569 | * testStringToSignGet 570 | * 571 | * @return void 572 | * @author Rob Mcvey 573 | **/ 574 | public function testStringToSignGet() { 575 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'; 576 | $this->AmazonS3->file = 'lolcat.jpg'; 577 | $this->AmazonS3->localPath = $file_path; 578 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 579 | $expected = "GET" . "\n"; 580 | $expected .= "" . "\n"; 581 | $expected .= "" . "\n"; 582 | $expected .= "Sun, 22 Sep 2013 14:43:04 GMT" . "\n"; 583 | $expected .= "/bucket/lolcat.jpg"; 584 | $result = $this->AmazonS3->stringToSign('get'); 585 | $this->assertEqual($expected, $result); 586 | } 587 | 588 | /** 589 | * testStringToSignPutPng 590 | * 591 | * @return void 592 | * @author Rob Mcvey 593 | **/ 594 | public function testStringToSignPutPng() { 595 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files'. DS . 'avatars' . DS . 'copify.png'; 596 | $this->AmazonS3->localPath = $file_path; 597 | $this->AmazonS3->file = basename($file_path); 598 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 599 | $expected = "PUT" . "\n"; 600 | $expected .= "aUIOL+kLNYqj1ZPXnf8+yw==" . "\n"; 601 | $expected .= "image/png" . "\n"; 602 | $expected .= "Sun, 22 Sep 2013 14:43:04 GMT" . "\n"; 603 | $expected .= "/bucket/copify.png"; 604 | $result = $this->AmazonS3->stringToSign('put'); 605 | $this->assertEqual($expected, $result); 606 | } 607 | 608 | /** 609 | * testStringToSignPutCsv 610 | * 611 | * @return void 612 | * @author Rob Mcvey 613 | **/ 614 | public function testStringToSignPutCsv() { 615 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'dots.csv'; 616 | $this->AmazonS3->localPath = $file_path; 617 | $this->AmazonS3->file = basename($file_path); 618 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 619 | $expected = "PUT" . "\n"; 620 | $expected .= "L0O0L9gz0ed0IKja50GQAA==" . "\n"; 621 | $expected .= "text/plain" . "\n"; 622 | $expected .= "Sun, 22 Sep 2013 14:43:04 GMT" . "\n"; 623 | $expected .= "/bucket/dots.csv"; 624 | $result = $this->AmazonS3->stringToSign('put'); 625 | $this->assertEqual($expected, $result); 626 | } 627 | 628 | /** 629 | * testStringToSignPutCsv 630 | * 631 | * @return void 632 | * @author Rob Mcvey 633 | **/ 634 | public function testStringToSignDeleteCsv() { 635 | $this->AmazonS3->file = 'lolcat.jpg'; 636 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 637 | $expected = "DELETE" . "\n"; 638 | $expected .= "" . "\n"; 639 | $expected .= "" . "\n"; 640 | $expected .= "Sun, 22 Sep 2013 14:43:04 GMT" . "\n"; 641 | $expected .= "/bucket/lolcat.jpg"; 642 | $result = $this->AmazonS3->stringToSign('delete'); 643 | $this->assertEqual($expected, $result); 644 | } 645 | 646 | /** 647 | * testStringToSignPutPngAdditionalHeaders 648 | * 649 | * @return void 650 | * @author Rob Mcvey 651 | **/ 652 | public function testStringToSignPutPngAdditionalHeaders() { 653 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'avatars' . DS . 'copify.png'; 654 | $this->AmazonS3->localPath = $file_path; 655 | $this->AmazonS3->file = basename($file_path); 656 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 657 | $this->AmazonS3->amazonHeaders = array( 658 | 'x-amz-acl' => 'public-read', 659 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 660 | ); 661 | $expected = "PUT" . "\n"; 662 | $expected .= "aUIOL+kLNYqj1ZPXnf8+yw==" . "\n"; 663 | $expected .= "image/png" . "\n"; 664 | $expected .= "Sun, 22 Sep 2013 14:43:04 GMT"; 665 | $expected .= "\n"; 666 | $expected .= "x-amz-acl:public-read" . "\n"; 667 | $expected .= "x-amz-meta-reviewedby:john.doe@yahoo.biz" . "\n"; 668 | $expected .= "/bucket/copify.png"; 669 | $result = $this->AmazonS3->stringToSign('put'); 670 | $this->assertEqual($expected, $result); 671 | } 672 | 673 | /** 674 | * testBuildAmazonHeaders 675 | * 676 | * @return void 677 | * @author Rob Mcvey 678 | **/ 679 | public function testBuildAmazonHeaders() { 680 | $this->AmazonS3->buildAmazonHeaders(); 681 | $this->assertTrue(empty($this->AmazonS3->canonicalizedAmzHeaders)); 682 | $this->AmazonS3->amazonHeaders = array( 683 | 'x-amz-acl' => 'public-read', 684 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 685 | ); 686 | $this->AmazonS3->buildAmazonHeaders(); 687 | $this->assertEqual("x-amz-acl:public-read\nx-amz-meta-reviewedby:john.doe@yahoo.biz\n" , $this->AmazonS3->canonicalizedAmzHeaders); 688 | } 689 | 690 | /** 691 | * testSortLexicographically 692 | * 693 | * @return void 694 | * @author Rob Mcvey 695 | **/ 696 | public function testSortLexicographically() { 697 | $this->AmazonS3->amazonHeaders = array( 698 | 'X-Amz-Meta-ReviewedBy' => 'bob@gmail.biz', 699 | 'X-Amz-CUSTOM' => 'chicken-soup', 700 | ); 701 | $this->AmazonS3->sortLexicographically(); 702 | $result = $this->AmazonS3->amazonHeaders; 703 | $expected = array( 704 | 'X-Amz-CUSTOM' => 'chicken-soup', 705 | 'X-Amz-Meta-ReviewedBy' => 'bob@gmail.biz', 706 | ); 707 | $this->assertSame($expected, $result); 708 | 709 | // 710 | $this->AmazonS3->amazonHeaders = array( 711 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 712 | 'x-amz-acl' => 'public-read', 713 | ); 714 | $this->AmazonS3->sortLexicographically(); 715 | $result = $this->AmazonS3->amazonHeaders; 716 | $expected = array( 717 | 'x-amz-acl' => 'public-read', 718 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 719 | ); 720 | $this->assertSame($expected, $result); 721 | } 722 | 723 | /** 724 | * testAddAmazonHeadersToRequest 725 | * 726 | * @return void 727 | * @author Rob Mcvey 728 | **/ 729 | public function testAddAmazonHeadersToRequest() { 730 | // Request 731 | $request = array( 732 | 'method' => 'PUT', 733 | 'uri' => array( 734 | 'host' => 'bucket.s3.amazonaws.com', 735 | 'scheme' => 'https', 736 | 'path' => 'some/dir/in/the/bucket/dots.csv' 737 | ), 738 | 'header' => array( 739 | 'Accept' => '*/*', 740 | 'User-Agent' => 'CakePHP', 741 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 742 | 'Authorization' => 'AWS foo:mCE9RQ8UJYRItzike6XZFd7XjcI=', 743 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 744 | 'Content-Type' => 'text/plain', 745 | 'Content-Length' => 3 746 | ), 747 | 'body' => '...' 748 | ); 749 | $this->AmazonS3->amazonHeaders = array( 750 | 'x-amz-acl' => 'public-read', 751 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 752 | ); 753 | $result = $this->AmazonS3->addAmazonHeadersToRequest($request); 754 | // Request 755 | $expected = array( 756 | 'method' => 'PUT', 757 | 'uri' => array( 758 | 'host' => 'bucket.s3.amazonaws.com', 759 | 'scheme' => 'https', 760 | 'path' => 'some/dir/in/the/bucket/dots.csv' 761 | ), 762 | 'header' => array( 763 | 'Accept' => '*/*', 764 | 'User-Agent' => 'CakePHP', 765 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 766 | 'Authorization' => 'AWS foo:mCE9RQ8UJYRItzike6XZFd7XjcI=', 767 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 768 | 'Content-Type' => 'text/plain', 769 | 'Content-Length' => 3, 770 | 'x-amz-acl' => 'public-read', 771 | 'X-Amz-Meta-ReviewedBy' => 'john.doe@yahoo.biz', 772 | ), 773 | 'body' => '...' 774 | ); 775 | $this->assertEqual($expected , $result); 776 | } 777 | 778 | /** 779 | * testAddAmazonHeadersToRequestNone 780 | * 781 | * @return void 782 | * @author Rob Mcvey 783 | **/ 784 | public function testAddAmazonHeadersToRequestNone() { 785 | // Request 786 | $request = array( 787 | 'method' => 'PUT', 788 | 'uri' => array( 789 | 'host' => 'bucket.s3.amazonaws.com', 790 | 'scheme' => 'https', 791 | 'path' => 'some/dir/in/the/bucket/dots.csv' 792 | ), 793 | 'header' => array( 794 | 'Accept' => '*/*', 795 | 'User-Agent' => 'CakePHP', 796 | 'Date' => 'Mon, 23 Sep 2013 08:46:05 GMT', 797 | 'Authorization' => 'AWS foo:mCE9RQ8UJYRItzike6XZFd7XjcI=', 798 | 'Content-MD5' => 'L0O0L9gz0ed0IKja50GQAA==', 799 | 'Content-Type' => 'text/plain', 800 | 'Content-Length' => 3 801 | ), 802 | 'body' => '...' 803 | ); 804 | $result = $this->AmazonS3->addAmazonHeadersToRequest($request); 805 | $this->assertEqual($request , $result); 806 | } 807 | 808 | /** 809 | * testGetContentMd5 810 | * 811 | * @return void 812 | * @author Rob Mcvey 813 | **/ 814 | public function testGetContentMd5() { 815 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'csv_iso8859.csv'; 816 | $this->AmazonS3->localPath = $file_path; 817 | $result = $this->AmazonS3->getContentMd5(); 818 | $this->assertEqual('ywdK3sOES1D0A+NyHoVKeA==', $result); 819 | // Image 820 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'avatars' . DS . 'copify.png'; 821 | $this->AmazonS3->localPath = $file_path; 822 | $result = $this->AmazonS3->getContentMd5(); 823 | $this->assertEqual('aUIOL+kLNYqj1ZPXnf8+yw==', $result); 824 | } 825 | 826 | /** 827 | * testGetLocalFileInfo 828 | * 829 | * @return void 830 | * @author Rob Mcvey 831 | **/ 832 | public function testGetLocalFileInfo() { 833 | // CSV 834 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'csv_iso8859.csv'; 835 | $this->AmazonS3->localPath = $file_path; 836 | $result = $this->AmazonS3->getLocalFileInfo(); 837 | $this->assertEqual('text/plain' , $result['mime']); 838 | $this->assertEqual(1114 , $result['filesize']); 839 | // Image 840 | $file_path = APP . 'Plugin' . DS . 'AmazonS3' . DS . 'Test' . DS . 'test_app' . DS . 'webroot' . DS . 'files' . DS . 'avatars' . DS . 'copify.png'; 841 | $this->AmazonS3->localPath = $file_path; 842 | $result = $this->AmazonS3->getLocalFileInfo(); 843 | $this->assertEqual('image/png' , $result['mime']); 844 | $this->assertEqual(48319 , $result['filesize']); 845 | } 846 | 847 | /** 848 | * testPublicUrl 849 | * 850 | * @return void 851 | * @author Rob Mcvey 852 | **/ 853 | public function testPublicUrl() { 854 | $this->assertEqual('http://bucket.s3.amazonaws.com/lolcat.jpg' , $this->AmazonS3->publicUrl('lolcat.jpg')); 855 | $this->assertEqual('http://bucket.s3.amazonaws.com/lolcat.jpg' , $this->AmazonS3->publicUrl('/lolcat.jpg')); 856 | $this->assertEqual('http://bucket.s3.amazonaws.com/foo/lolcat.jpg' , $this->AmazonS3->publicUrl('foo/lolcat.jpg')); 857 | $this->assertEqual('http://bucket.s3.amazonaws.com/foo/lolcat.jpg' , $this->AmazonS3->publicUrl('/foo/lolcat.jpg')); 858 | } 859 | 860 | /** 861 | * testSetDate 862 | * 863 | * @return void 864 | * @author Rob Mcvey 865 | **/ 866 | public function testSetDate() { 867 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 868 | $this->assertEqual('Sun, 22 Sep 2013 14:43:04 GMT', $this->AmazonS3->date); 869 | } 870 | 871 | /** 872 | * testCheckRemoteDir 873 | * 874 | * @return void 875 | * @author Rob Mcvey 876 | **/ 877 | public function testCheckRemoteDir() { 878 | $this->AmazonS3->file = 'foo.jpg'; 879 | $this->AmazonS3->checkRemoteDir('/flip/flop/bing/bing/toss/'); 880 | $this->assertEqual('flip/flop/bing/bing/toss/foo.jpg' , $this->AmazonS3->file); 881 | } 882 | 883 | /** 884 | * testSetDate 885 | * 886 | * @return void 887 | * @author Rob Mcvey 888 | **/ 889 | public function testSignature() { 890 | $this->AmazonS3->setDate('Sun, 22 Sep 2013 14:43:04 GMT'); 891 | $stringToSign = "PUT" . "\n"; 892 | $stringToSign .= "aUIOL+kLNYqj1ZPXnf8+yw==" . "\n"; 893 | $stringToSign .= "image/png" . "\n"; 894 | $stringToSign .= "Sun, 22 Sep 2013 14:43:04 GMT"; 895 | $stringToSign .= "\n"; 896 | $stringToSign .= "x-amz-acl:public-read" . "\n"; 897 | $stringToSign .= "x-amz-meta-reviewedby:john.doe@yahoo.biz" . "\n"; 898 | $stringToSign .= "/bucket/copify.png"; 899 | $signature = $this->AmazonS3->signature($stringToSign); 900 | $this->assertEqual('gbcL98O77pVLUSdcSIz4RCAots4=', $signature); 901 | 902 | $this->AmazonS3 = new AmazonS3(array('bang' , 'fizz', 'lulz')); 903 | $signature = $this->AmazonS3->signature($stringToSign); 904 | $this->assertEqual('dF2swNTRWEs9LiMxdxiVeWPwCR0=', $signature); 905 | } 906 | 907 | } 908 | -------------------------------------------------------------------------------- /Test/test_app/webroot/files/avatars/copify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robmcvey/cakephp-amazon-s3/3a580036b4bf4db18c07b8019c50f67002adcab3/Test/test_app/webroot/files/avatars/copify.png -------------------------------------------------------------------------------- /Test/test_app/webroot/files/csv_iso8859.csv: -------------------------------------------------------------------------------- 1 | "name","words","brief","type","quality","category" 2 | "http://www.corsets4u.co.uk/value-corsets/black-and-white-ruffle-burlesque-corset.html",100,"These corsets are in our ?fashion corset? range, so people usually buy them for fun and dressing up, they?re attracted to the competitive price and the quick delivery time. 3 | 4 | I ? utf-8 and thing's with ""proper"" quotes, yeah?","web page ","standard","lingerie " 5 | "http://www.corsets4u.co.uk/value-corsets/pink-and-black-criss-cross-lace-corset.html",100,"These corsets are in our ?fashion corset? range, so people usually buy them for fun and dressing up, they?re attracted to the competitive price and the quick delivery time. 6 | 7 | I ? utf-8 and thing's with ""proper"" quotes, yeah?","web page ","standard","lingerie " 8 | "http://www.corsets4u.co.uk/value-corsets/white-and-pink-ruffle-burlesque-corset.html",100,"These corsets are in our ?fashion corset? range, so people usually buy them for fun and dressing up, they?re attracted to the competitive price and the quick delivery time. 9 | 10 | I ? utf-8 and thing's with ""proper"" quotes, yeah?","web page ","standard","lingerie " 11 | -------------------------------------------------------------------------------- /Test/test_app/webroot/files/dots.csv: -------------------------------------------------------------------------------- 1 | ... --------------------------------------------------------------------------------