├── .gitignore ├── LICENSE.md ├── README.md ├── Source └── Suin │ ├── .phpmake │ └── FTPClient │ ├── FTPClient.php │ ├── FTPClientInterface.php │ ├── ObservableInterface.php │ ├── ObserverInterface.php │ └── StdOutObserver.php ├── Tests ├── Bootstrap.php ├── Coverage │ └── .gitkeep ├── FTPClient │ ├── .phpmake │ └── FTPClientTest.php ├── FTPConfig.sample.php ├── FTPMessageObserver.php ├── TestCase.php └── phpunit.xml └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | test.php 4 | Socket.php 5 | /Tests/Coverage 6 | /Tests/FTPConfig.php -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FTP Client Library for PHP 2 | 3 | ## Features 4 | 5 | * Works without the ftp extension. 6 | * Minimum and simple. 7 | * Unit tested. 8 | 9 | ## Requirements 10 | 11 | * PHP 5.2.0 or later 12 | 13 | ## Install 14 | 15 | Just copy ```Source/Suin``` to vendors directory in your project. 16 | 17 | ## How to Use 18 | 19 | ``` 20 | login('suin', 'password') === false ) 26 | { 27 | echo 'Cannot login!'; 28 | } 29 | 30 | if ( $client->upload('foo.php', 'foo.php', Suin_FTPClient_FTPClient::MODE_BINARY) === false ) 31 | { 32 | echo 'Failed to upload!'; 33 | } 34 | 35 | $client->disconnect(); 36 | } 37 | catch ( Exception $e ) 38 | { 39 | echo $e; 40 | } 41 | ``` 42 | 43 | More detail, please see ```Suin_FTPClient_FTPClientInterface```. 44 | 45 | ## Observer for debugging 46 | 47 | For logging the TCP messages, you can assing an observer object to FTPClient object. 48 | The observer object must implement ```Suin_FTPClient_ObserverInterface```. 49 | 50 | ``` 51 | '.$request; 57 | } 58 | 59 | public function updateWithResponse($message, $code) 60 | { 61 | echo 'GET < '.$message; 62 | } 63 | } 64 | 65 | $myObserver = new MyObserver(); 66 | $client = new Suin_FTPClient_FTPClient('127.0.0.1'); 67 | $client->setObserver($myObserver); 68 | ``` 69 | 70 | ## Testing 71 | 72 | * Needs PHPUnit 3.6 73 | * Needs PHP 5.3 or later 74 | 75 | ### Prepare for test 76 | 77 | ``` 78 | cd Tests 79 | cp FTPConfig.sample.php FTPConfig.php 80 | ``` 81 | 82 | And then, edit FTPConfig.php! 83 | 84 | ### How to run test 85 | 86 | ``` 87 | cd Tests 88 | phpunit 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Source/Suin/.phpmake: -------------------------------------------------------------------------------- 1 | namespace = 2 | namespaceSeparator = _ 3 | classDocComment = Off 4 | -------------------------------------------------------------------------------- /Source/Suin/FTPClient/FTPClient.php: -------------------------------------------------------------------------------- 1 | connection = fsockopen($host, $port, $errorCode, $errorMessage); 28 | 29 | if ( is_resource($this->connection) === false ) 30 | { 31 | throw new RuntimeException($errorMessage, $errorCode); 32 | } 33 | 34 | $this->transferMode = $transferMode; 35 | 36 | if ( in_array($this->transferMode, array(self::TRANSFER_MODE_PASSIVE)) === false ) 37 | { 38 | // TODO >> support active mode. 39 | throw new InvalidArgumentException('Transfer mode is invalid.'); 40 | } 41 | 42 | stream_set_blocking($this->connection, true); 43 | stream_set_timeout($this->connection, $this->timeout); 44 | 45 | $response = $this->_getResponse(); 46 | 47 | if ( $response['code'] !== 220 ) 48 | { 49 | throw new RuntimeException('Failed to connect to the FTP Server.'); 50 | } 51 | } 52 | 53 | /** 54 | * Login to the server. 55 | * @param string $username 56 | * @param string $password 57 | * @return bool If success return TRUE, fail return FALSE. 58 | */ 59 | public function login($username, $password) 60 | { 61 | $response = $this->_request(sprintf('USER %s', $username)); 62 | 63 | if ( $response['code'] !== 331 ) 64 | { 65 | return false; 66 | } 67 | 68 | $response = $this->_request(sprintf('PASS %s', $password)); 69 | 70 | if ( $response['code'] !== 230 ) 71 | { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | /** 79 | * Return the system name. 80 | * @return string|bool If error returns FALSE 81 | */ 82 | public function getSystem() 83 | { 84 | if ( $this->system === null ) 85 | { 86 | $this->system = $this->_getSystem(); 87 | } 88 | 89 | return $this->system; 90 | } 91 | 92 | /** 93 | * Return the features. 94 | * @return array|bool If error returns FALSE 95 | */ 96 | public function getFeatures() 97 | { 98 | if ( $this->features === null ) 99 | { 100 | $this->features = $this->_getFeatures(); 101 | } 102 | 103 | return $this->features; 104 | } 105 | 106 | /** 107 | * Close the connection. 108 | * @return void 109 | */ 110 | public function disconnect() 111 | { 112 | $this->_request('QUIT'); 113 | $this->connection = null; 114 | } 115 | 116 | /** 117 | * Return the current directory name. 118 | * @return string|bool If error, returns FALSE. 119 | */ 120 | public function getCurrentDirectory() 121 | { 122 | $response = $this->_request('PWD'); 123 | 124 | if ( $response['code'] !== 257 ) 125 | { 126 | return false; 127 | } 128 | 129 | $from = strpos($response['message'], '"') + 1; 130 | $to = strrpos($response['message'], '"') - $from; 131 | $currentDirectory = substr($response['message'], $from, $to); 132 | return $currentDirectory; 133 | } 134 | 135 | /** 136 | * Change the current directory on a FTP server. 137 | * @param string $directory 138 | * @return bool If success return TRUE, fail return FALSE. 139 | */ 140 | public function changeDirectory($directory) 141 | { 142 | $response = $this->_request(sprintf('CWD %s', $directory)); 143 | return ( $response['code'] === 250 ); 144 | } 145 | 146 | /** 147 | * Remove a directory. 148 | * @param string $directory 149 | * @return bool If success return TRUE, fail return FALSE. 150 | */ 151 | public function removeDirectory($directory) 152 | { 153 | $response = $this->_request(sprintf('RMD %s', $directory)); 154 | return ( $response['code'] === 250 ); 155 | } 156 | 157 | /** 158 | * Create a directory. 159 | * @param string $directory 160 | * @return bool If success return TRUE, fail return FALSE. 161 | */ 162 | public function createDirectory($directory) 163 | { 164 | $response = $this->_request(sprintf('MKD %s', $directory)); 165 | return ( $response['code'] === 257 ); 166 | } 167 | 168 | /** 169 | * Rename a file or a directory on the FTP server. 170 | * @param string $oldName 171 | * @param string $newName 172 | * @return bool If success return TRUE, fail return FALSE. 173 | */ 174 | public function rename($oldName, $newName) 175 | { 176 | $response = $this->_request(sprintf('RNFR %s', $oldName)); 177 | 178 | if ( $response['code'] !== 350 ) 179 | { 180 | return false; 181 | } 182 | 183 | $response = $this->_request(sprintf('RNTO %s', $newName)); 184 | 185 | if ( $response['code'] !== 250 ) 186 | { 187 | return false; 188 | } 189 | 190 | return true; 191 | } 192 | 193 | /** 194 | * Delete a file on the FTP server. 195 | * @param string $filename 196 | * @return bool If success return TRUE, fail return FALSE. 197 | */ 198 | public function removeFile($filename) 199 | { 200 | $response = $this->_request(sprintf('DELE %s', $filename)); 201 | return ( $response['code'] === 250 ); 202 | } 203 | 204 | /** 205 | * Set permissions on a file via FTP. 206 | * @param string $filename 207 | * @param int $mode The new permissions, given as an octal value. 208 | * @return bool If success return TRUE, fail return FALSE. 209 | * @throws InvalidArgumentException 210 | */ 211 | public function setPermission($filename, $mode) 212 | { 213 | if ( is_integer($mode) === false or $mode < 0 or 0777 < $mode ) 214 | { 215 | throw new InvalidArgumentException(sprintf('Invalid permission "%o" was given.', $mode)); 216 | } 217 | 218 | $response = $this->_request(sprintf('SITE CHMOD %o %s', $mode, $filename)); 219 | return ( $response['code'] === 200 ); 220 | } 221 | 222 | /** 223 | * Return a list of files in the given directory. 224 | * @param string $directory 225 | * @return array|bool If error, returns FALSE. 226 | */ 227 | public function getList($directory) 228 | { 229 | $dataConnection = $this->_openPassiveDataConnection(); 230 | 231 | if ( $dataConnection === false ) 232 | { 233 | return false; 234 | } 235 | 236 | $response = $this->_request(sprintf('NLST %s', $directory)); 237 | 238 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 239 | { 240 | return false; 241 | } 242 | 243 | $list = ''; 244 | 245 | while ( feof($dataConnection) === false ) 246 | { 247 | $list .= fread($dataConnection, 1024); 248 | } 249 | 250 | $list = trim($list); 251 | $list = preg_split("/[\n\r]+/", $list); 252 | 253 | return $list; 254 | } 255 | 256 | 257 | 258 | /** 259 | * Return a raw list of files in the given directory. 260 | * @param string $directory 261 | * @return array|bool If error, returns FALSE. 262 | */ 263 | public function getRawList($directory) 264 | { 265 | $dataConnection = $this->_openPassiveDataConnection(); 266 | 267 | if ( $dataConnection === false ) 268 | { 269 | return false; 270 | } 271 | 272 | $response = $this->_request(sprintf('LIST -a %s', $directory)); 273 | 274 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 275 | { 276 | return false; 277 | } 278 | 279 | $list = ''; 280 | 281 | while ( feof($dataConnection) === false ) 282 | { 283 | $list .= fread($dataConnection, 1024); 284 | } 285 | 286 | $list = trim($list); 287 | $list = preg_split("/[\n\r]+/", $list); 288 | 289 | return $list; 290 | } 291 | 292 | /** 293 | * Return the size of the given file. 294 | * @abstract 295 | * @param string $filename 296 | * @return int|bool If failed to get file size, returns FALSE 297 | * @note Not all servers support this feature! 298 | */ 299 | public function getFileSize($filename) 300 | { 301 | if ( $this->_supports('SIZE') === false ) 302 | { 303 | return false; 304 | } 305 | 306 | $response = $this->_request(sprintf('SIZE %s', $filename)); 307 | 308 | if ( $response['code'] !== 213 ) 309 | { 310 | return false; 311 | } 312 | 313 | if ( !preg_match('/^[0-9]{3} (?P[0-9]+)$/', trim($response['message']), $matches) ) 314 | { 315 | return false; 316 | } 317 | 318 | return intval($matches['size']); 319 | } 320 | 321 | /** 322 | * Return the last modified time of the given file. 323 | * @param string $filename 324 | * @return int|bool Returns the last modified time as a Unix timestamp on success, or FALSE on error. 325 | * @note Not all servers support this feature! 326 | */ 327 | public function getModifiedDateTime($filename) 328 | { 329 | if ( $this->_supports('MDTM') === false ) 330 | { 331 | return false; 332 | } 333 | 334 | $response = $this->_request(sprintf('MDTM %s', $filename)); 335 | 336 | if ( $response['code'] !== 213 ) 337 | { 338 | return false; 339 | } 340 | 341 | if ( !preg_match('/^[0-9]{3} (?P[0-9]{14})$/', trim($response['message']), $matches) ) 342 | { 343 | return false; 344 | } 345 | 346 | return strtotime($matches['datetime'].' UTC'); 347 | } 348 | 349 | /** 350 | * Download a file from the FTP server. 351 | * @param string $remoteFilename 352 | * @param string $localFilename 353 | * @param int $mode self::MODE_ASCII or self::MODE_BINARY 354 | * @return bool If success return TRUE, fail return FALSE. 355 | * @throws InvalidArgumentException 356 | * @throws RuntimeException 357 | */ 358 | public function download($remoteFilename, $localFilename, $mode=2) 359 | { 360 | $modes = array( 361 | self::MODE_ASCII => 'A', 362 | self::MODE_BINARY => 'I', 363 | ); 364 | 365 | if ( array_key_exists($mode, $modes) === false ) 366 | { 367 | throw new InvalidArgumentException(sprintf('Invalid mode "%s" was given', $mode)); 368 | } 369 | 370 | /* 371 | * WHY USE 'wb' HERE? 372 | * As fopen() function modifies line break character like LF, CR and CRLF depending on SAPI, 373 | * we use 'b' here in order to receive data as plain. 374 | * @see http://www.php.net/manual/en/function.fopen.php 375 | */ 376 | $localFilePointer = fopen($localFilename, 'wb'); 377 | 378 | if ( is_resource($localFilePointer) === false ) 379 | { 380 | throw new RuntimeException(sprintf('Failed to open local file "%s"', $localFilename)); 381 | } 382 | 383 | $response = $this->_request(sprintf('TYPE %s', $modes[$mode])); 384 | 385 | if ( $response['code'] !== 200 ) 386 | { 387 | return false; 388 | } 389 | 390 | $dataConnection = $this->_openPassiveDataConnection(); 391 | 392 | if ( $dataConnection === false ) 393 | { 394 | return false; 395 | } 396 | 397 | $response = $this->_request(sprintf('RETR %s', $remoteFilename)); 398 | 399 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 400 | { 401 | return false; 402 | } 403 | 404 | while ( feof($dataConnection) === false ) 405 | { 406 | fwrite($localFilePointer, fread($dataConnection, 10240), 10240); 407 | } 408 | 409 | 410 | return true; 411 | } 412 | 413 | /** 414 | * Download a file from the FTP server. 415 | * @param string $remoteFilename 416 | * @param int $mode self::MODE_ASCII or self::MODE_BINARY 417 | * @return string If success return file content, fail return FALSE. 418 | * @throws InvalidArgumentException 419 | * @throws RuntimeException 420 | */ 421 | public function downloadString($remoteFilename, $mode=2) 422 | { 423 | $modes = array( 424 | self::MODE_ASCII => 'A', 425 | self::MODE_BINARY => 'I', 426 | ); 427 | 428 | if ( array_key_exists($mode, $modes) === false ) 429 | { 430 | throw new InvalidArgumentException(sprintf('Invalid mode "%s" was given', $mode)); 431 | } 432 | 433 | $response = $this->_request(sprintf('TYPE %s', $modes[$mode])); 434 | 435 | if ( $response['code'] !== 200 ) 436 | { 437 | return false; 438 | } 439 | 440 | $dataConnection = $this->_openPassiveDataConnection(); 441 | 442 | if ( $dataConnection === false ) 443 | { 444 | return false; 445 | } 446 | 447 | $response = $this->_request(sprintf('RETR %s', $remoteFilename)); 448 | 449 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 450 | { 451 | return false; 452 | } 453 | 454 | $str = ''; 455 | while ( feof($dataConnection) === false ) 456 | { 457 | $str .= fread($dataConnection, 10240); 458 | } 459 | 460 | return $str; 461 | } 462 | 463 | /** 464 | * Upload a file to the FTP server. 465 | * @param string $localFilename 466 | * @param string $remoteFilename 467 | * @param int $mode self::MODE_ASCII or self::MODE_BINARY 468 | * @return bool If success return TRUE, fail return FALSE. 469 | * @throws InvalidArgumentException 470 | * @throws RuntimeException 471 | */ 472 | public function upload($localFilename, $remoteFilename, $mode=2) 473 | { 474 | $modes = array( 475 | self::MODE_ASCII => 'A', 476 | self::MODE_BINARY => 'I', 477 | ); 478 | 479 | if ( array_key_exists($mode, $modes) === false ) 480 | { 481 | throw new InvalidArgumentException(sprintf('Invalid mode "%s" was given', $mode)); 482 | } 483 | 484 | /* 485 | * WHY USE 'rb' HERE? 486 | * As fopen() function modifies line break character like LF, CR and CRLF depending on SAPI, 487 | * we use 'b' here in order to receive data as plain. 488 | * @see http://www.php.net/manual/en/function.fopen.php 489 | */ 490 | $localFilePointer = fopen($localFilename, 'rb'); 491 | 492 | if ( is_resource($localFilePointer) === false ) 493 | { 494 | throw new RuntimeException(sprintf('Failed to open local file "%s"', $localFilename)); 495 | } 496 | 497 | $response = $this->_request(sprintf('TYPE %s', $modes[$mode])); 498 | 499 | if ( $response['code'] !== 200 ) 500 | { 501 | return false; 502 | } 503 | 504 | $dataConnection = $this->_openPassiveDataConnection(); 505 | 506 | if ( $dataConnection === false ) 507 | { 508 | return false; 509 | } 510 | 511 | $response = $this->_request(sprintf('STOR %s', $remoteFilename)); 512 | 513 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 514 | { 515 | return false; 516 | } 517 | 518 | while ( feof($localFilePointer) === false ) 519 | { 520 | fwrite($dataConnection, fread($localFilePointer, 10240), 10240); 521 | } 522 | 523 | return true; 524 | } 525 | 526 | /** 527 | * Upload a file to the FTP server. 528 | * @param string $content 529 | * @param string $remoteFilename 530 | * @param int $mode self::MODE_ASCII or self::MODE_BINARY 531 | * @return bool If success return TRUE, fail return FALSE. 532 | * @throws InvalidArgumentException 533 | * @throws RuntimeException 534 | */ 535 | public function uploadString($content, $remoteFilename, $mode=2) 536 | { 537 | $modes = array( 538 | self::MODE_ASCII => 'A', 539 | self::MODE_BINARY => 'I', 540 | ); 541 | 542 | if ( array_key_exists($mode, $modes) === false ) 543 | { 544 | throw new InvalidArgumentException(sprintf('Invalid mode "%s" was given', $mode)); 545 | } 546 | 547 | $response = $this->_request(sprintf('TYPE %s', $modes[$mode])); 548 | 549 | if ( $response['code'] !== 200 ) 550 | { 551 | return false; 552 | } 553 | 554 | $dataConnection = $this->_openPassiveDataConnection(); 555 | 556 | if ( $dataConnection === false ) 557 | { 558 | return false; 559 | } 560 | 561 | $response = $this->_request(sprintf('STOR %s', $remoteFilename)); 562 | 563 | if ( $response['code'] !== 150 and $response['code'] !== 125 ) 564 | { 565 | return false; 566 | } 567 | 568 | fwrite($dataConnection, $content); 569 | 570 | return true; 571 | } 572 | 573 | /** 574 | * Send a raw command. 575 | * @param string $command 576 | * @return array 577 | */ 578 | public function raw($command) 579 | { 580 | return $this->_request($command); 581 | } 582 | 583 | /** 584 | * Set an observer. 585 | * @param Suin_FTPClient_ObserverInterface $observer 586 | */ 587 | public function setObserver(Suin_FTPClient_ObserverInterface $observer) 588 | { 589 | $this->observer = $observer; 590 | } 591 | 592 | /** 593 | * Open new passive data connection. 594 | * @return resource|bool 595 | */ 596 | protected function _openPassiveDataConnection() 597 | { 598 | $response = $this->_request('PASV'); 599 | 600 | if ( $response['code'] !== 227 ) 601 | { 602 | return false; 603 | } 604 | 605 | $serverInfo = $this->_parsePassiveServerInfo($response['message']); 606 | 607 | if ( $serverInfo === false ) 608 | { 609 | return false; 610 | } 611 | 612 | $dataConnection = fsockopen($serverInfo['host'], $serverInfo['port'], $errorNumber, $errorString, $this->timeout); 613 | 614 | if ( is_resource($dataConnection) === false ) 615 | { 616 | return false; 617 | } 618 | 619 | stream_set_blocking($dataConnection, true); 620 | stream_set_timeout($dataConnection, $this->timeout); 621 | 622 | return $dataConnection; 623 | } 624 | 625 | /** 626 | * Parse a message and return the host and port. 627 | * @param $message 628 | * @return array|bool 629 | */ 630 | protected function _parsePassiveServerInfo($message) 631 | { 632 | if ( !preg_match('/\((?P[0-9,]+),(?P[0-9]+),(?P[0-9]+)\)/', $message, $matches) ) 633 | { 634 | return false; 635 | } 636 | 637 | $host = strtr($matches['host'], ',', '.'); 638 | $port = ( $matches['port1'] * 256 ) + $matches['port2']; // low bit * 256 + high bit 639 | 640 | return array( 641 | 'host' => $host, 642 | 'port' => $port, 643 | ); 644 | } 645 | 646 | /** 647 | * Send a request. 648 | * @param string $request 649 | * @return array 650 | */ 651 | protected function _request($request) 652 | { 653 | $request = $request."\r\n"; 654 | 655 | if ( is_object($this->observer) === true ) 656 | { 657 | $this->observer->updateWithRequest($request); 658 | } 659 | 660 | fputs($this->connection, $request); 661 | return $this->_getResponse(); 662 | } 663 | 664 | /** 665 | * Fetch the response. 666 | * @return array 667 | */ 668 | protected function _getResponse() 669 | { 670 | $response = array( 671 | 'code' => 0, 672 | 'message' => '', 673 | ); 674 | 675 | while ( true ) 676 | { 677 | $line = fgets($this->connection, 8129); 678 | $response['message'] .= $line; 679 | 680 | if ( preg_match('/^[0-9]{3} /', $line) ) 681 | { 682 | break; 683 | } 684 | } 685 | 686 | $response['code'] = intval(substr(ltrim($response['message']), 0, 3)); 687 | 688 | if ( is_object($this->observer) === true ) 689 | { 690 | $this->observer->updateWithResponse($response['message'], $response['code']); 691 | } 692 | 693 | return $response; 694 | } 695 | 696 | /** 697 | * Return the system name. 698 | * @return string|bool If error returns FALSE 699 | */ 700 | protected function _getSystem() 701 | { 702 | $response = $this->_request('SYST'); 703 | 704 | if ( $response['code'] !== 215 ) 705 | { 706 | return false; 707 | } 708 | 709 | $tokens = explode(' ', $response['message']); 710 | return $tokens[1]; 711 | } 712 | 713 | /** 714 | * Return the features. 715 | * @return array|bool If error returns FALSE 716 | */ 717 | protected function _getFeatures() 718 | { 719 | $response = $this->_request('FEAT'); 720 | 721 | if ( $response['code'] !== 211 ) 722 | { 723 | return false; 724 | } 725 | 726 | $lines = explode("\n", $response['message']); 727 | $lines = array_map('trim', $lines); 728 | $lines = array_filter($lines); 729 | 730 | if ( count($lines) < 2 ) 731 | { 732 | return false; 733 | } 734 | 735 | $lines = array_slice($lines, 1, count($lines) - 2); 736 | 737 | $features = array(); 738 | 739 | foreach ( $lines as $line ) 740 | { 741 | $tokens = explode(' ', $line); 742 | $feature =$tokens[0]; 743 | $features[$feature] = $line; 744 | } 745 | 746 | return $features; 747 | } 748 | 749 | /** 750 | * Determine if a specific command supported. 751 | * @param string $command 752 | * @return bool 753 | */ 754 | protected function _supports($command) 755 | { 756 | $features = $this->getFeatures(); 757 | return array_key_exists($command, $features); 758 | } 759 | } 760 | -------------------------------------------------------------------------------- /Source/Suin/FTPClient/FTPClientInterface.php: -------------------------------------------------------------------------------- 1 | '.$request; 13 | } 14 | 15 | /** 16 | * @abstract 17 | * @param string $message 18 | * @param int $code 19 | * @return void 20 | */ 21 | public function updateWithResponse($message, $code) 22 | { 23 | echo 'GET < '.$message; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Bootstrap.php: -------------------------------------------------------------------------------- 1 | setObserver($observer); 23 | 24 | $success = $client->login(FTP_CLIENT_TEST_FTP_USER, FTP_CLIENT_TEST_FTP_PASS); 25 | 26 | if ( $success === false ) 27 | { 28 | throw new RuntimeException("Failed to login to the FTP Server.\n".$observer->getMessagesAsString()); 29 | } 30 | 31 | $success = $client->changeDirectory(FTP_CLIENT_TEST_REMOTE_SANDBOX_DIR); 32 | 33 | if ( $success === false ) 34 | { 35 | throw new RuntimeException("Failed to change the current directory.\n".$observer->getMessagesAsString()); 36 | } 37 | 38 | return $client; 39 | } 40 | 41 | public function testRealServerAvailable() 42 | { 43 | if ( defined('FTP_CLIENT_TEST_FTP_HOST') === false ) 44 | { 45 | $this->markTestSkipped("This test was skipped. Please set up FTP config at FTPConfig.php"); 46 | } 47 | 48 | if ( is_resource(@fsockopen(FTP_CLIENT_TEST_FTP_HOST, FTP_CLIENT_TEST_FTP_PORT)) === false ) 49 | { 50 | $this->markTestSkipped("This test was skipped. Please confirm if FTP server is running."); 51 | } 52 | } 53 | 54 | /** 55 | * @expectedException RuntimeException 56 | */ 57 | public function test__construct() 58 | { 59 | $this->disableErrorReporting(); 60 | // Case: fsockopen() returns NOT resource. 61 | new Suin_FTPClient_FTPClient('foo', 0); 62 | } 63 | 64 | /** 65 | * @depends testRealServerAvailable 66 | * @expectedException InvalidArgumentException 67 | * @expectedExceptionMessage Transfer mode is invalid. 68 | */ 69 | public function test__construct_With_invalid_transfer_mode() 70 | { 71 | new Suin_FTPClient_FTPClient(FTP_CLIENT_TEST_FTP_HOST, FTP_CLIENT_TEST_FTP_PORT, 'invalid'); 72 | } 73 | 74 | /** 75 | * @depends testRealServerAvailable 76 | * @expectedException RuntimeException 77 | * @expectedExceptionMessage Failed to connect to the FTP Server. 78 | */ 79 | public function test__construct_With_unexpected_response_code() 80 | { 81 | $client = $this 82 | ->getMockBuilder('Suin_FTPClient_FTPClient') 83 | ->disableOriginalConstructor() 84 | ->setMethods(array('_getResponse')) 85 | ->getMock(); 86 | $client 87 | ->expects($this->once()) 88 | ->method('_getResponse') 89 | ->will($this->returnValue(0)); 90 | $client->__construct(FTP_CLIENT_TEST_FTP_HOST, FTP_CLIENT_TEST_FTP_PORT); 91 | } 92 | 93 | public function testLogin() 94 | { 95 | // Case: Response code is not 331 96 | $client = $this 97 | ->getMockBuilder('Suin_FTPClient_FTPClient') 98 | ->disableOriginalConstructor() 99 | ->setMethods(array('_request')) 100 | ->getMock(); 101 | $client 102 | ->expects($this->once()) 103 | ->method('_request') 104 | ->with('USER foo') 105 | ->will($this->returnValue(array('code' => 0))); 106 | 107 | $actual = $client->login('foo', 'pass'); 108 | $this->assertFalse($actual); 109 | } 110 | 111 | public function testLogin_With_invalid_password() 112 | { 113 | // Case: Response code is not 230 114 | $client = $this 115 | ->getMockBuilder('Suin_FTPClient_FTPClient') 116 | ->disableOriginalConstructor() 117 | ->setMethods(array('_request')) 118 | ->getMock(); 119 | $client 120 | ->expects($this->at(0)) 121 | ->method('_request') 122 | ->with('USER foo') 123 | ->will($this->returnValue(array('code' => 331))); 124 | $client 125 | ->expects($this->at(1)) 126 | ->method('_request') 127 | ->with('PASS pass') 128 | ->will($this->returnValue(array('code' => 0))); 129 | $actual = $client->login('foo', 'pass'); 130 | $this->assertFalse($actual); 131 | } 132 | 133 | public function testLogin_Success_to_login() 134 | { 135 | $client = $this 136 | ->getMockBuilder('Suin_FTPClient_FTPClient') 137 | ->disableOriginalConstructor() 138 | ->setMethods(array('_request')) 139 | ->getMock(); 140 | $client 141 | ->expects($this->at(0)) 142 | ->method('_request') 143 | ->with('USER foo') 144 | ->will($this->returnValue(array('code' => 331))); 145 | $client 146 | ->expects($this->at(1)) 147 | ->method('_request') 148 | ->with('PASS pass') 149 | ->will($this->returnValue(array('code' => 230))); 150 | $actual = $client->login('foo', 'pass'); 151 | $this->assertTrue($actual); 152 | } 153 | 154 | /** 155 | * @depends testRealServerAvailable 156 | */ 157 | public function testLogin_With_real_server() 158 | { 159 | $client = $this->getNewFTPClient(false); 160 | $actual = $client->login(FTP_CLIENT_TEST_FTP_USER, FTP_CLIENT_TEST_FTP_PASS); 161 | $this->assertTrue($actual); 162 | } 163 | 164 | public function testGetSystem() 165 | { 166 | $client = $this 167 | ->getMockBuilder('Suin_FTPClient_FTPClient') 168 | ->disableOriginalConstructor() 169 | ->setMethods(array('_getSystem')) 170 | ->getMock(); 171 | $client 172 | ->expects($this->once()) 173 | ->method('_getSystem') 174 | ->will($this->returnValue('Unix')); 175 | 176 | $actual = $client->getSystem(); 177 | $this->assertSame('Unix', $actual); 178 | 179 | // Test cache 180 | $actual = $client->getSystem(); 181 | $this->assertSame('Unix', $actual); 182 | } 183 | 184 | public function testGetFeatures() 185 | { 186 | $client = $this 187 | ->getMockBuilder('Suin_FTPClient_FTPClient') 188 | ->disableOriginalConstructor() 189 | ->setMethods(array('_getFeatures')) 190 | ->getMock(); 191 | $client 192 | ->expects($this->once()) 193 | ->method('_getFeatures') 194 | ->will($this->returnValue('FEAT')); 195 | 196 | $actual = $client->getFeatures(); 197 | $this->assertSame('FEAT', $actual); 198 | 199 | // Test cache 200 | $actual = $client->getFeatures(); 201 | $this->assertSame('FEAT', $actual); 202 | } 203 | 204 | public function testDisconnect() 205 | { 206 | $client = $this 207 | ->getMockBuilder('Suin_FTPClient_FTPClient') 208 | ->disableOriginalConstructor() 209 | ->setMethods(array('_request')) 210 | ->getMock(); 211 | $client 212 | ->expects($this->once()) 213 | ->method('_request') 214 | ->with('QUIT'); 215 | 216 | $reflectionClass = new ReflectionClass($client); 217 | $reflectionProperty = $reflectionClass->getProperty('connection'); 218 | $reflectionProperty->setAccessible(true); 219 | $reflectionProperty->setValue($client, 'this is not Null.'); 220 | 221 | $client->disconnect(); 222 | $this->assertAttributeSame(null, 'connection', $client); 223 | } 224 | 225 | public function testGetCurrentDirectory() 226 | { 227 | // Case: Response code is not 257 228 | $client = $this 229 | ->getMockBuilder('Suin_FTPClient_FTPClient') 230 | ->disableOriginalConstructor() 231 | ->setMethods(array('_request')) 232 | ->getMock(); 233 | $client 234 | ->expects($this->once()) 235 | ->method('_request') 236 | ->with('PWD') 237 | ->will($this->returnValue(array('code' => 0))); 238 | $actual = $client->getCurrentDirectory(); 239 | $this->assertFalse($actual); 240 | } 241 | 242 | public function testGetCurrentDirectory_Success() 243 | { 244 | $client = $this 245 | ->getMockBuilder('Suin_FTPClient_FTPClient') 246 | ->disableOriginalConstructor() 247 | ->setMethods(array('_request')) 248 | ->getMock(); 249 | $client 250 | ->expects($this->once()) 251 | ->method('_request') 252 | ->with('PWD') 253 | ->will($this->returnValue(array( 254 | 'code' => 257, 255 | 'message' => '257 "/Users/suin" is the current directory.', 256 | ))); 257 | $actual = $client->getCurrentDirectory(); 258 | $this->assertSame('/Users/suin', $actual); 259 | } 260 | 261 | /** 262 | * @depends testRealServerAvailable 263 | */ 264 | public function testGetCurrentDirectory_With_real_server() 265 | { 266 | $client = $this->getNewFTPClient(false); 267 | $client->login(FTP_CLIENT_TEST_FTP_USER, FTP_CLIENT_TEST_FTP_PASS); 268 | $actual = $client->getCurrentDirectory(); 269 | $this->assertTrue(is_string($actual)); 270 | } 271 | 272 | public function testChangeDirectory() 273 | { 274 | // Case: Response code is not 250 275 | $client = $this 276 | ->getMockBuilder('Suin_FTPClient_FTPClient') 277 | ->disableOriginalConstructor() 278 | ->setMethods(array('_request')) 279 | ->getMock(); 280 | $client 281 | ->expects($this->once()) 282 | ->method('_request') 283 | ->with('CWD /foo/bar') 284 | ->will($this->returnValue(array('code' => 0))); 285 | $actual = $client->changeDirectory('/foo/bar'); 286 | $this->assertFalse($actual); 287 | } 288 | 289 | public function testChangeDirectory_Success() 290 | { 291 | $client = $this 292 | ->getMockBuilder('Suin_FTPClient_FTPClient') 293 | ->disableOriginalConstructor() 294 | ->setMethods(array('_request')) 295 | ->getMock(); 296 | $client 297 | ->expects($this->once()) 298 | ->method('_request') 299 | ->with('CWD /foo/bar') 300 | ->will($this->returnValue(array('code' => 250))); 301 | $actual = $client->changeDirectory('/foo/bar'); 302 | $this->assertTrue($actual); 303 | } 304 | 305 | /** 306 | * @depends testRealServerAvailable 307 | */ 308 | public function testChangeDirectory_With_real_server() 309 | { 310 | $client = $this->getNewFTPClient(false); 311 | $client->login(FTP_CLIENT_TEST_FTP_USER, FTP_CLIENT_TEST_FTP_PASS); 312 | $actual = $client->changeDirectory(FTP_CLIENT_TEST_REMOTE_SANDBOX_DIR); 313 | $this->assertTrue($actual); 314 | } 315 | 316 | public function testRemoveDirectory() 317 | { 318 | // Case: Response code is not 250 319 | $client = $this 320 | ->getMockBuilder('Suin_FTPClient_FTPClient') 321 | ->disableOriginalConstructor() 322 | ->setMethods(array('_request')) 323 | ->getMock(); 324 | $client 325 | ->expects($this->once()) 326 | ->method('_request') 327 | ->with('RMD foo') 328 | ->will($this->returnValue(array('code' => 0))); 329 | $actual = $client->removeDirectory('foo'); 330 | $this->assertFalse($actual); 331 | } 332 | 333 | public function testRemoveDirectory_With_success() 334 | { 335 | $client = $this 336 | ->getMockBuilder('Suin_FTPClient_FTPClient') 337 | ->disableOriginalConstructor() 338 | ->setMethods(array('_request')) 339 | ->getMock(); 340 | $client 341 | ->expects($this->once()) 342 | ->method('_request') 343 | ->with('RMD foo') 344 | ->will($this->returnValue(array('code' => 250))); 345 | $actual = $client->removeDirectory('foo'); 346 | $this->assertTrue($actual); 347 | } 348 | 349 | /** 350 | * @depends testRealServerAvailable 351 | */ 352 | public function testRemoveDirectory_With_real_server() 353 | { 354 | $dirname = __FUNCTION__; 355 | $client = $this->getNewFTPClient(); 356 | $client->createDirectory($dirname); 357 | $actual = $client->removeDirectory($dirname); 358 | $this->assertTrue($actual); 359 | } 360 | 361 | public function testCreateDirectory() 362 | { 363 | // Case: Response code is not 257 364 | $client = $this 365 | ->getMockBuilder('Suin_FTPClient_FTPClient') 366 | ->disableOriginalConstructor() 367 | ->setMethods(array('_request')) 368 | ->getMock(); 369 | $client 370 | ->expects($this->once()) 371 | ->method('_request') 372 | ->with('MKD foo') 373 | ->will($this->returnValue(array('code' => 0))); 374 | $actual = $client->createDirectory('foo'); 375 | $this->assertFalse($actual); 376 | } 377 | 378 | public function testCreateDirectory_Success() 379 | { 380 | $client = $this 381 | ->getMockBuilder('Suin_FTPClient_FTPClient') 382 | ->disableOriginalConstructor() 383 | ->setMethods(array('_request')) 384 | ->getMock(); 385 | $client 386 | ->expects($this->once()) 387 | ->method('_request') 388 | ->with('MKD foo') 389 | ->will($this->returnValue(array('code' => 257))); 390 | $actual = $client->createDirectory('foo'); 391 | $this->assertTrue($actual); 392 | } 393 | 394 | /** 395 | * @depends testRealServerAvailable 396 | */ 397 | public function testCreateDirectory_With_real_server() 398 | { 399 | $client = $this->getNewFTPClient(); 400 | $actual = $client->createDirectory(__FUNCTION__); 401 | $client->removeDirectory(__FUNCTION__); 402 | $this->assertTrue($actual); 403 | } 404 | 405 | public function testRename() 406 | { 407 | // Return code for RNFR is not 350 408 | $client = $this 409 | ->getMockBuilder('Suin_FTPClient_FTPClient') 410 | ->disableOriginalConstructor() 411 | ->setMethods(array('_request')) 412 | ->getMock(); 413 | $client 414 | ->expects($this->once()) 415 | ->method('_request') 416 | ->with('RNFR foo') 417 | ->will($this->returnValue(array('code' => 0))); 418 | $actual = $client->rename('foo', 'bar'); 419 | $this->assertFalse($actual); 420 | } 421 | 422 | public function testRename_With_RNTO_fail() 423 | { 424 | // Return code for RNTO is not 250 425 | $client = $this 426 | ->getMockBuilder('Suin_FTPClient_FTPClient') 427 | ->disableOriginalConstructor() 428 | ->setMethods(array('_request')) 429 | ->getMock(); 430 | $client 431 | ->expects($this->at(0)) 432 | ->method('_request') 433 | ->with('RNFR foo') 434 | ->will($this->returnValue(array('code' => 350))); 435 | $client 436 | ->expects($this->at(1)) 437 | ->method('_request') 438 | ->with('RNTO bar') 439 | ->will($this->returnValue(array('code' => 0))); 440 | $actual = $client->rename('foo', 'bar'); 441 | $this->assertFalse($actual); 442 | } 443 | 444 | public function testRename_Success() 445 | { 446 | $client = $this 447 | ->getMockBuilder('Suin_FTPClient_FTPClient') 448 | ->disableOriginalConstructor() 449 | ->setMethods(array('_request')) 450 | ->getMock(); 451 | $client 452 | ->expects($this->at(0)) 453 | ->method('_request') 454 | ->with('RNFR foo') 455 | ->will($this->returnValue(array('code' => 350))); 456 | $client 457 | ->expects($this->at(1)) 458 | ->method('_request') 459 | ->with('RNTO bar') 460 | ->will($this->returnValue(array('code' => 250))); 461 | $actual = $client->rename('foo', 'bar'); 462 | $this->assertTrue($actual); 463 | } 464 | 465 | /** 466 | * @depends testRealServerAvailable 467 | */ 468 | public function testRename_With_real_server() 469 | { 470 | $oldName = __FUNCTION__.'1'; 471 | $newName = __FUNCTION__.'2'; 472 | 473 | $client = $this->getNewFTPClient(); 474 | $client->createDirectory($oldName); 475 | $actual = $client->rename($oldName, $newName); 476 | $client->removeDirectory($newName); 477 | $this->assertTrue($actual); 478 | } 479 | 480 | public function testRemoveFile() 481 | { 482 | // Return code for RNFR is not 250 483 | $client = $this 484 | ->getMockBuilder('Suin_FTPClient_FTPClient') 485 | ->disableOriginalConstructor() 486 | ->setMethods(array('_request')) 487 | ->getMock(); 488 | $client 489 | ->expects($this->once()) 490 | ->method('_request') 491 | ->with('DELE foo') 492 | ->will($this->returnValue(array('code' => 0))); 493 | $actual = $client->removeFile('foo'); 494 | $this->assertFalse($actual); 495 | } 496 | 497 | public function testRemoveFile_Success() 498 | { 499 | $client = $this 500 | ->getMockBuilder('Suin_FTPClient_FTPClient') 501 | ->disableOriginalConstructor() 502 | ->setMethods(array('_request')) 503 | ->getMock(); 504 | $client 505 | ->expects($this->once()) 506 | ->method('_request') 507 | ->with('DELE foo') 508 | ->will($this->returnValue(array('code' => 250))); 509 | $actual = $client->removeFile('foo'); 510 | $this->assertTrue($actual); 511 | } 512 | 513 | /** 514 | * @param $mode 515 | * @dataProvider data4testSetPermission 516 | * @expectedException InvalidArgumentException 517 | */ 518 | public function testSetPermission($mode) 519 | { 520 | // Case: Invalid mode 521 | $client = $this 522 | ->getMockBuilder('Suin_FTPClient_FTPClient') 523 | ->disableOriginalConstructor() 524 | ->setMethods(array('_request')) 525 | ->getMock(); 526 | $client 527 | ->expects($this->never()) 528 | ->method('_request'); 529 | $client->setPermission('foo', $mode); 530 | } 531 | 532 | public static function data4testSetPermission() 533 | { 534 | return array( 535 | array('1'), // Not int 536 | array(-1), // invalid range 537 | array(0777 + 1), //invalid range 538 | ); 539 | } 540 | 541 | public function testSetPermission_With_unexpected_response_code() 542 | { 543 | // Case: Invalid mode 544 | $client = $this 545 | ->getMockBuilder('Suin_FTPClient_FTPClient') 546 | ->disableOriginalConstructor() 547 | ->setMethods(array('_request')) 548 | ->getMock(); 549 | $client 550 | ->expects($this->once()) 551 | ->method('_request') 552 | ->with('SITE CHMOD 777 foo') 553 | ->will($this->returnValue(array('code' => 0))); 554 | $actual = $client->setPermission('foo', 0777); 555 | $this->assertFalse($actual); 556 | } 557 | 558 | public function testSetPermission_Success() 559 | { 560 | $client = $this 561 | ->getMockBuilder('Suin_FTPClient_FTPClient') 562 | ->disableOriginalConstructor() 563 | ->setMethods(array('_request')) 564 | ->getMock(); 565 | $client 566 | ->expects($this->once()) 567 | ->method('_request') 568 | ->with('SITE CHMOD 777 foo') 569 | ->will($this->returnValue(array('code' => 200))); 570 | $actual = $client->setPermission('foo', 0777); 571 | $this->assertTrue($actual); 572 | } 573 | 574 | /** 575 | * @depends testRealServerAvailable 576 | */ 577 | public function testSetPermission_With_real_server() 578 | { 579 | // TODO 580 | } 581 | 582 | public function testGetList() 583 | { 584 | // Case: Fails to open data connection. 585 | $client = $this 586 | ->getMockBuilder('Suin_FTPClient_FTPClient') 587 | ->disableOriginalConstructor() 588 | ->setMethods(array('_openPassiveDataConnection', '_request')) 589 | ->getMock(); 590 | $client 591 | ->expects($this->once()) 592 | ->method('_openPassiveDataConnection') 593 | ->will($this->returnValue(false)); 594 | $client 595 | ->expects($this->never()) 596 | ->method('_request'); 597 | 598 | $actual = $client->getList('dir'); 599 | $this->assertFalse($actual); 600 | } 601 | 602 | public function testGetList_With_NLST_response_code_not_150() 603 | { 604 | $client = $this 605 | ->getMockBuilder('Suin_FTPClient_FTPClient') 606 | ->disableOriginalConstructor() 607 | ->setMethods(array('_openPassiveDataConnection', '_request')) 608 | ->getMock(); 609 | $client 610 | ->expects($this->once()) 611 | ->method('_openPassiveDataConnection') 612 | ->will($this->returnValue(true)); 613 | $client 614 | ->expects($this->once()) 615 | ->method('_request') 616 | ->with('NLST dir') 617 | ->will($this->returnValue(array('code' => 0))); 618 | $actual = $client->getList('dir'); 619 | $this->assertFalse($actual); 620 | } 621 | 622 | public function testGetList_Success() 623 | { 624 | $resource = fopen('php://memory', 'rw'); 625 | fwrite($resource, "index.php\r\nfoo.gif\nbar.png\r"); 626 | fseek($resource, 0); 627 | 628 | $expect = array('index.php', 'foo.gif', 'bar.png'); 629 | 630 | $client = $this 631 | ->getMockBuilder('Suin_FTPClient_FTPClient') 632 | ->disableOriginalConstructor() 633 | ->setMethods(array('_openPassiveDataConnection', '_request')) 634 | ->getMock(); 635 | $client 636 | ->expects($this->once()) 637 | ->method('_openPassiveDataConnection') 638 | ->will($this->returnValue($resource)); 639 | $client 640 | ->expects($this->once()) 641 | ->method('_request') 642 | ->with('NLST dir') 643 | ->will($this->returnValue(array('code' => 150))); 644 | 645 | $actual = $client->getList('dir'); 646 | 647 | $this->assertSame($expect, $actual); 648 | } 649 | 650 | /** 651 | * @depends testRealServerAvailable 652 | */ 653 | public function testGetList_With_real_server() 654 | { 655 | // TODO 656 | } 657 | 658 | public function testGetFileSize() 659 | { 660 | // Case: Not supporting SIZE command. 661 | $client = $this 662 | ->getMockBuilder('Suin_FTPClient_FTPClient') 663 | ->disableOriginalConstructor() 664 | ->setMethods(array('_supports', '_request')) 665 | ->getMock(); 666 | $client 667 | ->expects($this->once()) 668 | ->method('_supports') 669 | ->with('SIZE') 670 | ->will($this->returnValue(false)); 671 | $client 672 | ->expects($this->never()) 673 | ->method('_request'); 674 | 675 | $actual = $client->getFileSize('filename'); 676 | $this->assertFalse($actual); 677 | } 678 | 679 | public function testGetFileSize_Response_code_is_not_213() 680 | { 681 | $client = $this 682 | ->getMockBuilder('Suin_FTPClient_FTPClient') 683 | ->disableOriginalConstructor() 684 | ->setMethods(array('_supports', '_request')) 685 | ->getMock(); 686 | $client 687 | ->expects($this->once()) 688 | ->method('_supports') 689 | ->with('SIZE') 690 | ->will($this->returnValue(true)); 691 | $client 692 | ->expects($this->once()) 693 | ->method('_request') 694 | ->with('SIZE filename') 695 | ->will($this->returnValue(array('code' => 0))); 696 | 697 | $actual = $client->getFileSize('filename'); 698 | $this->assertFalse($actual); 699 | } 700 | 701 | public function testGetFileSize_Response_message_is_not_valid_format() 702 | { 703 | $responseMessage = '213 this is invalid format'; 704 | $client = $this 705 | ->getMockBuilder('Suin_FTPClient_FTPClient') 706 | ->disableOriginalConstructor() 707 | ->setMethods(array('_supports', '_request')) 708 | ->getMock(); 709 | $client 710 | ->expects($this->once()) 711 | ->method('_supports') 712 | ->with('SIZE') 713 | ->will($this->returnValue(true)); 714 | $client 715 | ->expects($this->once()) 716 | ->method('_request') 717 | ->with('SIZE filename') 718 | ->will($this->returnValue(array('code' => 213, 'message' => $responseMessage))); 719 | 720 | $actual = $client->getFileSize('filename'); 721 | $this->assertFalse($actual); 722 | } 723 | 724 | public function testGetFileSize_Success() 725 | { 726 | $responseMessage = '213 1024'; 727 | $client = $this 728 | ->getMockBuilder('Suin_FTPClient_FTPClient') 729 | ->disableOriginalConstructor() 730 | ->setMethods(array('_supports', '_request')) 731 | ->getMock(); 732 | $client 733 | ->expects($this->once()) 734 | ->method('_supports') 735 | ->with('SIZE') 736 | ->will($this->returnValue(true)); 737 | $client 738 | ->expects($this->once()) 739 | ->method('_request') 740 | ->with('SIZE filename') 741 | ->will($this->returnValue(array('code' => 213, 'message' => $responseMessage))); 742 | 743 | $actual = $client->getFileSize('filename'); 744 | $this->assertSame(1024, $actual); 745 | } 746 | 747 | /** 748 | * @depends testRealServerAvailable 749 | */ 750 | public function testGetFileSize_With_real_server() 751 | { 752 | // TODO 753 | } 754 | 755 | public function testGetModifiedDateTime() 756 | { 757 | // Case: Not supporting MDTM command. 758 | $client = $this 759 | ->getMockBuilder('Suin_FTPClient_FTPClient') 760 | ->disableOriginalConstructor() 761 | ->setMethods(array('_supports', '_request')) 762 | ->getMock(); 763 | $client 764 | ->expects($this->once()) 765 | ->method('_supports') 766 | ->with('MDTM') 767 | ->will($this->returnValue(false)); 768 | $client 769 | ->expects($this->never()) 770 | ->method('_request'); 771 | 772 | $actual = $client->getModifiedDateTime('filename'); 773 | $this->assertFalse($actual); 774 | } 775 | 776 | public function testGetModifiedDateTime_Response_code_is_not_213() 777 | { 778 | $client = $this 779 | ->getMockBuilder('Suin_FTPClient_FTPClient') 780 | ->disableOriginalConstructor() 781 | ->setMethods(array('_supports', '_request')) 782 | ->getMock(); 783 | $client 784 | ->expects($this->once()) 785 | ->method('_supports') 786 | ->with('MDTM') 787 | ->will($this->returnValue(true)); 788 | $client 789 | ->expects($this->once()) 790 | ->method('_request') 791 | ->with('MDTM filename') 792 | ->will($this->returnValue(array('code' => 0))); 793 | 794 | $actual = $client->getModifiedDateTime('filename'); 795 | $this->assertFalse($actual); 796 | } 797 | 798 | public function testGetModifiedDateTime_Response_message_is_not_valid_format() 799 | { 800 | $responseMessage = '213 this is invalid format'; 801 | $client = $this 802 | ->getMockBuilder('Suin_FTPClient_FTPClient') 803 | ->disableOriginalConstructor() 804 | ->setMethods(array('_supports', '_request')) 805 | ->getMock(); 806 | $client 807 | ->expects($this->once()) 808 | ->method('_supports') 809 | ->with('MDTM') 810 | ->will($this->returnValue(true)); 811 | $client 812 | ->expects($this->once()) 813 | ->method('_request') 814 | ->with('MDTM filename') 815 | ->will($this->returnValue(array('code' => 213, 'message' => $responseMessage))); 816 | 817 | $actual = $client->getModifiedDateTime('filename'); 818 | $this->assertFalse($actual); 819 | } 820 | 821 | public function testGetModifiedDateTime_Success() 822 | { 823 | $utc = new DateTime('now', new DateTimeZone('UTC')); 824 | $responseMessage = '213 '.$utc->format('YmdHis'); 825 | $client = $this 826 | ->getMockBuilder('Suin_FTPClient_FTPClient') 827 | ->disableOriginalConstructor() 828 | ->setMethods(array('_supports', '_request')) 829 | ->getMock(); 830 | $client 831 | ->expects($this->once()) 832 | ->method('_supports') 833 | ->with('MDTM') 834 | ->will($this->returnValue(true)); 835 | $client 836 | ->expects($this->once()) 837 | ->method('_request') 838 | ->with('MDTM filename') 839 | ->will($this->returnValue(array('code' => 213, 'message' => $responseMessage))); 840 | 841 | $actual = $client->getModifiedDateTime('filename'); 842 | $this->assertSame($utc->getTimestamp(), $actual); 843 | } 844 | 845 | /** 846 | * @depends testRealServerAvailable 847 | */ 848 | public function testGetModifiedDateTime_With_real_server() 849 | { 850 | // TODO 851 | } 852 | 853 | /** 854 | * @expectedException InvalidArgumentException 855 | * @expectedExceptionMessage Invalid mode "invalid mode" was given 856 | */ 857 | public function testDownload() 858 | { 859 | $client = $this 860 | ->getMockBuilder('Suin_FTPClient_FTPClient') 861 | ->disableOriginalConstructor() 862 | ->setMethods(array('_openPassiveDataConnection', '_request')) 863 | ->getMock(); 864 | $client 865 | ->expects($this->never()) 866 | ->method('_openPassiveDataConnection'); 867 | $client 868 | ->expects($this->never()) 869 | ->method('_request'); 870 | 871 | $client->download('remote file', 'local file', 'invalid mode'); 872 | } 873 | 874 | /** 875 | * @expectedException RuntimeException 876 | * @expectedExceptionMessage Failed to open local file "" 877 | */ 878 | public function testDownload_Fails_to_open_local_file() 879 | { 880 | $this->disableErrorReporting(); 881 | 882 | $client = $this 883 | ->getMockBuilder('Suin_FTPClient_FTPClient') 884 | ->disableOriginalConstructor() 885 | ->setMethods(array('_openPassiveDataConnection', '_request')) 886 | ->getMock(); 887 | $client 888 | ->expects($this->never()) 889 | ->method('_openPassiveDataConnection'); 890 | $client 891 | ->expects($this->never()) 892 | ->method('_request'); 893 | 894 | $client->download('remote file', null, Suin_FTPClient_FTPClient::MODE_ASCII); 895 | } 896 | 897 | public function testDownload_Ascii_mode() 898 | { 899 | $client = $this 900 | ->getMockBuilder('Suin_FTPClient_FTPClient') 901 | ->disableOriginalConstructor() 902 | ->setMethods(array('_openPassiveDataConnection', '_request')) 903 | ->getMock(); 904 | $client 905 | ->expects($this->never()) 906 | ->method('_openPassiveDataConnection'); 907 | $client 908 | ->expects($this->once()) 909 | ->method('_request') 910 | ->with('TYPE A') 911 | ->will($this->returnValue(array('code' => 0))); 912 | 913 | $client->download('remote file', 'php://stdout', Suin_FTPClient_FTPClient::MODE_ASCII); 914 | } 915 | 916 | public function testDownload_Binary_mode() 917 | { 918 | $client = $this 919 | ->getMockBuilder('Suin_FTPClient_FTPClient') 920 | ->disableOriginalConstructor() 921 | ->setMethods(array('_openPassiveDataConnection', '_request')) 922 | ->getMock(); 923 | $client 924 | ->expects($this->never()) 925 | ->method('_openPassiveDataConnection'); 926 | $client 927 | ->expects($this->once()) 928 | ->method('_request') 929 | ->with('TYPE I') 930 | ->will($this->returnValue(array('code' => 0))); 931 | 932 | $client->download('remote file', 'php://stdout', Suin_FTPClient_FTPClient::MODE_BINARY); 933 | } 934 | 935 | public function testDownload_TYPE_not_200() 936 | { 937 | $client = $this 938 | ->getMockBuilder('Suin_FTPClient_FTPClient') 939 | ->disableOriginalConstructor() 940 | ->setMethods(array('_openPassiveDataConnection', '_request')) 941 | ->getMock(); 942 | $client 943 | ->expects($this->never()) 944 | ->method('_openPassiveDataConnection'); 945 | $client 946 | ->expects($this->once()) 947 | ->method('_request') 948 | ->with('TYPE I') 949 | ->will($this->returnValue(array('code' => 0))); 950 | 951 | $actual = $client->download('remote file', 'php://stdout', Suin_FTPClient_FTPClient::MODE_BINARY); 952 | $this->assertFalse($actual); 953 | } 954 | 955 | public function testDownload_Fails_to_open_data_connection() 956 | { 957 | $client = $this 958 | ->getMockBuilder('Suin_FTPClient_FTPClient') 959 | ->disableOriginalConstructor() 960 | ->setMethods(array('_openPassiveDataConnection', '_request')) 961 | ->getMock(); 962 | $client 963 | ->expects($this->once()) 964 | ->method('_request') 965 | ->with('TYPE I') 966 | ->will($this->returnValue(array('code' => 200))); 967 | $client 968 | ->expects($this->once()) 969 | ->method('_openPassiveDataConnection') 970 | ->will($this->returnValue(false)); 971 | 972 | $actual = $client->download('remote file', 'php://stdout', Suin_FTPClient_FTPClient::MODE_BINARY); 973 | $this->assertFalse($actual); 974 | } 975 | 976 | public function testDownload_RETR_not_150() 977 | { 978 | $client = $this 979 | ->getMockBuilder('Suin_FTPClient_FTPClient') 980 | ->disableOriginalConstructor() 981 | ->setMethods(array('_openPassiveDataConnection', '_request')) 982 | ->getMock(); 983 | $client 984 | ->expects($this->at(0)) 985 | ->method('_request') 986 | ->with('TYPE I') 987 | ->will($this->returnValue(array('code' => 200))); 988 | $client 989 | ->expects($this->at(1)) 990 | ->method('_openPassiveDataConnection') 991 | ->will($this->returnValue(true)); 992 | $client 993 | ->expects($this->at(2)) 994 | ->method('_request') 995 | ->with('RETR foo') 996 | ->will($this->returnValue(array('code' => 0))); 997 | 998 | $actual = $client->download('foo', 'php://stdout', Suin_FTPClient_FTPClient::MODE_BINARY); 999 | $this->assertFalse($actual); 1000 | } 1001 | 1002 | public function testDownload_Success() 1003 | { 1004 | $contents = 'data data data'; 1005 | 1006 | $dataConnection = tmpfile(); 1007 | fwrite($dataConnection, $contents); 1008 | fseek($dataConnection, 0); 1009 | 1010 | $localFile = tempnam(sys_get_temp_dir(), 'Tux'); 1011 | 1012 | $client = $this 1013 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1014 | ->disableOriginalConstructor() 1015 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1016 | ->getMock(); 1017 | $client 1018 | ->expects($this->at(0)) 1019 | ->method('_request') 1020 | ->with('TYPE I') 1021 | ->will($this->returnValue(array('code' => 200))); 1022 | $client 1023 | ->expects($this->at(1)) 1024 | ->method('_openPassiveDataConnection') 1025 | ->will($this->returnValue($dataConnection)); 1026 | $client 1027 | ->expects($this->at(2)) 1028 | ->method('_request') 1029 | ->with('RETR foo') 1030 | ->will($this->returnValue(array('code' => 150))); 1031 | 1032 | $actual = $client->download('foo', $localFile, Suin_FTPClient_FTPClient::MODE_BINARY); 1033 | $this->assertTrue($actual); 1034 | 1035 | $this->assertSame($contents, file_get_contents($localFile)); 1036 | } 1037 | 1038 | /** 1039 | * @depends testRealServerAvailable 1040 | */ 1041 | public function testDownload_With_real_server_ASCII() 1042 | { 1043 | // TODO 1044 | } 1045 | 1046 | /** 1047 | * @depends testRealServerAvailable 1048 | */ 1049 | public function testDownload_With_real_server_BINARY() 1050 | { 1051 | // TODO 1052 | } 1053 | 1054 | /** 1055 | * @expectedException InvalidArgumentException 1056 | * @expectedExceptionMessage Invalid mode "invalid mode" was given 1057 | */ 1058 | public function testUpload() 1059 | { 1060 | $client = $this 1061 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1062 | ->disableOriginalConstructor() 1063 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1064 | ->getMock(); 1065 | $client 1066 | ->expects($this->never()) 1067 | ->method('_openPassiveDataConnection'); 1068 | $client 1069 | ->expects($this->never()) 1070 | ->method('_request'); 1071 | 1072 | $client->upload('local file', 'remote file', 'invalid mode'); 1073 | } 1074 | 1075 | 1076 | /** 1077 | * @expectedException RuntimeException 1078 | * @expectedExceptionMessage Failed to open local file "" 1079 | */ 1080 | public function testUpload_Fails_to_open_local_file() 1081 | { 1082 | $this->disableErrorReporting(); 1083 | 1084 | $client = $this 1085 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1086 | ->disableOriginalConstructor() 1087 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1088 | ->getMock(); 1089 | $client 1090 | ->expects($this->never()) 1091 | ->method('_openPassiveDataConnection'); 1092 | $client 1093 | ->expects($this->never()) 1094 | ->method('_request'); 1095 | 1096 | $client->upload(null, 'remote file', Suin_FTPClient_FTPClient::MODE_ASCII); 1097 | } 1098 | 1099 | public function testUpload_Ascii_mode() 1100 | { 1101 | $client = $this 1102 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1103 | ->disableOriginalConstructor() 1104 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1105 | ->getMock(); 1106 | $client 1107 | ->expects($this->never()) 1108 | ->method('_openPassiveDataConnection'); 1109 | $client 1110 | ->expects($this->once()) 1111 | ->method('_request') 1112 | ->with('TYPE A') 1113 | ->will($this->returnValue(array('code' => 0))); 1114 | 1115 | $client->upload('php://memory', 'remote file name', Suin_FTPClient_FTPClient::MODE_ASCII); 1116 | } 1117 | 1118 | public function testUpload_Binary_mode() 1119 | { 1120 | $client = $this 1121 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1122 | ->disableOriginalConstructor() 1123 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1124 | ->getMock(); 1125 | $client 1126 | ->expects($this->never()) 1127 | ->method('_openPassiveDataConnection'); 1128 | $client 1129 | ->expects($this->once()) 1130 | ->method('_request') 1131 | ->with('TYPE I') 1132 | ->will($this->returnValue(array('code' => 0))); 1133 | 1134 | $client->upload('php://memory', 'remote file name', Suin_FTPClient_FTPClient::MODE_BINARY); 1135 | } 1136 | 1137 | public function testUpload_TYPE_not_200() 1138 | { 1139 | $client = $this 1140 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1141 | ->disableOriginalConstructor() 1142 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1143 | ->getMock(); 1144 | $client 1145 | ->expects($this->never()) 1146 | ->method('_openPassiveDataConnection'); 1147 | $client 1148 | ->expects($this->once()) 1149 | ->method('_request') 1150 | ->with('TYPE I') 1151 | ->will($this->returnValue(array('code' => 0))); 1152 | 1153 | $actual = $client->upload('php://memory', 'remote file name', Suin_FTPClient_FTPClient::MODE_BINARY); 1154 | $this->assertFalse($actual); 1155 | } 1156 | 1157 | public function testUpload_Fails_to_open_data_connection() 1158 | { 1159 | $client = $this 1160 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1161 | ->disableOriginalConstructor() 1162 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1163 | ->getMock(); 1164 | $client 1165 | ->expects($this->once()) 1166 | ->method('_request') 1167 | ->with('TYPE I') 1168 | ->will($this->returnValue(array('code' => 200))); 1169 | $client 1170 | ->expects($this->once()) 1171 | ->method('_openPassiveDataConnection') 1172 | ->will($this->returnValue(false)); 1173 | 1174 | $actual = $client->upload('php://memory', 'remote file name', Suin_FTPClient_FTPClient::MODE_BINARY); 1175 | $this->assertFalse($actual); 1176 | } 1177 | 1178 | public function testUpload_STOR_not_150() 1179 | { 1180 | $client = $this 1181 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1182 | ->disableOriginalConstructor() 1183 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1184 | ->getMock(); 1185 | $client 1186 | ->expects($this->at(0)) 1187 | ->method('_request') 1188 | ->with('TYPE I') 1189 | ->will($this->returnValue(array('code' => 200))); 1190 | $client 1191 | ->expects($this->at(1)) 1192 | ->method('_openPassiveDataConnection') 1193 | ->will($this->returnValue(true)); 1194 | $client 1195 | ->expects($this->at(2)) 1196 | ->method('_request') 1197 | ->with('STOR remote file name') 1198 | ->will($this->returnValue(array('code' => 0))); 1199 | 1200 | $actual = $client->upload('php://memory', 'remote file name', Suin_FTPClient_FTPClient::MODE_BINARY); 1201 | $this->assertFalse($actual); 1202 | } 1203 | 1204 | public function testUpload_Success() 1205 | { 1206 | $contents = 'data data data'; 1207 | $dataConnection = tmpfile(); 1208 | 1209 | $localFile = tempnam(sys_get_temp_dir(), 'Tux'); 1210 | file_put_contents($localFile, $contents); 1211 | 1212 | $client = $this 1213 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1214 | ->disableOriginalConstructor() 1215 | ->setMethods(array('_openPassiveDataConnection', '_request')) 1216 | ->getMock(); 1217 | $client 1218 | ->expects($this->at(0)) 1219 | ->method('_request') 1220 | ->with('TYPE I') 1221 | ->will($this->returnValue(array('code' => 200))); 1222 | $client 1223 | ->expects($this->at(1)) 1224 | ->method('_openPassiveDataConnection') 1225 | ->will($this->returnValue($dataConnection)); 1226 | $client 1227 | ->expects($this->at(2)) 1228 | ->method('_request') 1229 | ->with('STOR foo') 1230 | ->will($this->returnValue(array('code' => 150))); 1231 | 1232 | $actual = $client->upload($localFile, 'foo', Suin_FTPClient_FTPClient::MODE_BINARY); 1233 | $this->assertTrue($actual); 1234 | 1235 | fseek($dataConnection, 0); 1236 | 1237 | $this->assertSame($contents, fgets($dataConnection)); 1238 | } 1239 | 1240 | /** 1241 | * @depends testRealServerAvailable 1242 | */ 1243 | public function testUpload_With_real_server() 1244 | { 1245 | // TODO 1246 | } 1247 | 1248 | public function testSetObserver() 1249 | { 1250 | $client = $this->getNewFTPClient(false); 1251 | 1252 | $this->assertAttributeSame(null, 'observer', $client); 1253 | 1254 | $observer = new Suin_FTPClient_StdOutObserver(); 1255 | 1256 | $client->setObserver($observer); 1257 | 1258 | $this->assertAttributeSame($observer, 'observer', $client); 1259 | } 1260 | 1261 | public function test_openPassiveDataConnection() 1262 | { 1263 | // Response code is not 227 1264 | $client = $this 1265 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1266 | ->disableOriginalConstructor() 1267 | ->setMethods(array('_request', '_parsePassiveServerInfo')) 1268 | ->getMock(); 1269 | $client 1270 | ->expects($this->once()) 1271 | ->method('_request') 1272 | ->with('PASV') 1273 | ->will($this->returnValue(array('code' => 0))); 1274 | $client 1275 | ->expects($this->never()) 1276 | ->method('_parsePassiveServerInfo'); 1277 | 1278 | $reflectionClass = new ReflectionClass($client); 1279 | $method = $reflectionClass->getMethod('_openPassiveDataConnection'); 1280 | $method->setAccessible(true); 1281 | $actual = $method->invoke($client); 1282 | 1283 | $this->assertFalse($actual); 1284 | } 1285 | 1286 | public function test_openPassiveDataConnection_Fails_to_parse_response_message() 1287 | { 1288 | $responseMessage = 'the response message'; 1289 | 1290 | $client = $this 1291 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1292 | ->disableOriginalConstructor() 1293 | ->setMethods(array('_request', '_parsePassiveServerInfo')) 1294 | ->getMock(); 1295 | $client 1296 | ->expects($this->once()) 1297 | ->method('_request') 1298 | ->with('PASV') 1299 | ->will($this->returnValue(array('code' => 227, 'message' => $responseMessage))); 1300 | $client 1301 | ->expects($this->once()) 1302 | ->method('_parsePassiveServerInfo') 1303 | ->with($responseMessage) 1304 | ->will($this->returnValue(false)); 1305 | 1306 | $reflectionClass = new ReflectionClass($client); 1307 | $method = $reflectionClass->getMethod('_openPassiveDataConnection'); 1308 | $method->setAccessible(true); 1309 | $actual = $method->invoke($client); 1310 | 1311 | $this->assertFalse($actual); 1312 | } 1313 | 1314 | public function test_openPassiveDataConnection_Fails_to_open_socket() 1315 | { 1316 | $this->disableErrorReporting(); 1317 | 1318 | $client = $this 1319 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1320 | ->disableOriginalConstructor() 1321 | ->setMethods(array('_request', '_parsePassiveServerInfo')) 1322 | ->getMock(); 1323 | $client 1324 | ->expects($this->once()) 1325 | ->method('_request') 1326 | ->with('PASV') 1327 | ->will($this->returnValue(array('code' => 227, 'message' => 'response message'))); 1328 | $client 1329 | ->expects($this->once()) 1330 | ->method('_parsePassiveServerInfo') 1331 | ->will($this->returnValue(array('host' => null, 'port' => null))); 1332 | 1333 | $reflectionClass = new ReflectionClass($client); 1334 | $method = $reflectionClass->getMethod('_openPassiveDataConnection'); 1335 | $method->setAccessible(true); 1336 | $actual = $method->invoke($client); 1337 | 1338 | $this->assertFalse($actual); 1339 | } 1340 | 1341 | /** 1342 | * @depends testRealServerAvailable 1343 | */ 1344 | public function test_openPassiveDataConnection_With_real_server() 1345 | { 1346 | // TODO 1347 | } 1348 | 1349 | public function test_parsePassiveServerInfo() 1350 | { 1351 | // Fails to parse, as invalid format is given. 1352 | 1353 | $client = $this 1354 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1355 | ->disableOriginalConstructor() 1356 | ->getMock(); 1357 | 1358 | $reflectionClass = new ReflectionClass($client); 1359 | $method = $reflectionClass->getMethod('_parsePassiveServerInfo'); 1360 | $method->setAccessible(true); 1361 | $actual = $method->invoke($client, 'invalid format'); 1362 | $this->assertFalse($actual); 1363 | } 1364 | 1365 | public function test_parsePassiveServerInfo_Success() 1366 | { 1367 | $client = $this 1368 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1369 | ->disableOriginalConstructor() 1370 | ->getMock(); 1371 | 1372 | $reflectionClass = new ReflectionClass($client); 1373 | $method = $reflectionClass->getMethod('_parsePassiveServerInfo'); 1374 | $method->setAccessible(true); 1375 | $actual = $method->invoke($client, '227 Entering Passive Mode (127,0,0,1,192,3)'); 1376 | 1377 | $expect = array( 1378 | 'host' => '127.0.0.1', 1379 | 'port' => 192 * 256 + 3, 1380 | ); 1381 | $this->assertSame($expect, $actual); 1382 | } 1383 | 1384 | public function test_request() 1385 | { 1386 | $client = $this 1387 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1388 | ->disableOriginalConstructor() 1389 | ->setMethods(array('_getResponse')) 1390 | ->getMock(); 1391 | 1392 | $client 1393 | ->expects($this->once()) 1394 | ->method('_getResponse') 1395 | ->will($this->returnValue('the result of _getResponse()')); 1396 | 1397 | $connection = tmpfile(); 1398 | 1399 | $reflectionClass = new ReflectionClass($client); 1400 | $property = $reflectionClass->getProperty('connection'); 1401 | $property->setAccessible(true); 1402 | $property->setValue($client, $connection); 1403 | 1404 | $method = $reflectionClass->getMethod('_request'); 1405 | $method->setAccessible(true); 1406 | $actual = $method->invoke($client, 'REQUEST'); 1407 | 1408 | $this->assertSame('the result of _getResponse()', $actual); 1409 | 1410 | fseek($connection, 0); 1411 | $this->assertSame("REQUEST\r\n", fgets($connection)); 1412 | } 1413 | 1414 | public function test_request_With_observer() 1415 | { 1416 | $expectedRequest = "REQUEST\r\n"; 1417 | 1418 | $client = $this 1419 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1420 | ->disableOriginalConstructor() 1421 | ->setMethods(array('_getResponse')) 1422 | ->getMock(); 1423 | 1424 | $client 1425 | ->expects($this->once()) 1426 | ->method('_getResponse') 1427 | ->will($this->returnValue('the result of _getResponse()')); 1428 | 1429 | $connection = tmpfile(); 1430 | $observer = $this 1431 | ->getMockBuilder('stdclass') 1432 | ->setMethods(array('updateWithRequest')) 1433 | ->getMock(); 1434 | $observer 1435 | ->expects($this->once()) 1436 | ->method('updateWithRequest') 1437 | ->with($expectedRequest); 1438 | 1439 | $reflectionClass = new ReflectionClass($client); 1440 | $property = $reflectionClass->getProperty('connection'); 1441 | $property->setAccessible(true); 1442 | $property->setValue($client, $connection); 1443 | 1444 | $property = $reflectionClass->getProperty('observer'); 1445 | $property->setAccessible(true); 1446 | $property->setValue($client, $observer); 1447 | 1448 | $method = $reflectionClass->getMethod('_request'); 1449 | $method->setAccessible(true); 1450 | $method->invoke($client, 'REQUEST'); 1451 | } 1452 | 1453 | public function test_getResponse() 1454 | { 1455 | $response = "123 Message Message\r\n"; 1456 | 1457 | $connection = tmpfile(); 1458 | fwrite($connection, $response); 1459 | fseek($connection, 0); 1460 | 1461 | $client = $this 1462 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1463 | ->disableOriginalConstructor() 1464 | ->getMock(); 1465 | 1466 | $class = new ReflectionClass($client); 1467 | $property = $class->getProperty('connection'); 1468 | $property->setAccessible(true); 1469 | $property->setValue($client, $connection); 1470 | 1471 | $method = $class->getMethod('_getResponse'); 1472 | $method->setAccessible(true); 1473 | $actual = $method->invoke($client); 1474 | $expect = array( 1475 | 'code' => 123, 1476 | 'message' => $response, 1477 | ); 1478 | $this->assertSame($expect, $actual); 1479 | } 1480 | 1481 | public function test_getResponse_With_observer() 1482 | { 1483 | $response = '123 message message'; 1484 | $code = 123; 1485 | 1486 | $observer = $this 1487 | ->getMockBuilder('stdclass') 1488 | ->setMethods(array('updateWithResponse')) 1489 | ->getMock(); 1490 | $observer 1491 | ->expects($this->once()) 1492 | ->method('updateWithResponse') 1493 | ->with($response, $code); 1494 | 1495 | $connection = tmpfile(); 1496 | fwrite($connection, $response); 1497 | fseek($connection, 0); 1498 | 1499 | $client = $this 1500 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1501 | ->disableOriginalConstructor() 1502 | ->getMock(); 1503 | 1504 | $class = new ReflectionClass($client); 1505 | $property = $class->getProperty('connection'); 1506 | $property->setAccessible(true); 1507 | $property->setValue($client, $connection); 1508 | 1509 | $property = $class->getProperty('observer'); 1510 | $property->setAccessible(true); 1511 | $property->setValue($client, $observer); 1512 | 1513 | $method = $class->getMethod('_getResponse'); 1514 | $method->setAccessible(true); 1515 | $method->invoke($client); 1516 | } 1517 | 1518 | public function test_getResponse_With_multiline() 1519 | { 1520 | $response = "123-Message Message\r\nLINE 1\r\nLINE 2\r\n123 Message Message"; 1521 | 1522 | $connection = tmpfile(); 1523 | fwrite($connection, $response); 1524 | fseek($connection, 0); 1525 | 1526 | $client = $this 1527 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1528 | ->disableOriginalConstructor() 1529 | ->getMock(); 1530 | 1531 | $class = new ReflectionClass($client); 1532 | $property = $class->getProperty('connection'); 1533 | $property->setAccessible(true); 1534 | $property->setValue($client, $connection); 1535 | 1536 | $method = $class->getMethod('_getResponse'); 1537 | $method->setAccessible(true); 1538 | $actual = $method->invoke($client); 1539 | $expect = array( 1540 | 'code' => 123, 1541 | 'message' => $response, 1542 | ); 1543 | $this->assertSame($expect, $actual); 1544 | } 1545 | 1546 | public function test_getSystem() 1547 | { 1548 | // Case: code is not 215 1549 | $client = $this 1550 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1551 | ->disableOriginalConstructor() 1552 | ->setMethods(array('_request')) 1553 | ->getMock(); 1554 | $client 1555 | ->expects($this->once()) 1556 | ->method('_request') 1557 | ->with('SYST') 1558 | ->will($this->returnValue(array('code' => 0))); 1559 | 1560 | $class = new ReflectionClass($client); 1561 | $method = $class->getMethod('_getSystem'); 1562 | $method->setAccessible(true); 1563 | $actual = $method->invoke($client); 1564 | $this->assertFalse($actual); 1565 | } 1566 | 1567 | public function test_getSystem_Success() 1568 | { 1569 | $client = $this 1570 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1571 | ->disableOriginalConstructor() 1572 | ->setMethods(array('_request')) 1573 | ->getMock(); 1574 | $client 1575 | ->expects($this->once()) 1576 | ->method('_request') 1577 | ->with('SYST') 1578 | ->will($this->returnValue(array('code' => 215, 'message' => '215 Unix Foo Bar.'))); 1579 | 1580 | $class = new ReflectionClass($client); 1581 | $method = $class->getMethod('_getSystem'); 1582 | $method->setAccessible(true); 1583 | $actual = $method->invoke($client); 1584 | $this->assertSame('Unix', $actual); 1585 | } 1586 | 1587 | public function test_getSystem_With_real_server() 1588 | { 1589 | // TODO 1590 | } 1591 | 1592 | public function test_getFeatures() 1593 | { 1594 | $client = $this 1595 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1596 | ->disableOriginalConstructor() 1597 | ->setMethods(array('_request')) 1598 | ->getMock(); 1599 | $client 1600 | ->expects($this->once()) 1601 | ->method('_request') 1602 | ->with('FEAT') 1603 | ->will($this->returnValue(array('code' => 0))); 1604 | 1605 | $class = new ReflectionClass($client); 1606 | $method = $class->getMethod('_getFeatures'); 1607 | $method->setAccessible(true); 1608 | $actual = $method->invoke($client); 1609 | $this->assertFalse($actual); 1610 | } 1611 | 1612 | public function test_getFeatures_With_not_enough_response() 1613 | { 1614 | $client = $this 1615 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1616 | ->disableOriginalConstructor() 1617 | ->setMethods(array('_request')) 1618 | ->getMock(); 1619 | $client 1620 | ->expects($this->once()) 1621 | ->method('_request') 1622 | ->with('FEAT') 1623 | ->will($this->returnValue(array('code' => 211, 'message' => "line 1"))); 1624 | 1625 | $class = new ReflectionClass($client); 1626 | $method = $class->getMethod('_getFeatures'); 1627 | $method->setAccessible(true); 1628 | $actual = $method->invoke($client); 1629 | $this->assertFalse($actual); 1630 | } 1631 | 1632 | public function test_getFeatures_With_no_features() 1633 | { 1634 | $client = $this 1635 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1636 | ->disableOriginalConstructor() 1637 | ->setMethods(array('_request')) 1638 | ->getMock(); 1639 | $client 1640 | ->expects($this->once()) 1641 | ->method('_request') 1642 | ->with('FEAT') 1643 | ->will($this->returnValue(array('code' => 211, 'message' => "line 1\r\nline 2"))); 1644 | 1645 | $class = new ReflectionClass($client); 1646 | $method = $class->getMethod('_getFeatures'); 1647 | $method->setAccessible(true); 1648 | $actual = $method->invoke($client); 1649 | $this->assertSame(array(), $actual); 1650 | } 1651 | 1652 | public function test_getFeatures_Success() 1653 | { 1654 | $client = $this 1655 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1656 | ->disableOriginalConstructor() 1657 | ->setMethods(array('_request')) 1658 | ->getMock(); 1659 | $client 1660 | ->expects($this->once()) 1661 | ->method('_request') 1662 | ->with('FEAT') 1663 | ->will($this->returnValue(array('code' => 211, 'message' => "line 1\r\nFEAT1\r\nFEAT2\r\nFEAT3 foo bar\r\nlast line"))); 1664 | 1665 | $expect = array( 1666 | 'FEAT1' => 'FEAT1', 1667 | 'FEAT2' => 'FEAT2', 1668 | 'FEAT3' => 'FEAT3 foo bar', 1669 | ); 1670 | 1671 | $class = new ReflectionClass($client); 1672 | $method = $class->getMethod('_getFeatures'); 1673 | $method->setAccessible(true); 1674 | $actual = $method->invoke($client); 1675 | $this->assertSame($expect, $actual); 1676 | } 1677 | 1678 | /** 1679 | * @param $expect 1680 | * @param $command 1681 | * @param $features 1682 | * @dataProvider data4test_supports 1683 | */ 1684 | public function test_supports($expect, $command, $features) 1685 | { 1686 | $client = $this 1687 | ->getMockBuilder('Suin_FTPClient_FTPClient') 1688 | ->disableOriginalConstructor() 1689 | ->setMethods(array('getFeatures')) 1690 | ->getMock(); 1691 | $client 1692 | ->expects($this->once()) 1693 | ->method('getFeatures') 1694 | ->will($this->returnValue($features)); 1695 | 1696 | $class = new ReflectionClass($client); 1697 | $method = $class->getMethod('_supports'); 1698 | $method->setAccessible(true); 1699 | $actual = $method->invoke($client, $command); 1700 | $this->assertSame($expect, $actual); 1701 | } 1702 | 1703 | public static function data4test_supports() 1704 | { 1705 | return array( 1706 | // [expect, command, features] 1707 | array(false, 'FEAT1', array('FEAT2' => null, 'FEAT3' => null)), 1708 | array(true, 'FEAT1', array('FEAT1' => null, 'FEAT2' => null)), 1709 | ); 1710 | } 1711 | } 1712 | -------------------------------------------------------------------------------- /Tests/FTPConfig.sample.php: -------------------------------------------------------------------------------- 1 | messages[] = sprintf('PUT > %s', trim($request)); 14 | } 15 | 16 | /** 17 | * @param string $message 18 | * @param int $code 19 | * @return void 20 | */ 21 | public function updateWithResponse($message, $code) 22 | { 23 | $this->messages[] = sprintf('GET < %s', trim($message)); 24 | } 25 | 26 | public function getMessagesAsString() 27 | { 28 | return implode("\n", $this->messages); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/TestCase.php: -------------------------------------------------------------------------------- 1 | enableErrorReporting(); 18 | } 19 | 20 | $this->_tearDown(); 21 | } 22 | 23 | /** 24 | * Tears down the fixture, for example, close a network connection. 25 | * This method is called after a test is executed. 26 | */ 27 | protected function _tearDown() 28 | { 29 | } 30 | 31 | /** 32 | * Disable error reporting. 33 | */ 34 | public function disableErrorReporting() 35 | { 36 | /** 37 | * refs php - test the return value of a method that triggers an error with PHPUnit - Stack Overflow 38 | * http://stackoverflow.com/questions/1225776/test-the-return-value-of-a-method-that-triggers-an-error-with-phpunit 39 | */ 40 | static::$warningEnabledOrig = PHPUnit_Framework_Error_Warning::$enabled; 41 | static::$errorReportingOrig = error_reporting(); 42 | PHPUnit_Framework_Error_Warning::$enabled = false; 43 | error_reporting(0); 44 | static::$isDisabledErrorReporting = true; 45 | } 46 | 47 | /** 48 | * Enable error reporting. 49 | */ 50 | public function enableErrorReporting() 51 | { 52 | PHPUnit_Framework_Error_Warning::$enabled = static::$warningEnabledOrig; 53 | error_reporting(static::$errorReportingOrig); 54 | static::$isDisabledErrorReporting = false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | FTPClient 12 | 13 | 14 | 15 | 16 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ../Source 31 | 32 | 33 | ../Vendor 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "suin/php-ftp-client", 3 | "type": "library", 4 | "description": "FTP library for PHP 5.3 or later.", 5 | "keywords": ["ftp", "php"], 6 | "homepage": "https://github.com/suin/php-ftp-client", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Hidehito Nozawa aka Suin", 11 | "email": "suinyeze@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0" 16 | }, 17 | "autoload": { 18 | "psr-0": { 19 | "Suin\\FTPClient": "Source" 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------