1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
39: class Horde_Vfs_Ftp extends Horde_Vfs_Base
40: {
41: 42: 43: 44: 45:
46: protected $_params = array('port' => 21);
47:
48: 49: 50: 51: 52:
53: protected $_credentials = array('username', 'password');
54:
55: 56: 57: 58: 59:
60: protected $_permissions = array(
61: 'owner' => array(
62: 'read' => true,
63: 'write' => true,
64: 'execute' => true
65: ),
66: 'group' => array(
67: 'read' => true,
68: 'write' => true,
69: 'execute' => true
70: ),
71: 'all' => array(
72: 'read' => true,
73: 'write' => true,
74: 'execute' => true
75: )
76: );
77:
78: 79: 80: 81: 82:
83: protected $_stream = false;
84:
85: 86: 87: 88: 89:
90: protected $_uids = array();
91:
92: 93: 94: 95: 96:
97: protected $_gids = array();
98:
99: 100: 101: 102: 103:
104: protected $_type;
105:
106: 107: 108: 109: 110: 111: 112: 113: 114:
115: public function size($path, $name)
116: {
117: $this->_connect();
118:
119: if (($size = @ftp_size($this->_stream, $this->_getPath($path, $name))) === -1) {
120: throw new Horde_Vfs_Exception(sprintf('Unable to check file size of "%s".', $this->_getPath($path, $name)));
121: }
122:
123: return $size;
124: }
125:
126: 127: 128: 129: 130: 131: 132: 133:
134: public function read($path, $name)
135: {
136: $file = $this->readFile($path, $name);
137: clearstatcache();
138: $size = filesize($file);
139:
140: return ($size === 0)
141: ? ''
142: : file_get_contents($file);
143: }
144:
145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157:
158: public function readFile($path, $name)
159: {
160: $this->_connect();
161:
162:
163:
164: if (!($localFile = Horde_Util::getTempFile('vfs'))) {
165: throw new Horde_Vfs_Exception('Unable to create temporary file.');
166: }
167:
168: $result = @ftp_get(
169: $this->_stream,
170: $localFile,
171: $this->_getPath($path, $name),
172: FTP_BINARY);
173:
174: if ($result === false) {
175: throw new Horde_Vfs_Exception(sprintf('Unable to open VFS file "%s".', $this->_getPath($path, $name)));
176: }
177:
178: clearstatcache();
179:
180: return $localFile;
181: }
182:
183: 184: 185: 186: 187: 188: 189: 190: 191:
192: public function readStream($path, $name)
193: {
194: return fopen($this->readFile($path, $name), OS_WINDOWS ? 'rb' : 'r');
195: }
196:
197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207:
208: public function write($path, $name, $tmpFile, $autocreate = false)
209: {
210: $this->_connect();
211: $this->_checkQuotaWrite('file', $tmpFile);
212:
213: if (!@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY)) {
214: if ($autocreate) {
215: $this->autocreatePath($path);
216: if (@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY)) {
217: return;
218: }
219: }
220:
221: throw new Horde_Vfs_Exception(sprintf('Unable to write VFS file "%s".', $this->_getPath($path, $name)));
222: }
223: }
224:
225: 226: 227: 228: 229: 230: 231: 232: 233: 234:
235: public function writeData($path, $name, $data, $autocreate = false)
236: {
237: $this->_checkQuotaWrite('string', $data);
238: $tmpFile = Horde_Util::getTempFile('vfs');
239: file_put_contents($tmpFile, $data);
240: try {
241: $this->write($path, $name, $tmpFile, $autocreate);
242: unlink($tmpFile);
243: } catch (Horde_Vfs_Exception $e) {
244: unlink($tmpFile);
245: throw $e;
246: }
247: }
248:
249: 250: 251: 252: 253: 254: 255: 256:
257: public function deleteFile($path, $name)
258: {
259: $this->_connect();
260: $this->_checkQuotaDelete($path, $name);
261:
262: if (!@ftp_delete($this->_stream, $this->_getPath($path, $name))) {
263: throw new Horde_Vfs_Exception(sprintf('Unable to delete VFS file "%s".', $this->_getPath($path, $name)));
264: }
265: }
266:
267: 268: 269: 270: 271: 272: 273: 274:
275: public function isFolder($path, $name)
276: {
277: $result = false;
278:
279: try {
280: $this->_connect();
281:
282: $olddir = $this->getCurrentDirectory();
283:
284:
285: $result = @ftp_chdir($this->_stream, $this->_getPath($path, $name));
286:
287: $this->_setPath($olddir);
288: } catch (Horde_Vfs_Exception $e) {
289: }
290:
291: return $result;
292: }
293:
294: 295: 296: 297: 298: 299: 300: 301: 302:
303: public function deleteFolder($path, $name, $recursive = false)
304: {
305: $this->_connect();
306:
307: $isDir = false;
308: foreach ($this->listFolder($path) as $file) {
309: if ($file['name'] == $name && $file['type'] == '**dir') {
310: $isDir = true;
311: break;
312: }
313: }
314:
315: if ($isDir) {
316: $dir = ltrim($path . '/' . $name, '/');
317: $file_list = $this->listFolder($dir);
318: if (count($file_list) && !$recursive) {
319: throw new Horde_Vfs_Exception(sprintf('Unable to delete "%s", as the directory is not empty.', $this->_getPath($path, $name)));
320: }
321:
322: foreach ($file_list as $file) {
323: if ($file['type'] == '**dir') {
324: $this->deleteFolder($dir, $file['name'], $recursive);
325: } else {
326: $this->deleteFile($dir, $file['name']);
327: }
328: }
329:
330: if (!@ftp_rmdir($this->_stream, $this->_getPath($path, $name))) {
331: throw new Horde_Vfs_Exception(sprintf('Cannot remove directory "%s".', $this->_getPath($path, $name)));
332: }
333: } elseif (!@ftp_delete($this->_stream, $this->_getPath($path, $name))) {
334: throw new Horde_Vfs_Exception(sprintf('Cannot delete file "%s".', $this->_getPath($path, $name)));
335: }
336: }
337:
338: 339: 340: 341: 342: 343: 344: 345: 346: 347:
348: public function rename($oldpath, $oldname, $newpath, $newname)
349: {
350: $this->_connect();
351: $this->autocreatePath($newpath);
352:
353: if (!@ftp_rename($this->_stream, $this->_getPath($oldpath, $oldname), $this->_getPath($newpath, $newname))) {
354: throw new Horde_Vfs_Exception(sprintf('Unable to rename VFS file "%s".', $this->_getPath($oldpath, $oldname)));
355: }
356: }
357:
358: 359: 360: 361: 362: 363: 364: 365:
366: public function createFolder($path, $name)
367: {
368: $this->_connect();
369:
370: if (!@ftp_mkdir($this->_stream, $this->_getPath($path, $name))) {
371: throw new Horde_Vfs_Exception(sprintf('Unable to create VFS directory "%s".', $this->_getPath($path, $name)));
372: }
373: }
374:
375: 376: 377: 378: 379: 380: 381: 382: 383:
384: public function changePermissions($path, $name, $permission)
385: {
386: $this->_connect();
387:
388: if (!@ftp_site($this->_stream, 'CHMOD ' . $permission . ' ' . $this->_getPath($path, $name))) {
389: throw new Horde_Vfs_Exception(sprintf('Unable to change permission for VFS file "%s".', $this->_getPath($path, $name)));
390: }
391: }
392:
393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403:
404: protected function _listFolder($path = '', $filter = null,
405: $dotfiles = true, $dironly = false)
406: {
407: $this->_connect();
408:
409: if (empty($this->_type)) {
410: if (!empty($this->_params['type'])) {
411: $this->_type = $this->_params['type'];
412: } else {
413: $type = Horde_String::lower(@ftp_systype($this->_stream));
414: if ($type == 'unknown') {
415:
416: $type = 'unix';
417: } elseif (strpos($type, 'win') !== false) {
418: $type = 'win';
419: } elseif (strpos($type, 'netware') !== false) {
420: $type = 'netware';
421: }
422:
423: $this->_type = $type;
424: }
425: }
426:
427: $olddir = $this->getCurrentDirectory();
428: $path = $this->_getPath('', $path);
429: if (strlen($path)) {
430: $this->_setPath($path);
431: }
432:
433: if ($this->_type == 'unix') {
434:
435:
436:
437: if ($dotfiles) {
438: $list = ftp_rawlist($this->_stream, '-al');
439: $dotfiles = true;
440: } else {
441: $list = ftp_rawlist($this->_stream, '-l');
442: }
443: } else {
444: $list = ftp_rawlist($this->_stream, '');
445: }
446:
447: if (!is_array($list)) {
448: if (isset($olddir)) {
449: $this->_setPath($olddir);
450: }
451: return array();
452: }
453:
454:
455: $mapids = (!empty($this->_params['maplocalids']) && extension_loaded('posix'));
456:
457: $currtime = time();
458: $files = array();
459:
460: foreach ($list as $line) {
461: $file = array();
462:
463: $item = preg_split('/\s+/', $line);
464: if (($this->_type == 'unix') ||
465: (($this->_type == 'win') && !preg_match('|\d\d-\d\d-\d\d|', $item[0]))) {
466: if (count($item) < 8 || substr($line, 0, 5) == 'total') {
467: continue;
468: }
469: $file['perms'] = $item[0];
470: if ($mapids) {
471: if (!isset($this->_uids[$item[2]])) {
472: $entry = posix_getpwuid($item[2]);
473: $this->_uids[$item[2]] = (empty($entry)) ? $item[2] : $entry['name'];
474: }
475: $file['owner'] = $this->_uids[$item[2]];
476: if (!isset($this->_uids[$item[3]])) {
477: $entry = posix_getgrgid($item[3]);
478: $this->_uids[$item[3]] = (empty($entry)) ? $item[3] : $entry['name'];
479: }
480: $file['group'] = $this->_uids[$item[3]];
481:
482: } else {
483: $file['owner'] = $item[2];
484: $file['group'] = $item[3];
485: }
486:
487: if (!empty($this->_params['lsformat']) &&
488: ($this->_params['lsformat'] == 'aix')) {
489: $file['name'] = substr($line, strpos($line, sprintf("%s %2s %-5s", $item[5], $item[6], $item[7])) + 13);
490: } else {
491: $file['name'] = substr($line, strpos($line, sprintf("%s %2s %5s", $item[5], $item[6], $item[7])) + 13);
492: }
493:
494:
495: if (preg_match('/^\.\.?\/?$/', $file['name'])) {
496: continue;
497: }
498:
499:
500: if (!$dotfiles && substr($file['name'], 0, 1) == '.') {
501: continue;
502: }
503:
504: $p1 = substr($file['perms'], 0, 1);
505: if ($p1 === 'l') {
506: $file['link'] = substr($file['name'], strpos($file['name'], '->') + 3);
507: $file['name'] = substr($file['name'], 0, strpos($file['name'], '->') - 1);
508: $file['type'] = '**sym';
509:
510: if ($this->isFolder('', $file['link'])) {
511: $file['linktype'] = '**dir';
512: } else {
513: $parts = explode('/', $file['link']);
514: $name = explode('.', array_pop($parts));
515: if (count($name) == 1 || ($name[0] === '' && count($name) == 2)) {
516: $file['linktype'] = '**none';
517: } else {
518: $file['linktype'] = Horde_String::lower(array_pop($name));
519: }
520: }
521: } elseif ($p1 === 'd') {
522: $file['type'] = '**dir';
523: } else {
524: $name = explode('.', $file['name']);
525: if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
526: $file['type'] = '**none';
527: } else {
528: $file['type'] = Horde_String::lower($name[count($name) - 1]);
529: }
530: }
531: if ($file['type'] == '**dir') {
532: $file['size'] = -1;
533: } else {
534: $file['size'] = $item[4];
535: }
536: if (strpos($item[7], ':') !== false) {
537: $file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . date('Y', $currtime));
538:
539:
540:
541:
542:
543:
544: if ($file['date'] > ($currtime + 86400)) {
545: $file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . (date('Y', $currtime) - 1));
546: }
547: } else {
548: $file['date'] = strtotime('00:00:00' . $item[5] . ' ' . $item[6] . ' ' . $item[7]);
549: }
550: } elseif ($this->_type == 'netware') {
551: if (count($item) < 8 || substr($line, 0, 5) == 'total') {
552: continue;
553: }
554:
555: $file = array();
556: $file['perms'] = $item[1];
557: $file['owner'] = $item[2];
558: if ($item[0] == 'd') {
559: $file['type'] = '**dir';
560: } else {
561: $file['type'] = '**none';
562: }
563: $file['size'] = $item[3];
564:
565:
566: if (strpos($item[6], ':') !== false) {
567: $file['date'] = strtotime($item[6] . ':00 ' . $item[5] . ' ' . $item[4] . ' ' . date('Y'));
568: } else {
569: $file['date'] = strtotime('00:00:00 ' . $item[5] . ' ' . $item[4] . ' ' . $item[6]);
570: }
571:
572: $file['name'] = $item[7];
573: for ($index = 8, $c = count($item); $index < $c; $index++) {
574: $file['name'] .= ' ' . $item[$index];
575: }
576: } else {
577: 578:
579: $file['perms'] = '';
580: $file['owner'] = '';
581: $file['group'] = '';
582: $file['name'] = $item[3];
583: for ($index = 4, $c = count($item); $index < $c; $index++) {
584: $file['name'] .= ' ' . $item[$index];
585: }
586: $file['date'] = strtotime($item[0] . ' ' . $item[1]);
587: if ($item[2] == '<DIR>') {
588: $file['type'] = '**dir';
589: $file['size'] = -1;
590: } else {
591: $file['size'] = $item[2];
592: $name = explode('.', $file['name']);
593: if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
594: $file['type'] = '**none';
595: } else {
596: $file['type'] = Horde_String::lower($name[count($name) - 1]);
597: }
598: }
599: }
600:
601:
602: if ($this->_filterMatch($filter, $file['name'])) {
603: unset($file);
604: continue;
605: }
606: if ($dironly && $file['type'] !== '**dir') {
607: unset($file);
608: continue;
609: }
610:
611: $files[$file['name']] = $file;
612: unset($file);
613: }
614:
615: if (isset($olddir)) {
616: $this->_setPath($olddir);
617: }
618:
619: return $files;
620: }
621:
622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632:
633: public function listFolders($path = '', $filter = null, $dotfolders = true)
634: {
635: $this->_connect();
636:
637: $folder = array(
638: 'abbrev' => '..',
639: 'label' => '..',
640: 'val' => $this->_parentDir($path)
641: );
642: $folders = array($folder['val'] => $folder);
643:
644: $folderList = $this->listFolder($path, null, $dotfolders, true);
645: foreach ($folderList as $files) {
646: $folders[$folder['val']] = array(
647: 'abbrev' => $files['name'],
648: 'label' => $folder['val'],
649: 'val' => $this->_getPath($path, $files['name'])
650: );
651: }
652:
653: ksort($folders);
654:
655: return $folders;
656: }
657:
658: 659: 660: 661: 662: 663: 664: 665: 666: 667:
668: public function copy($path, $name, $dest, $autocreate = false)
669: {
670: $orig = $this->_getPath($path, $name);
671: if (preg_match('|^' . preg_quote($orig) . '/?$|', $dest)) {
672: throw new Horde_Vfs_Exception('Cannot copy file(s) - source and destination are the same.');
673: }
674:
675: $this->_connect();
676:
677: if ($autocreate) {
678: $this->autocreatePath($dest);
679: }
680:
681: foreach ($this->listFolder($dest, null, true) as $file) {
682: if ($file['name'] == $name) {
683: throw new Horde_Vfs_Exception(sprintf('%s already exists.'), $this->_getPath($dest, $name));
684: }
685: }
686:
687: if ($this->isFolder($path, $name)) {
688: $this->_copyRecursive($path, $name, $dest);
689: } else {
690: $tmpFile = Horde_Util::getTempFile('vfs');
691: $fetch = @ftp_get($this->_stream, $tmpFile, $orig, FTP_BINARY);
692: if (!$fetch) {
693: unlink($tmpFile);
694: throw new Horde_Vfs_Exception(sprintf('Failed to copy from "%s".', $orig));
695: }
696:
697: clearstatcache();
698: $this->_checkQuotaWrite('file', $tmpFile);
699:
700: if (!@ftp_put($this->_stream, $this->_getPath($dest, $name), $tmpFile, FTP_BINARY)) {
701: unlink($tmpFile);
702: throw new Horde_Vfs_Exception(sprintf('Failed to copy to "%s".', $this->_getPath($dest, $name)));
703: }
704:
705: unlink($tmpFile);
706: }
707: }
708:
709: 710: 711: 712: 713: 714: 715: 716: 717: 718:
719: public function move($path, $name, $dest, $autocreate = false)
720: {
721: $orig = $this->_getPath($path, $name);
722: if (preg_match('|^' . preg_quote($orig) . '/?$|', $dest)) {
723: throw new Horde_Vfs_Exception('Cannot move file(s) - destination is within source.');
724: }
725:
726: $this->_connect();
727:
728: if ($autocreate) {
729: $this->autocreatePath($dest);
730: }
731:
732: foreach ($this->listFolder($dest, null, true) as $file) {
733: if ($file['name'] == $name) {
734: throw new Horde_Vfs_Exception(sprintf('%s already exists.', $this->_getPath($dest, $name)));
735: }
736: }
737:
738: if (!@ftp_rename($this->_stream, $orig, $this->_getPath($dest, $name))) {
739: throw new Horde_Vfs_Exception(sprintf('Failed to move to "%s".', $this->_getPath($dest, $name)));
740: }
741: }
742:
743: 744: 745: 746: 747: 748:
749: public function getCurrentDirectory()
750: {
751: $this->_connect();
752: return @ftp_pwd($this->_stream);
753: }
754:
755: 756: 757: 758: 759: 760: 761: 762:
763: protected function _getPath($path, $name)
764: {
765: if (strlen($this->_params['vfsroot'])) {
766: if (strlen($path)) {
767: $path = $this->_params['vfsroot'] . '/' . $path;
768: } else {
769: $path = $this->_params['vfsroot'];
770: }
771: }
772: return parent::_getPath($path, $name);
773: }
774:
775: 776: 777: 778: 779: 780: 781:
782: protected function _setPath($path)
783: {
784: if (!@ftp_chdir($this->_stream, $path)) {
785: throw new Horde_Vfs_Exception(sprintf('Unable to change to %s.', $path));
786: }
787: }
788:
789: 790: 791: 792: 793: 794: 795: 796:
797: protected function _parentDir($path)
798: {
799: $this->_connect();
800:
801: $olddir = $this->getCurrentDirectory();
802: @ftp_cdup($this->_stream);
803:
804: $parent = $this->getCurrentDirectory();
805: $this->_setPath($olddir);
806:
807: if (!$parent) {
808: throw new Horde_Vfs_Exception('Unable to determine current directory.');
809: }
810:
811: return $parent;
812: }
813:
814: 815: 816: 817: 818:
819: protected function _connect()
820: {
821: if ($this->_stream !== false) {
822: return;
823: }
824:
825: if (!extension_loaded('ftp')) {
826: throw new Horde_Vfs_Exception('The FTP extension is not available.');
827: }
828:
829: if (!is_array($this->_params)) {
830: throw new Horde_Vfs_Exception('No configuration information specified for FTP VFS.');
831: }
832:
833: $required = array('hostspec', 'username', 'password');
834: foreach ($required as $val) {
835: if (!isset($this->_params[$val])) {
836: throw new Horde_Vfs_Exception(sprintf('Required "%s" not specified in VFS configuration.', $val));
837: }
838: }
839:
840:
841: if (!empty($this->_params['ssl'])) {
842: if (function_exists('ftp_ssl_connect')) {
843: $this->_stream = @ftp_ssl_connect($this->_params['hostspec'], $this->_params['port']);
844: } else {
845: throw new Horde_Vfs_Exception('Unable to connect with SSL.');
846: }
847: } else {
848: $this->_stream = @ftp_connect($this->_params['hostspec'], $this->_params['port']);
849: }
850:
851: if (!$this->_stream) {
852: throw new Horde_Vfs_Exception('Connection to FTP server failed.');
853: }
854:
855: if (!@ftp_login($this->_stream, $this->_params['username'], $this->_params['password'])) {
856: @ftp_quit($this->_stream);
857: $this->_stream = false;
858: throw new Horde_Vfs_Exception('Authentication to FTP server failed.');
859: }
860:
861: if (!empty($this->_params['pasv'])) {
862: @ftp_pasv($this->_stream, true);
863: }
864:
865: if (!empty($this->_params['timeout'])) {
866: ftp_set_option($this->_stream, FTP_TIMEOUT_SEC, $this->_params['timeout']);
867: }
868:
869: if (!empty($this->_params['vfsroot']) &&
870: !@ftp_chdir($this->_stream, $this->_params['vfsroot']) &&
871: !@ftp_mkdir($this->_stream, $this->_params['vfsroot'])) {
872: throw new Horde_Vfs_Exception(sprintf('Unable to create VFS root directory "%s".', $this->_params['vfsroot']));
873: }
874: }
875: }
876: