Project

General

Profile

FTP.php.txt

Magh S, 02/07/2021 11:22 AM

 
1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3

    
4
/**
5
 * Net_FTP main file.
6
 *
7
 * This file must be included to use the Net_FTP package.
8
 *
9
 * PHP versions 4 and 5
10
 *
11
 * @category  Networking
12
 * @package   FTP
13
 * @author    Tobias Schlitt <toby@php.net>
14
 * @author    Jorrit Schippers <jschippers@php.net>
15
 * @copyright 1997-2008 The PHP Group
16
 * @license   BSD http://www.opensource.org/licenses/bsd-license.php
17
 * @version   CVS: $Id: FTP.php 246 2016-02-10 21:21:12Z soeren $
18
 * @link      http://pear.php.net/package/Net_FTP
19
 * @since     File available since Release 0.0.1
20
 */
21

    
22
/**
23
 * Include PEAR.php to obtain the PEAR base class
24
 */
25
require_once 'PEAR.php';
26

    
27
/**
28
 * Option to let the ls() method return only files.
29
 *
30
 * @since 1.3
31
 * @name NET_FTP_FILES_ONLY
32
 * @see Net_FTP::ls()
33
 */
34
define('NET_FTP_FILES_ONLY', 0);
35

    
36
/**
37
 * Option to let the ls() method return only directories.
38
 *
39
 * @since 1.3
40
 * @name NET_FTP_DIRS_ONLY
41
 * @see Net_FTP::ls()
42
 */
43
define('NET_FTP_DIRS_ONLY', 1);
44

    
45
/**
46
 * Option to let the ls() method return directories and files (default).
47
 *
48
 * @since 1.3
49
 * @name NET_FTP_DIRS_FILES
50
 * @see Net_FTP::ls()
51
 */
52
define('NET_FTP_DIRS_FILES', 2);
53

    
54
/**
55
 * Option to let the ls() method return the raw directory listing from ftp_rawlist()
56
 *
57
 * @since 1.3
58
 * @name NET_FTP_RAWLIST
59
 * @see Net_FTP::ls()
60
 */
61
define('NET_FTP_RAWLIST', 3);
62

    
63
/**
64
 * Option to indicate that non-blocking features should not be used in
65
 * put(). This will also disable the listener functionality as a side effect.
66
 *
67
 * @since 1.4a1
68
 * @name NET_FTP_BLOCKING
69
 * @see Net_FTP::put()
70
 */
71
define('NET_FTP_BLOCKING', 1);
72

    
73
/**
74
 * Option to indicate that non-blocking features should be used if available in
75
 * put(). This will also enable the listener functionality.
76
 *
77
 * This is the default behaviour.
78
 *
79
 * @since 1.4a1
80
 * @name NET_FTP_NONBLOCKING
81
 * @see Net_FTP::put()
82
 */
83
define('NET_FTP_NONBLOCKING', 2);
84

    
85
/**
86
 * Error code to indicate a failed connection
87
 * This error code indicates, that the connection you tryed to set up
88
 * could not be established. Check your connection settings (host & port)!
89
 *
90
 * @since 1.3
91
 * @name NET_FTP_ERR_CONNECT_FAILED
92
 * @see Net_FTP::connect()
93
 */
94
define('NET_FTP_ERR_CONNECT_FAILED', -1);
95

    
96
/**
97
 * Error code to indicate a failed login
98
 * This error code indicates, that the login to the FTP server failed. Check
99
 * your user data (username & password).
100
 *
101
 * @since 1.3
102
 * @name NET_FTP_ERR_LOGIN_FAILED
103
 * @see Net_FTP::login()
104
 */
105
define('NET_FTP_ERR_LOGIN_FAILED', -2);
106

    
107
/**
108
 * Error code to indicate a failed directory change
109
 * The cd() method failed. Ensure that the directory you wanted to access exists.
110
 *
111
 * @since 1.3
112
 * @name NET_FTP_ERR_DIRCHANGE_FAILED
113
 * @see Net_FTP::cd()
114
 */
115
define('NET_FTP_ERR_DIRCHANGE_FAILED', 2); // Compatibillity reasons!
116

    
117
/**
118
 * Error code to indicate that Net_FTP could not determine the current path
119
 * The pwd() method failed and could not determine the path you currently reside
120
 * in on the FTP server.
121
 *
122
 * @since 1.3
123
 * @name NET_FTP_ERR_DETERMINEPATH_FAILED
124
 * @see Net_FTP::pwd()
125
 */
126
define('NET_FTP_ERR_DETERMINEPATH_FAILED', 4); // Compatibillity reasons!
127

    
128
/**
129
 * Error code to indicate that the creation of a directory failed
130
 * The directory you tryed to create could not be created. Check the
131
 * access rights on the parent directory!
132
 *
133
 * @since 1.3
134
 * @name NET_FTP_ERR_CREATEDIR_FAILED
135
 * @see Net_FTP::mkdir()
136
 */
137
define('NET_FTP_ERR_CREATEDIR_FAILED', -4);
138

    
139
/**
140
 * Error code to indicate that the EXEC execution failed.
141
 * The execution of a command using EXEC failed. Ensure, that your
142
 * FTP server supports the EXEC command.
143
 *
144
 * @since 1.3
145
 * @name NET_FTP_ERR_EXEC_FAILED
146
 * @see Net_FTP::execute()
147
 */
148
define('NET_FTP_ERR_EXEC_FAILED', -5);
149

    
150
/**
151
 * Error code to indicate that the SITE command failed.
152
 * The execution of a command using SITE failed. Ensure, that your
153
 * FTP server supports the SITE command.
154
 *
155
 * @since 1.3
156
 * @name NET_FTP_ERR_SITE_FAILED
157
 * @see Net_FTP::site()
158
 */
159
define('NET_FTP_ERR_SITE_FAILED', -6);
160

    
161
/**
162
 * Error code to indicate that the CHMOD command failed.
163
 * The execution of CHMOD failed. Ensure, that your
164
 * FTP server supports the CHMOD command and that you have the appropriate
165
 * access rights to use CHMOD.
166
 *
167
 * @since 1.3
168
 * @name NET_FTP_ERR_CHMOD_FAILED
169
 * @see Net_FTP::chmod()
170
 */
171
define('NET_FTP_ERR_CHMOD_FAILED', -7);
172

    
173
/**
174
 * Error code to indicate that a file rename failed
175
 * The renaming of a file on the server failed. Ensure that you have the
176
 * appropriate access rights to rename the file.
177
 *
178
 * @since 1.3
179
 * @name NET_FTP_ERR_RENAME_FAILED
180
 * @see Net_FTP::rename()
181
 */
182
define('NET_FTP_ERR_RENAME_FAILED', -8);
183

    
184
/**
185
 * Error code to indicate that the MDTM command failed
186
 * The MDTM command is not supported for directories. Ensure that you gave
187
 * a file path to the mdtm() method, not a directory path.
188
 *
189
 * @since 1.3
190
 * @name NET_FTP_ERR_MDTMDIR_UNSUPPORTED
191
 * @see Net_FTP::mdtm()
192
 */
193
define('NET_FTP_ERR_MDTMDIR_UNSUPPORTED', -9);
194

    
195
/**
196
 * Error code to indicate that the MDTM command failed
197
 * The MDTM command failed. Ensure that your server supports the MDTM command.
198
 *
199
 * @since 1.3
200
 * @name NET_FTP_ERR_MDTM_FAILED
201
 * @see Net_FTP::mdtm()
202
 */
203
define('NET_FTP_ERR_MDTM_FAILED', -10);
204

    
205
/**
206
 * Error code to indicate that a date returned by the server was misformated
207
 * A date string returned by your server seems to be missformated and could not be
208
 * parsed. Check that the server is configured correctly. If you're sure, please
209
 * send an email to the auhtor with a dumped output of 
210
 * $ftp->ls('./', NET_FTP_RAWLIST); to get the date format supported.
211
 *
212
 * @since 1.3
213
 * @name NET_FTP_ERR_DATEFORMAT_FAILED
214
 * @see Net_FTP::mdtm(), Net_FTP::ls()
215
 */
216
define('NET_FTP_ERR_DATEFORMAT_FAILED', -11);
217

    
218
/**
219
 * Error code to indicate that the SIZE command failed
220
 * The determination of the filesize of a file failed. Ensure that your server
221
 * supports the SIZE command.
222
 *
223
 * @since 1.3
224
 * @name NET_FTP_ERR_SIZE_FAILED
225
 * @see Net_FTP::size()
226
 */
227
define('NET_FTP_ERR_SIZE_FAILED', -12);
228

    
229
/**
230
 * Error code to indicate that a local file could not be overwritten
231
 * You specified not to overwrite files. Therefore the local file has not been
232
 * overwriten. If you want to get the file overwriten, please set the option to
233
 * do so.
234
 *
235
 * @since 1.3
236
 * @name NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN
237
 * @see Net_FTP::get(), Net_FTP::getRecursive()
238
 */
239
define('NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN', -13);
240

    
241
/**
242
 * Error code to indicate that a local file could not be overwritten
243
 * Also you specified to overwrite the local file you want to download to,
244
 * it has not been possible to do so. Check that you have the appropriate access
245
 * rights on the local file to overwrite it.
246
 *
247
 * @since 1.3
248
 * @name NET_FTP_ERR_OVERWRITELOCALFILE_FAILED
249
 * @see Net_FTP::get(), Net_FTP::getRecursive()
250
 */
251
define('NET_FTP_ERR_OVERWRITELOCALFILE_FAILED', -14);
252

    
253
/**
254
 * Error code to indicate that the file you wanted to upload does not exist
255
 * The file you tried to upload does not exist. Ensure that it exists.
256
 *
257
 * @since 1.3
258
 * @name NET_FTP_ERR_LOCALFILENOTEXIST
259
 * @see Net_FTP::put(), Net_FTP::putRecursive()
260
 */
261
define('NET_FTP_ERR_LOCALFILENOTEXIST', -15);
262

    
263
/**
264
 * Error code to indicate that a remote file could not be overwritten
265
 * You specified not to overwrite files. Therefore the remote file has not been
266
 * overwriten. If you want to get the file overwriten, please set the option to
267
 * do so.
268
 *
269
 * @since 1.3
270
 * @name NET_FTP_ERR_OVERWRITEREMOTEFILE_FORBIDDEN
271
 * @see Net_FTP::put(), Net_FTP::putRecursive()
272
 */
273
define('NET_FTP_ERR_OVERWRITEREMOTEFILE_FORBIDDEN', -16);
274

    
275
/**
276
 * Error code to indicate that the upload of a file failed
277
 * The upload you tried failed. Ensure that you have appropriate access rights
278
 * to upload the desired file.
279
 *
280
 * @since 1.3
281
 * @name NET_FTP_ERR_UPLOADFILE_FAILED
282
 * @see Net_FTP::put(), Net_FTP::putRecursive()
283
 */
284
define('NET_FTP_ERR_UPLOADFILE_FAILED', -17);
285

    
286
/**
287
 * Error code to indicate that you specified an incorrect directory path
288
 * The remote path you specified seems not to be a directory. Ensure that
289
 * the path you specify is a directory and that the path string ends with
290
 * a /.
291
 *
292
 * @since 1.3
293
 * @name NET_FTP_ERR_REMOTEPATHNODIR
294
 * @see Net_FTP::putRecursive(), Net_FTP::getRecursive()
295
 */
296
define('NET_FTP_ERR_REMOTEPATHNODIR', -18);
297

    
298
/**
299
 * Error code to indicate that you specified an incorrect directory path
300
 * The local path you specified seems not to be a directory. Ensure that
301
 * the path you specify is a directory and that the path string ends with
302
 * a /.
303
 *
304
 * @since 1.3
305
 * @name NET_FTP_ERR_LOCALPATHNODIR
306
 * @see Net_FTP::putRecursive(), Net_FTP::getRecursive()
307
 */
308
define('NET_FTP_ERR_LOCALPATHNODIR', -19);
309

    
310
/**
311
 * Error code to indicate that a local directory failed to be created
312
 * You tried to create a local directory through getRecursive() method,
313
 * which has failed. Ensure that you have the appropriate access rights
314
 * to create it.
315
 *
316
 * @since 1.3
317
 * @name NET_FTP_ERR_CREATELOCALDIR_FAILED
318
 * @see Net_FTP::getRecursive()
319
 */
320
define('NET_FTP_ERR_CREATELOCALDIR_FAILED', -20);
321

    
322
/**
323
 * Error code to indicate that the provided hostname was incorrect
324
 * The hostname you provided was invalid. Ensure to provide either a
325
 * full qualified domain name or an IP address.
326
 *
327
 * @since 1.3
328
 * @name NET_FTP_ERR_HOSTNAMENOSTRING
329
 * @see Net_FTP::setHostname()
330
 */
331
define('NET_FTP_ERR_HOSTNAMENOSTRING', -21);
332

    
333
/**
334
 * Error code to indicate that the provided port was incorrect
335
 * The port number you provided was invalid. Ensure to provide either a
336
 * a numeric port number greater zero.
337
 *
338
 * @since 1.3
339
 * @name NET_FTP_ERR_PORTLESSZERO
340
 * @see Net_FTP::setPort()
341
 */
342
define('NET_FTP_ERR_PORTLESSZERO', -22);
343

    
344
/**
345
 * Error code to indicate that you provided an invalid mode constant
346
 * The mode constant you provided was invalid. You may only provide
347
 * FTP_ASCII or FTP_BINARY.
348
 *
349
 * @since 1.3
350
 * @name NET_FTP_ERR_NOMODECONST
351
 * @see Net_FTP::setMode()
352
 */
353
define('NET_FTP_ERR_NOMODECONST', -23);
354

    
355
/**
356
 * Error code to indicate that you provided an invalid timeout
357
 * The timeout you provided was invalid. You have to provide a timeout greater
358
 * or equal to zero.
359
 *
360
 * @since 1.3
361
 * @name NET_FTP_ERR_TIMEOUTLESSZERO
362
 * @see Net_FTP::Net_FTP(), Net_FTP::setTimeout()
363
 */
364
define('NET_FTP_ERR_TIMEOUTLESSZERO', -24);
365

    
366
/**
367
 * Error code to indicate that you provided an invalid timeout
368
 * An error occured while setting the timeout. Ensure that you provide a
369
 * valid integer for the timeount and that your PHP installation works
370
 * correctly.
371
 *
372
 * @since 1.3
373
 * @name NET_FTP_ERR_SETTIMEOUT_FAILED
374
 * @see Net_FTP::Net_FTP(), Net_FTP::setTimeout()
375
 */
376
define('NET_FTP_ERR_SETTIMEOUT_FAILED', -25);
377

    
378
/**
379
 * Error code to indicate that the provided extension file doesn't exist
380
 * The provided extension file does not exist. Ensure to provided an
381
 * existant extension file.
382
 *
383
 * @since 1.3
384
 * @name NET_FTP_ERR_EXTFILENOTEXIST
385
 * @see Net_FTP::getExtensionsFile()
386
 */
387
define('NET_FTP_ERR_EXTFILENOTEXIST', -26);
388

    
389
/**
390
 * Error code to indicate that the provided extension file is not readable
391
 * The provided extension file is not readable. Ensure to have sufficient
392
 * access rights for it.
393
 *
394
 * @since 1.3
395
 * @name NET_FTP_ERR_EXTFILEREAD_FAILED
396
 * @see Net_FTP::getExtensionsFile()
397
 */
398
define('NET_FTP_ERR_EXTFILEREAD_FAILED', -27);
399

    
400
/**
401
 * Error code to indicate that the deletion of a file failed
402
 * The specified file could not be deleted. Ensure to have sufficient
403
 * access rights to delete the file.
404
 *
405
 * @since 1.3
406
 * @name NET_FTP_ERR_EXTFILEREAD_FAILED
407
 * @see Net_FTP::rm()
408
 */
409
define('NET_FTP_ERR_DELETEFILE_FAILED', -28);
410

    
411
/**
412
 * Error code to indicate that the deletion of a directory faild
413
 * The specified file could not be deleted. Ensure to have sufficient
414
 * access rights to delete the file.
415
 *
416
 * @since 1.3
417
 * @name NET_FTP_ERR_EXTFILEREAD_FAILED
418
 * @see Net_FTP::rm()
419
 */
420
define('NET_FTP_ERR_DELETEDIR_FAILED', -29);
421

    
422
/**
423
 * Error code to indicate that the directory listing failed
424
 * PHP could not list the directory contents on the server. Ensure
425
 * that your server is configured appropriate.
426
 *
427
 * @since 1.3
428
 * @name NET_FTP_ERR_RAWDIRLIST_FAILED
429
 * @see Net_FTP::ls()
430
 */
431
define('NET_FTP_ERR_RAWDIRLIST_FAILED', -30);
432

    
433
/**
434
 * Error code to indicate that the directory listing failed
435
 * The directory listing format your server uses seems not to
436
 * be supported by Net_FTP. Please send the output of the
437
 * call ls('./', NET_FTP_RAWLIST); to the author of this
438
 * class to get it supported.
439
 *
440
 * @since 1.3
441
 * @name NET_FTP_ERR_DIRLIST_UNSUPPORTED
442
 * @see Net_FTP::ls()
443
 */
444
define('NET_FTP_ERR_DIRLIST_UNSUPPORTED', -31);
445

    
446
/**
447
 * Error code to indicate failed disconnecting
448
 * This error code indicates, that disconnection was not possible.
449
 *
450
 * @since 1.3
451
 * @name NET_FTP_ERR_DISCONNECT_FAILED
452
 * @see Net_FTP::disconnect()
453
 */
454
define('NET_FTP_ERR_DISCONNECT_FAILED', -32);
455

    
456
/**
457
 * Error code to indicate that the username you provided was invalid.
458
 * Check that you provided a non-empty string as the username.
459
 *
460
 * @since 1.3
461
 * @name NET_FTP_ERR_USERNAMENOSTRING
462
 * @see Net_FTP::setUsername()
463
 */
464
define('NET_FTP_ERR_USERNAMENOSTRING', -33);
465

    
466
/**
467
 * Error code to indicate that the username you provided was invalid.
468
 * Check that you provided a non-empty string as the username.
469
 *
470
 * @since 1.3
471
 * @name NET_FTP_ERR_PASSWORDNOSTRING
472
 * @see Net_FTP::setPassword()
473
 */
474
define('NET_FTP_ERR_PASSWORDNOSTRING', -34);
475

    
476
/**
477
 * Error code to indicate that the provided extension file is not loadable
478
 * The provided extension file is not loadable. Ensure to have a correct file
479
 * syntax.
480
 *
481
 * @since 1.3.3
482
 * @name NET_FTP_ERR_EXTFILELOAD_FAILED
483
 * @see Net_FTP::getExtensionsFile()
484
 */
485
define('NET_FTP_ERR_EXTFILELOAD_FAILED', -35);
486

    
487
/**
488
 * Error code to indicate that the directory listing pattern provided is not a
489
 * string.
490
 *
491
 * @since 1.4.0a1
492
 * @name NET_FTP_ERR_ILLEGALPATTERN
493
 * @see Net_FTP::setDirMatcher()
494
 */
495
define('NET_FTP_ERR_ILLEGALPATTERN', -36);
496

    
497
/**
498
 * Error code to indicate that the directory listing matcher map provided is not an
499
 * array.
500
 *
501
 * @since 1.4.0a1
502
 * @name NET_FTP_ERR_ILLEGALMAP
503
 * @see Net_FTP::setDirMatcher()
504
 */
505
define('NET_FTP_ERR_ILLEGALMAP', -37);
506

    
507
/**
508
 * Error code to indicate that the directory listing matcher map provided contains
509
 * wrong values (ie: it contains non-numerical values)
510
 *
511
 * @since 1.4.0a1
512
 * @name NET_FTP_ERR_ILLEGALMAPVALUE
513
 * @see Net_FTP::setDirMatcher()
514
 */
515
define('NET_FTP_ERR_ILLEGALMAPVALUE', -38);
516

    
517
/**
518
 * Error code indicating that bad options were supplied to the
519
 * put() method.
520
 *
521
 * @since 1.4a1
522
 * @name NET_FTP_ERR_BADOPTIONS
523
 * @see Net_FTP::put()
524
 */
525
define('NET_FTP_ERR_BADOPTIONS', -39);
526

    
527
/**
528
 * Error code indicating that SSL connection is not supported as either the
529
 * ftp module or OpenSSL support is not statically built into php.
530
 *
531
 * @since 1.4a2
532
 * @name NET_FTP_ERR_NOSSL
533
 * @see Net_FTP::setSsl()
534
 */
535
define('NET_FTP_ERR_NOSSL', -40);
536

    
537
/**
538
 * Class for comfortable FTP-communication
539
 *
540
 * This class provides comfortable communication with FTP-servers. You may do
541
 * everything enabled by the PHP-FTP-extension and further functionalities, like
542
 * recursive-deletion, -up- and -download. Another feature is to create directories
543
 * recursively.
544
 *
545
 * @category  Networking
546
 * @package   FTP
547
 * @author    Tobias Schlitt <toby@php.net>
548
 * @author    Jorrit Schippers <jschippers@php.net>
549
 * @copyright 1997-2008 The PHP Group
550
 * @license   http://www.php.net/license/3_0.txt PHP License 3.0
551
 * @version   Release: 1.4.0
552
 * @link      http://pear.php.net/package/Net_FTP
553
 * @since     0.0.1
554
 * @access    public
555
 */
556
class Net_FTP extends PEAR
557
{
558
    /**
559
     * The host to connect to
560
     *
561
     * @access  private
562
     * @var     string
563
     */
564
    var $_hostname;
565

    
566
    /**
567
     * The port for ftp-connection (standard is 21)
568
     *
569
     * @access  private
570
     * @var     int
571
     */
572
    var $_port = 21;
573

    
574
    /**
575
     * The username for login
576
     *
577
     * @access  private
578
     * @var     string
579
     */
580
    var $_username;
581

    
582
    /**
583
     * The password for login
584
     *
585
     * @access  private
586
     * @var     string
587
     */
588
    var $_password;
589

    
590
    /**
591
     * Determine whether to connect through secure SSL connection or not
592
     *
593
     * Is null when it hasn't been explicitly set
594
     *
595
     * @access  private
596
     * @var     bool
597
     */
598
    var $_ssl;
599

    
600
    /**
601
     * Determine whether to use passive-mode (true) or active-mode (false)
602
     *
603
     * Is null when it hasn't been explicitly set
604
     *
605
     * @access  private
606
     * @var     bool
607
     */
608
    var $_passv = null;
609

    
610
    /**
611
     * The standard mode for ftp-transfer
612
     *
613
     * @access  private
614
     * @var     int
615
     */
616
    var $_mode = FTP_BINARY;
617

    
618
    /**
619
     * This holds the handle for the ftp-connection
620
     *
621
     * If null, the connection hasn't been setup yet. If false, the connection
622
     * attempt has failed. Else, it contains an ftp resource.
623
     *
624
     * @access  private
625
     * @var     resource
626
     */
627
    var $_handle = null;
628

    
629
    /**
630
     * Contains the timeout for FTP operations
631
     *
632
     * @access  private
633
     * @var     int
634
     * @since   1.3
635
     */
636
    var $_timeout = 90;
637
        
638
    /**
639
     * Saves file-extensions for ascii- and binary-mode
640
     *
641
     * The array is built like this: 'php' => FTP_ASCII, 'png' => FTP_BINARY
642
     *
643
     * @access  private
644
     * @var     array
645
     */
646
    var $_file_extensions = array();
647

    
648
    /**
649
     * ls match
650
     * Matches the ls entries against a regex and maps the resulting array to
651
     * speaking names
652
     *
653
     * The values are set in the constructor because of line length constaints.
654
     *
655
     * Typical lines for the Windows format:
656
     * 07-05-07  08:40AM                 4701 SomeFile.ext
657
     * 04-29-07  10:28PM       <DIR>          SomeDir
658
     *
659
     * @access  private
660
     * @var     array
661
     * @since   1.3
662
     */
663
    var $_ls_match = null;
664
    
665
    /**
666
     * matcher
667
     * Stores the matcher for the current connection
668
     *
669
     * @access  private
670
     * @var     array
671
     * @since   1.3
672
     */
673
    var $_matcher = null;
674
    
675
    /**
676
     * Holds all Net_FTP_Observer objects 
677
     * that wish to be notified of new messages.
678
     *
679
     * @var     array
680
     * @access  private
681
     * @since   1.3
682
     */
683
    var $_listeners = array();
684

    
685
    /**
686
     * Is true when a login has been performed
687
     * and was successful
688
     *
689
     * @access  private
690
     * @var     boolean
691
     * @since   1.4
692
     */
693
    var $_loggedin = false;
694

    
695
    /**
696
     * This generates a new FTP-Object. The FTP-connection will not be established,
697
     * yet.
698
     * You can leave $host and $port blank, if you want. The $host will not be set
699
     * and the $port will be left at 21. You have to set the $host manualy before
700
     * trying to connect or with the connect() method.
701
     *
702
     * @param string $host    (optional) The hostname 
703
     * @param int    $port    (optional) The port
704
     * @param int    $timeout (optional) Sets the standard timeout
705
     *
706
     * @access public
707
     * @return void
708
     * @see Net_FTP::setHostname(), Net_FTP::setPort(), Net_FTP::connect()
709
     */
710
    function __construct($host = null, $port = null, $timeout = 90)
711
    {
712
        $this->PEAR();
713
        if (isset($host)) {
714
            $this->setHostname($host);
715
        }
716
        if (isset($port)) {
717
            $this->setPort($port);
718
        }
719
        $this->_timeout                     = $timeout;
720
        
721
        $this->_ls_match = array(
722
            'unix'    => array(
723
                'pattern' => '/(?:(d)|.)([rwxts-]{9})\s+(\w+)\s+([\w\d-()?.]+)\s+'.
724
                             '([\w\d-()?.]+)\s+(\w+)\s+(\S+\s+\S+\s+\S+)\s+(.+)/',
725
                'map'     => array(
726
                    'is_dir'        => 1,
727
                    'rights'        => 2,
728
                    'files_inside'  => 3,
729
                    'user'          => 4,
730
                    'group'         => 5,
731
                    'size'          => 6,
732
                    'date'          => 7,
733
                    'name'          => 8,
734
                )
735
            ),
736
            'windows' => array(
737
                'pattern' => '/([0-9\-]+\s+[0-9:APM]+)\s+((<DIR>)|\d+)\s+(.+)/',
738
                'map'     => array(
739
                    'date'   => 1,
740
                    'size'   => 2,
741
                    'is_dir' => 3,
742
                    'name'   => 4,
743
                )
744
            )
745
        );
746
    }
747

    
748
    /**
749
     * This function generates the FTP-connection. You can optionally define a
750
     * hostname and/or a port. If you do so, this data is stored inside the object.
751
     *
752
     * @param string $host (optional) The Hostname
753
     * @param int    $port (optional) The Port
754
     * @param bool   $ssl  (optional) Whether to connect through secure SSL connection
755
     *
756
     * @access public
757
     * @return mixed True on success, otherwise PEAR::Error
758
     * @see NET_FTP_ERR_CONNECT_FAILED
759
     */
760
    function connect($host = null, $port = null, $ssl = null)
761
    {
762
        $this->_matcher = null;
763
        if (isset($host)) {
764
            $this->setHostname($host);
765
        }
766
        if (isset($port)) {
767
            $this->setPort($port);
768
        }
769
        if (isset($ssl) && is_bool($ssl) && $ssl) {
770
            $this->setSsl();
771
        }
772
        if ($this->getSsl()) {
773
            $handle = @ftp_ssl_connect($this->getHostname(), $this->getPort(),
774
                                       $this->_timeout);
775
        } else {
776
            $handle = @ftp_connect($this->getHostname(), $this->getPort(),
777
                                   $this->_timeout);
778
        }
779
        if (!$handle) {
780
            $this->_handle = false;
781
            return $this->raiseError("Connection to host failed",
782
                                     NET_FTP_ERR_CONNECT_FAILED);
783
        } else {
784
            $this->_handle =& $handle;
785
            return true;
786
        }
787
    }
788

    
789
    /**
790
     * This function close the FTP-connection
791
     *
792
     * @access public
793
     * @return bool|PEAR_Error Returns true on success, PEAR_Error on failure
794
     */
795
    function disconnect()
796
    {
797
        $res = @ftp_close($this->_handle);
798
        if (!$res) {
799
            return PEAR::raiseError('Disconnect failed.',
800
                                    NET_FTP_ERR_DISCONNECT_FAILED);
801
        }
802
        $this->_handle = null;
803
        return true;
804
    }
805

    
806
    /**
807
     * This logs you into the ftp-server. You are free to specify username and
808
     * password in this method. If you specify it, the values will be taken into 
809
     * the corresponding attributes, if do not specify, the attributes are taken.
810
     *
811
     * If connect() has not been called yet, a connection will be setup
812
     *
813
     * @param string $username (optional) The username to use 
814
     * @param string $password (optional) The password to use
815
     *
816
     * @access public
817
     * @return mixed True on success, otherwise PEAR::Error
818
     * @see NET_FTP_ERR_LOGIN_FAILED
819
     */
820
    function login($username = null, $password = null)
821
    {
822
        if ($this->_handle === null) {
823
            $res = $this->connect();
824
            if (PEAR::isError($res)) {
825
                return $res;
826
            }
827
        }
828
        
829
        if (!isset($username)) {
830
            $username = $this->getUsername();
831
        } else {
832
            $this->setUsername($username);
833
        }
834

    
835
        if (!isset($password)) {
836
            $password = $this->getPassword();
837
        } else {
838
            $this->setPassword($password);
839
        }
840

    
841
        $res = @ftp_login($this->_handle, $username, $password);
842

    
843
        if (!$res) {
844
            return $this->raiseError("Unable to login", NET_FTP_ERR_LOGIN_FAILED);
845
        } else {
846
            $this->_loggedin = true;
847

    
848
            // distinguish between null and false, null means this setting wasn't
849
            // explicitly changed, so we only change it when setPassive or
850
            // setActive was called by the user
851
            if ($this->_passv === true) {
852
                $this->setPassive();
853
            } elseif ($this->_passv === false) {
854
                $this->setActive();
855
            }
856

    
857
            return true;
858
        }
859
    }
860

    
861
    /**
862
     * This changes the currently used directory. You can use either an absolute
863
     * directory-path (e.g. "/home/blah") or a relative one (e.g. "../test").
864
     *
865
     * @param string $dir The directory to go to.
866
     *
867
     * @access public
868
     * @return mixed True on success, otherwise PEAR::Error
869
     * @see NET_FTP_ERR_DIRCHANGE_FAILED
870
     */
871
    function cd($dir)
872
    {
873
        $erg = @ftp_chdir($this->_handle, $dir);
874
        if (!$erg) {
875
            return $this->raiseError("Directory change failed",
876
                                     NET_FTP_ERR_DIRCHANGE_FAILED);
877
        } else {
878
            return true;
879
        }
880
    }
881

    
882
    /**
883
     * Show's you the actual path on the server
884
     * This function questions the ftp-handle for the actual selected path and
885
     * returns it.
886
     *
887
     * @access public
888
     * @return mixed The actual path or PEAR::Error
889
     * @see NET_FTP_ERR_DETERMINEPATH_FAILED
890
     */
891
    function pwd()
892
    {
893
        $res = @ftp_pwd($this->_handle);
894
        if (!$res) {
895
            return $this->raiseError("Could not determine the actual path.",
896
                                     NET_FTP_ERR_DETERMINEPATH_FAILED);
897
        } else {
898
            return $res;
899
        }
900
    }
901

    
902
    /**
903
     * This works similar to the mkdir-command on your local machine. You can either
904
     * give it an absolute or relative path. The relative path will be completed
905
     * with the actual selected server-path. (see: pwd())
906
     *
907
     * @param string $dir       Absolute or relative dir-path
908
     * @param bool   $recursive (optional) Create all needed directories
909
     *
910
     * @access public
911
     * @return mixed True on success, otherwise PEAR::Error
912
     * @see NET_FTP_ERR_CREATEDIR_FAILED
913
     */
914
    function mkdir($dir, $recursive = false)
915
    {
916
        $dir     = $this->_constructPath($dir);
917
        $savedir = $this->pwd();
918
        $this->pushErrorHandling(PEAR_ERROR_RETURN);
919
        $e = $this->cd($dir);
920
        $this->popErrorHandling();
921
        if ($e === true) {
922
            $this->cd($savedir);
923
            return true;
924
        }
925
        $this->cd($savedir);
926
        if ($recursive === false) {
927
            $res = @ftp_mkdir($this->_handle, $dir);
928
            if (!$res) {
929
                return $this->raiseError("Creation of '$dir' failed",
930
                                         NET_FTP_ERR_CREATEDIR_FAILED);
931
            } else {
932
                return true;
933
            }
934
        } else {
935
            // do not look at the first character, as $dir is absolute,
936
            // it will always be a /
937
            if (strpos(substr($dir, 1), '/') === false) {
938
                return $this->mkdir($dir, false);
939
            }
940
            if (substr($dir, -1) == '/') {
941
                $dir = substr($dir, 0, -1);
942
            }
943
            $parent = substr($dir, 0, strrpos($dir, '/'));
944
            $res    = $this->mkdir($parent, true);
945
            if ($res === true) {
946
                $res = $this->mkdir($dir, false);
947
            }
948
            if ($res !== true) {
949
                return $res;
950
            }
951
            return true;
952
        }
953
    }
954

    
955
    /**
956
     * This method tries executing a command on the ftp, using SITE EXEC.
957
     *
958
     * @param string $command The command to execute
959
     *
960
     * @access public
961
     * @return mixed The result of the command (if successfull), otherwise
962
     *               PEAR::Error
963
     * @see NET_FTP_ERR_EXEC_FAILED
964
     */
965
    function execute($command)
966
    {
967
        $res = @ftp_exec($this->_handle, $command);
968
        if (!$res) {
969
            return $this->raiseError("Execution of command '$command' failed.",
970
                                     NET_FTP_ERR_EXEC_FAILED);
971
        } else {
972
            return $res;
973
        }
974
    }
975

    
976
    /**
977
     * Execute a SITE command on the server
978
     * This method tries to execute a SITE command on the ftp server.
979
     *
980
     * @param string $command The command with parameters to execute
981
     *
982
     * @access public
983
     * @return mixed True if successful, otherwise PEAR::Error
984
     * @see NET_FTP_ERR_SITE_FAILED
985
     */
986
    function site($command)
987
    {
988
        $res = @ftp_site($this->_handle, $command);
989
        if (!$res) {
990
            return $this->raiseError("Execution of SITE command '$command' failed.",
991
                                     NET_FTP_ERR_SITE_FAILED);
992
        } else {
993
            return $res;
994
        }
995
    }
996

    
997
    /**
998
     * This method will try to chmod the file specified on the server
999
     * Currently, you must give a number as the the permission argument (777 or
1000
     * similar). The file can be either a relative or absolute path.
1001
     * NOTE: Some servers do not support this feature. In that case, you will
1002
     * get a PEAR error object returned. If successful, the method returns true
1003
     *
1004
     * @param mixed   $target      The file or array of files to set permissions for
1005
     * @param integer $permissions The mode to set the file permissions to
1006
     *
1007
     * @access public
1008
     * @return mixed True if successful, otherwise PEAR::Error
1009
     * @see NET_FTP_ERR_CHMOD_FAILED
1010
     */
1011
    function chmod($target, $permissions)
1012
    {
1013
        // If $target is an array: Loop through it.
1014
        if (is_array($target)) {
1015

    
1016
            for ($i = 0; $i < count($target); $i++) {
1017
                $res = $this->chmod($target[$i], $permissions);
1018
                if (PEAR::isError($res)) {
1019
                    return $res;
1020
                } // end if isError
1021
            } // end for i < count($target)
1022
            
1023
            return true;
1024
        } else {
1025

    
1026
            $res = $this->site("CHMOD " . $permissions . " " . $target);
1027
            if (!$res) {
1028
                return PEAR::raiseError("CHMOD " . $permissions . " " . $target .
1029
                                        " failed", NET_FTP_ERR_CHMOD_FAILED);
1030
            } else {
1031
                return $res;
1032
            }
1033

    
1034
        } // end if is_array
1035

    
1036
    } // end method chmod
1037

    
1038
    /**
1039
     * This method will try to chmod a folder and all of its contents
1040
     * on the server. The target argument must be a folder or an array of folders
1041
     * and the permissions argument have to be an integer (i.e. 777).
1042
     * The file can be either a relative or absolute path.
1043
     * NOTE: Some servers do not support this feature. In that case, you
1044
     * will get a PEAR error object returned. If successful, the method
1045
     * returns true
1046
     *
1047
     * @param mixed   $target      The folder or array of folders to
1048
     *                             set permissions for
1049
     * @param integer $permissions The mode to set the folder
1050
     *                             and file permissions to
1051
     *
1052
     * @access public
1053
     * @return mixed True if successful, otherwise PEAR::Error
1054
     * @see NET_FTP_ERR_CHMOD_FAILED, NET_FTP_ERR_DETERMINEPATH_FAILED,
1055
     *      NET_FTP_ERR_RAWDIRLIST_FAILED, NET_FTP_ERR_DIRLIST_UNSUPPORTED
1056
     */
1057
    function chmodRecursive($target, $permissions)
1058
    {
1059
        static $dir_permissions;
1060

    
1061
        if (!isset($dir_permissions)) { // Making directory specific permissions
1062
            $dir_permissions = $this->_makeDirPermissions($permissions);
1063
        }
1064

    
1065
        // If $target is an array: Loop through it
1066
        if (is_array($target)) {
1067

    
1068
            for ($i = 0; $i < count($target); $i++) {
1069
                $res = $this->chmodRecursive($target[$i], $permissions);
1070
                if (PEAR::isError($res)) {
1071
                    return $res;
1072
                } // end if isError
1073
            } // end for i < count($target)
1074

    
1075
        } else {
1076

    
1077
            $remote_path = $this->_constructPath($target);
1078

    
1079
            // Chmod the directory itself
1080
            $result = $this->chmod($remote_path, $dir_permissions);
1081

    
1082
            if (PEAR::isError($result)) {
1083
                return $result;
1084
            }
1085

    
1086
            // If $remote_path last character is not a slash, add one
1087
            if (substr($remote_path, strlen($remote_path)-1) != "/") {
1088

    
1089
                $remote_path .= "/";
1090
            }
1091

    
1092
            $dir_list = array();
1093
            $mode     = NET_FTP_DIRS_ONLY;
1094
            $dir_list = $this->ls($remote_path, $mode);
1095
            foreach ($dir_list as $dir_entry) {
1096
                if ($dir_entry['name'] == '.' || $dir_entry['name'] == '..') {
1097
                    continue;
1098
                }
1099
                
1100
                $remote_path_new = $remote_path.$dir_entry["name"]."/";
1101

    
1102
                // Chmod the directory we're about to enter
1103
                $result = $this->chmod($remote_path_new, $dir_permissions);
1104

    
1105
                if (PEAR::isError($result)) {
1106
                    return $result;
1107
                }
1108

    
1109
                $result = $this->chmodRecursive($remote_path_new, $permissions);
1110

    
1111
                if (PEAR::isError($result)) {
1112
                    return $result;
1113
                }
1114

    
1115
            } // end foreach dir_list as dir_entry
1116

    
1117
            $file_list = array();
1118
            $mode      = NET_FTP_FILES_ONLY;
1119
            $file_list = $this->ls($remote_path, $mode);
1120
    
1121
            if (PEAR::isError($file_list)) {
1122
                return $file_list;
1123
            }
1124

    
1125
            foreach ($file_list as $file_entry) {
1126

    
1127
                $remote_file = $remote_path.$file_entry["name"];
1128

    
1129
                $result = $this->chmod($remote_file, $permissions);
1130

    
1131
                if (PEAR::isError($result)) {
1132
                    return $result;
1133
                }
1134

    
1135
            } // end foreach $file_list
1136

    
1137
        } // end if is_array
1138

    
1139
        return true; // No errors
1140

    
1141
    } // end method chmodRecursive
1142

    
1143
    /**
1144
     * Rename or move a file or a directory from the ftp-server
1145
     *
1146
     * @param string $remote_from The remote file or directory original to rename or
1147
     *                            move
1148
     * @param string $remote_to   The remote file or directory final to rename or
1149
     *                            move
1150
     *
1151
     * @access public
1152
     * @return bool $res True on success, otherwise PEAR::Error
1153
     * @see NET_FTP_ERR_RENAME_FAILED
1154
     */
1155
    function rename ($remote_from, $remote_to) 
1156
    {
1157
        $res = @ftp_rename($this->_handle, $remote_from, $remote_to);
1158
        if (!$res) {
1159
            return $this->raiseError("Could not rename ".$remote_from." to ".
1160
                                     $remote_to." !", NET_FTP_ERR_RENAME_FAILED);
1161
        }
1162
        return true;
1163
    }
1164

    
1165
    /**
1166
     * This will return logical permissions mask for directory.
1167
     * if directory has to be readable it have also be executable
1168
     *
1169
     * @param string $permissions File permissions in digits for file (i.e. 666)
1170
     *
1171
     * @access private
1172
     * @return string File permissions in digits for directory (i.e. 777)
1173
     */
1174
    function _makeDirPermissions($permissions)
1175
    {
1176
        $permissions = (string)$permissions;
1177
        
1178
        // going through (user, group, world)
1179
        for ($i = 0; $i < strlen($permissions); $i++) {
1180
            // Read permission is set but execute not yet
1181
            if ((int)$permissions[$i] & 4 and !((int)$permissions[$i] & 1)) {
1182
                // Adding execute flag
1183
                $permissions[$i] = (int)$permissions[$i] + 1;
1184
            }
1185
        }
1186

    
1187
        return (string)$permissions;
1188
    }
1189

    
1190
    /**
1191
     * This will return the last modification-time of a file. You can either give
1192
     * this function a relative or an absolute path to the file to check.
1193
     * NOTE: Some servers will not support this feature and the function works
1194
     * only on files, not directories! When successful,
1195
     * it will return the last modification-time as a unix-timestamp or, when
1196
     * $format is specified, a preformated timestring.
1197
     *
1198
     * @param string $file   The file to check
1199
     * @param string $format (optional) The format to give the date back 
1200
     *                       if not set, it will return a Unix timestamp
1201
     *
1202
     * @access public
1203
     * @return mixed Unix timestamp, a preformated date-string or PEAR::Error
1204
     * @see NET_FTP_ERR_MDTMDIR_UNSUPPORTED, NET_FTP_ERR_MDTM_FAILED,
1205
     *      NET_FTP_ERR_DATEFORMAT_FAILED
1206
     */
1207
    function mdtm($file, $format = null)
1208
    {
1209
        $file = $this->_constructPath($file);
1210
        if ($this->_checkRemoteDir($file) !== false) {
1211
            return $this->raiseError("Filename '$file' seems to be a directory.",
1212
                                     NET_FTP_ERR_MDTMDIR_UNSUPPORTED);
1213
        }
1214
        $res = @ftp_mdtm($this->_handle, $file);
1215
        if ($res == -1) {
1216
            return $this->raiseError("Could not get last-modification-date of '".
1217
                                     $file."'.", NET_FTP_ERR_MDTM_FAILED);
1218
        }
1219
        if (isset($format)) {
1220
            $res = date($format, $res);
1221
            if (!$res) {
1222
                return $this->raiseError("Date-format failed on timestamp '".$res.
1223
                                         "'.", NET_FTP_ERR_DATEFORMAT_FAILED);
1224
            }
1225
        }
1226
        return $res;
1227
    }
1228

    
1229
    /**
1230
     * This will return the size of a given file in bytes. You can either give this
1231
     * function a relative or an absolute file-path. NOTE: Some servers do not
1232
     * support this feature!
1233
     *
1234
     * @param string $file The file to check
1235
     *
1236
     * @access public
1237
     * @return mixed Size in bytes or PEAR::Error
1238
     * @see NET_FTP_ERR_SIZE_FAILED
1239
     */
1240
    function size($file)
1241
    {
1242
        $file = $this->_constructPath($file);
1243
        $res  = @ftp_size($this->_handle, $file);
1244
        if ($res == -1) {
1245
            return $this->raiseError("Could not determine filesize of '$file'.",
1246
                                     NET_FTP_ERR_SIZE_FAILED);
1247
        } else {
1248
            return $res;
1249
        }
1250
    }
1251

    
1252
    /**
1253
     * This method returns a directory-list of the current directory or given one.
1254
     * To display the current selected directory, simply set the first parameter to
1255
     * null
1256
     * or leave it blank, if you do not want to use any other parameters.
1257
     * <br><br>
1258
     * There are 4 different modes of listing directories. Either to list only
1259
     * the files (using NET_FTP_FILES_ONLY), to list only directories (using
1260
     * NET_FTP_DIRS_ONLY) or to show both (using NET_FTP_DIRS_FILES, which is
1261
     * default).
1262
     * <br><br>
1263
     * The 4th one is the NET_FTP_RAWLIST, which returns just the array created by
1264
     * the ftp_rawlist()-function build into PHP.
1265
     * <br><br>
1266
     * The other function-modes will return an array containing the requested data.
1267
     * The files and dirs are listed in human-sorted order, but if you select
1268
     * NET_FTP_DIRS_FILES the directories will be added above the files,
1269
     * but although both sorted.
1270
     * <br><br>
1271
     * All elements in the arrays are associative arrays themselves. They have the
1272
     * following structure:
1273
     * <br><br>
1274
     * Dirs:<br>
1275
     *           ["name"]        =>  string The name of the directory<br>
1276
     *           ["rights"]      =>  string The rights of the directory (in style
1277
     *                               "rwxr-xr-x")<br>
1278
     *           ["user"]        =>  string The owner of the directory<br>
1279
     *           ["group"]       =>  string The group-owner of the directory<br>
1280
     *           ["files_inside"]=>  string The number of files/dirs inside the
1281
     *                               directory excluding "." and ".."<br>
1282
     *           ["date"]        =>  int The creation-date as Unix timestamp<br>
1283
     *           ["is_dir"]      =>  bool true, cause this is a dir<br>
1284
     * <br><br>
1285
     * Files:<br>
1286
     *           ["name"]        =>  string The name of the file<br>
1287
     *           ["size"]        =>  int Size in bytes<br>
1288
     *           ["rights"]      =>  string The rights of the file (in style 
1289
     *                               "rwxr-xr-x")<br>
1290
     *           ["user"]        =>  string The owner of the file<br>
1291
     *           ["group"]       =>  string The group-owner of the file<br>
1292
     *           ["date"]        =>  int The creation-date as Unix timestamp<br>
1293
     *           ["is_dir"]      =>  bool false, cause this is a file<br>
1294
     *
1295
     * @param string $dir  (optional) The directory to list or null, when listing
1296
     *                     the current directory.
1297
     * @param int    $mode (optional) The mode which types to list (files,
1298
     *                     directories or both).
1299
     *
1300
     * @access public
1301
     * @return mixed The directory list as described above or PEAR::Error on failure
1302
     * @see NET_FTP_DIRS_FILES, NET_FTP_DIRS_ONLY, NET_FTP_FILES_ONLY,
1303
     *      NET_FTP_RAWLIST, NET_FTP_ERR_DETERMINEPATH_FAILED,
1304
     *      NET_FTP_ERR_RAWDIRLIST_FAILED, NET_FTP_ERR_DIRLIST_UNSUPPORTED
1305
     */
1306
    function ls($dir = null, $mode = NET_FTP_DIRS_FILES)
1307
    {
1308
        if (!isset($dir)) {
1309
            $dir = @ftp_pwd($this->_handle);
1310
            if (!$dir) {
1311
                return $this->raiseError("Could not retrieve current directory",
1312
                                         NET_FTP_ERR_DETERMINEPATH_FAILED);
1313
            }
1314
        }
1315
        if (($mode != NET_FTP_FILES_ONLY) && ($mode != NET_FTP_DIRS_ONLY) &&
1316
            ($mode != NET_FTP_RAWLIST)) {
1317
            $mode = NET_FTP_DIRS_FILES;
1318
        }
1319

    
1320
        switch ($mode) {
1321
        case NET_FTP_DIRS_FILES:
1322
            $res = $this->_lsBoth($dir);
1323
            break;
1324
        case NET_FTP_DIRS_ONLY:
1325
            $res = $this->_lsDirs($dir);
1326
            break;
1327
        case NET_FTP_FILES_ONLY:
1328
            $res = $this->_lsFiles($dir);
1329
            break;
1330
        case NET_FTP_RAWLIST:
1331
            $res = @ftp_rawlist($this->_handle, $dir);
1332
            break;
1333
        }
1334

    
1335
        return $res;
1336
    }
1337

    
1338
    /**
1339
     * This method will delete the given file or directory ($path) from the server
1340
     * (maybe recursive).
1341
     *
1342
     * Whether the given string is a file or directory is only determined by the
1343
     * last sign inside the string ("/" or not).
1344
     *
1345
     * If you specify a directory, you can optionally specify $recursive as true,
1346
     * to let the directory be deleted recursive (with all sub-directories and files
1347
     * inherited).
1348
     *
1349
     * You can either give a absolute or relative path for the file / dir. If you
1350
     * choose to use the relative path, it will be automatically completed with the
1351
     * actual selected directory.
1352
     *
1353
     * @param string $path      The absolute or relative path to the file/directory.
1354
     * @param bool   $recursive Recursively delete everything in $path
1355
     * @param bool   $filesonly When deleting recursively, only delete files so the
1356
     *                          directory structure is preserved
1357
     *
1358
     * @access public
1359
     * @return mixed True on success, otherwise PEAR::Error
1360
     * @see NET_FTP_ERR_DELETEFILE_FAILED, NET_FTP_ERR_DELETEDIR_FAILED,
1361
     *      NET_FTP_ERR_REMOTEPATHNODIR
1362
     */
1363
    function rm($path, $recursive = false, $filesonly = false)
1364
    {
1365
        $path = $this->_constructPath($path);
1366
        if ($this->_checkRemoteDir($path) === true) {
1367
            if ($recursive) {
1368
                return $this->_rmDirRecursive($path, $filesonly);
1369
            } else {
1370
                return $this->_rmDir($path);
1371
            }
1372
        } else {
1373
            return $this->_rmFile($path);
1374
        }
1375
    }
1376

    
1377
    /**
1378
     * This function will download a file from the ftp-server. You can either
1379
     * specify an absolute path to the file (beginning with "/") or a relative one,
1380
     * which will be completed with the actual directory you selected on the server.
1381
     * You can specify the path to which the file will be downloaded on the local
1382
     * machine, if the file should be overwritten if it exists (optionally, default
1383
     * is no overwriting) and in which mode (FTP_ASCII or FTP_BINARY) the file
1384
     * should be downloaded (if you do not specify this, the method tries to
1385
     * determine it automatically from the mode-directory or uses the default-mode,
1386
     * set by you).
1387
     * If you give a relative path to the local-file, the script-path is used as
1388
     * basepath.
1389
     *
1390
     * @param string $remote_file The absolute or relative path to the file to
1391
     *                            download
1392
     * @param string $local_file  The local file to put the downloaded in
1393
     * @param bool   $overwrite   (optional) Whether to overwrite existing file
1394
     * @param int    $mode        (optional) Either FTP_ASCII or FTP_BINARY
1395
     *
1396
     * @access public
1397
     * @return mixed True on success, otherwise PEAR::Error
1398
     * @see NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN,
1399
     *      NET_FTP_ERR_OVERWRITELOCALFILE_FAILED,
1400
     *      NET_FTP_ERR_OVERWRITELOCALFILE_FAILED
1401
     */
1402
    function get($remote_file, $local_file, $overwrite = false, $mode = null)
1403
    {
1404
        if (!isset($mode)) {
1405
            $mode = $this->checkFileExtension($remote_file);
1406
        }
1407

    
1408
        $remote_file = $this->_constructPath($remote_file);
1409

    
1410
        if (@file_exists($local_file) && !$overwrite) {
1411
            return $this->raiseError("Local file '".$local_file.
1412
                                     "' exists and may not be overwriten.",
1413
                                     NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN);
1414
        }
1415
        if (@file_exists($local_file) &&
1416
            !@is_writeable($local_file) && $overwrite) {
1417
            return $this->raiseError("Local file '".$local_file.
1418
                                     "' is not writeable. Can not overwrite.",
1419
                                     NET_FTP_ERR_OVERWRITELOCALFILE_FAILED);
1420
        }
1421

    
1422
        if (@function_exists('ftp_nb_get')) {
1423
            $res = @ftp_nb_get($this->_handle, $local_file, $remote_file, $mode);
1424
            while ($res == FTP_MOREDATA) {
1425
                $this->_announce('nb_get');
1426
                $res = @ftp_nb_continue($this->_handle);
1427
            }
1428
        } else {
1429
            $res = @ftp_get($this->_handle, $local_file, $remote_file, $mode);
1430
        }
1431
        if (!$res) {
1432
            return $this->raiseError("File '".$remote_file.
1433
                                     "' could not be downloaded to '$local_file'.",
1434
                                     NET_FTP_ERR_OVERWRITELOCALFILE_FAILED);
1435
        } else {
1436
            return true;
1437
        }
1438
    }
1439

    
1440
    /**
1441
     * This function will upload a file to the ftp-server. You can either specify a
1442
     * absolute path to the remote-file (beginning with "/") or a relative one,
1443
     * which will be completed with the actual directory you selected on the server.
1444
     * You can specify the path from which the file will be uploaded on the local
1445
     * maschine, if the file should be overwritten if it exists (optionally, default
1446
     * is no overwriting) and in which mode (FTP_ASCII or FTP_BINARY) the file
1447
     * should be downloaded (if you do not specify this, the method tries to
1448
     * determine it automatically from the mode-directory or uses the default-mode,
1449
     * set by you).
1450
     * If you give a relative path to the local-file, the script-path is used as
1451
     * basepath.
1452
     *
1453
     * @param string $local_file  The local file to upload
1454
     * @param string $remote_file The absolute or relative path to the file to
1455
     *                            upload to
1456
     * @param bool   $overwrite   (optional) Whether to overwrite existing file
1457
     * @param int    $mode        (optional) Either FTP_ASCII or FTP_BINARY
1458
     * @param int    $options     (optional) Flags describing the behaviour of this
1459
     *                            function. Currently NET_FTP_BLOCKING and 
1460
     *                            NET_FTP_NONBLOCKING are supported, of which
1461
     *                            NET_FTP_NONBLOCKING is the default.
1462
     *
1463
     * @access public
1464
     * @return mixed True on success, otherwise PEAR::Error
1465
     * @see NET_FTP_ERR_LOCALFILENOTEXIST,
1466
     *      NET_FTP_ERR_OVERWRITEREMOTEFILE_FORBIDDEN,
1467
     *      NET_FTP_ERR_UPLOADFILE_FAILED, NET_FTP_NONBLOCKING, NET_FTP_BLOCKING
1468
     */
1469
    function put($local_file, $remote_file, $overwrite = false, $mode = null,
1470
        $options = 0)
1471
    {
1472
        if ($options & (NET_FTP_BLOCKING | NET_FTP_NONBLOCKING) === 
1473
            (NET_FTP_BLOCKING | NET_FTP_NONBLOCKING)) {
1474
            return $this->raiseError('Bad options given: NET_FTP_NONBLOCKING and '.
1475
                                     'NET_FTP_BLOCKING can\'t both be set',
1476
                                     NET_FTP_ERR_BADOPTIONS);
1477
        }
1478
        
1479
        $usenb = ! ($options & (NET_FTP_BLOCKING == NET_FTP_BLOCKING));
1480
        
1481
        if (!isset($mode)) {
1482
            $mode = $this->checkFileExtension($local_file);
1483
        }
1484
        $remote_file = $this->_constructPath($remote_file);
1485

    
1486
        if (!@file_exists($local_file)) {
1487
            return $this->raiseError("Local file '$local_file' does not exist.",
1488
                                     NET_FTP_ERR_LOCALFILENOTEXIST);
1489
        }
1490
        if ((@ftp_size($this->_handle, $remote_file) != -1) && !$overwrite) {
1491
            return $this->raiseError("Remote file '".$remote_file.
1492
                                     "' exists and may not be overwriten.",
1493
                                     NET_FTP_ERR_OVERWRITEREMOTEFILE_FORBIDDEN);
1494
        }
1495

    
1496
        if (function_exists('ftp_alloc')) {
1497
            ftp_alloc($this->_handle, filesize($local_file));
1498
        }
1499
        if ($usenb && function_exists('ftp_nb_put')) {
1500
            $res = @ftp_nb_put($this->_handle, $remote_file, $local_file, $mode);
1501
            while ($res == FTP_MOREDATA) {
1502
                $this->_announce('nb_put');
1503
                $res = @ftp_nb_continue($this->_handle);
1504
            }
1505

    
1506
        } else {
1507
            $res = @ftp_put($this->_handle, $remote_file, $local_file, $mode);
1508
        }
1509
        if (!$res) {
1510
            return $this->raiseError("File '$local_file' could not be uploaded to '"
1511
                                     .$remote_file."'.",
1512
                                     NET_FTP_ERR_UPLOADFILE_FAILED);
1513
        } else {
1514
            return true;
1515
        }
1516
    }
1517

    
1518
    /**
1519
     * This functionality allows you to transfer a whole directory-structure from
1520
     * the remote-ftp to your local host. You have to give a remote-directory
1521
     * (ending with '/') and the local directory (ending with '/') where to put the
1522
     * files you download.
1523
     * The remote path is automatically completed with the current-remote-dir, if
1524
     * you give a relative path to this function. You can give a relative path for
1525
     * the $local_path, too. Then the script-basedir will be used for comletion of
1526
     * the path.
1527
     * The parameter $overwrite will determine, whether to overwrite existing files
1528
     * or not. Standard for this is false. Fourth you can explicitly set a mode for
1529
     * all transfer actions done. If you do not set this, the method tries to
1530
     * determine the transfer mode by checking your mode-directory for the file
1531
     * extension. If the extension is not inside the mode-directory, it will get
1532
     * your default mode.
1533
     * 
1534
     * Since 1.4 no error will be returned when a file exists while $overwrite is 
1535
     * set to false. 
1536
     *
1537
     * @param string $remote_path The path to download
1538
     * @param string $local_path  The path to download to
1539
     * @param bool   $overwrite   (optional) Whether to overwrite existing files
1540
     *                            (true) or not (false, standard).
1541
     * @param int    $mode        (optional) The transfermode (either FTP_ASCII or
1542
     *                            FTP_BINARY).
1543
     * @param array  $excluded_paths (optional) List of remote files or directories to
1544
     *                               exclude from the transfer. All files and 
1545
     *                               directories must be stated as absolute paths.
1546
     *                               Note: You must include a trailing slash on directory names.
1547
     *
1548
     * @access public
1549
     * @return mixed True on succes, otherwise PEAR::Error
1550
     * @see NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN,
1551
     * NET_FTP_ERR_OVERWRITELOCALFILE_FAILED, NET_FTP_ERR_OVERWRITELOCALFILE_FAILED,
1552
     * NET_FTP_ERR_REMOTEPATHNODIR, NET_FTP_ERR_LOCALPATHNODIR,
1553
     * NET_FTP_ERR_CREATELOCALDIR_FAILED
1554
     */
1555
    function getRecursive($remote_path, $local_path, $overwrite = false,
1556
                          $mode = null, $excluded_paths = array())
1557
    {
1558
        $remote_path = $this->_constructPath($remote_path);
1559
        if ($this->_checkRemoteDir($remote_path) !== true) {
1560
            return $this->raiseError("Given remote-path '".$remote_path.
1561
                                     "' seems not to be a directory.",
1562
                                     NET_FTP_ERR_REMOTEPATHNODIR);
1563
        }
1564

    
1565
        if (!@file_exists($local_path)) {
1566
            $res = @mkdir($local_path);
1567
            if (!$res) {
1568
                return $this->raiseError("Could not create dir '$local_path'",
1569
                                         NET_FTP_ERR_CREATELOCALDIR_FAILED);
1570
            }
1571
        } elseif (!@is_dir($local_path)) {
1572
            return $this->raiseError("Given local-path '".$local_path.
1573
                                     "' seems not to be a directory.",
1574
                                     NET_FTP_ERR_LOCALPATHNODIR);
1575
        }
1576
        
1577
        $dir_list = array();
1578
        $dir_list = $this->ls($remote_path, NET_FTP_DIRS_ONLY);
1579
        if (PEAR::isError($dir_list)) {
1580
            return $dir_list;
1581
        }
1582
        foreach ($dir_list as $dir_entry) {
1583
            if ($dir_entry['name'] != '.' && $dir_entry['name'] != '..') {
1584
                $remote_path_new = $remote_path.$dir_entry["name"]."/";
1585
                $local_path_new  = $local_path.$dir_entry["name"]."/";
1586

    
1587
                // Check whether the directory should be excluded
1588
                if (!in_array($remote_path_new, $excluded_paths)) {
1589
                    $result = $this->getRecursive($remote_path_new,
1590
                                   $local_path_new, $overwrite, $mode);
1591
                    if ($this->isError($result)) {
1592
                        return $result;
1593
                    }
1594
                }
1595
            }
1596
        }
1597
        $file_list = array();
1598
        $file_list = $this->ls($remote_path, NET_FTP_FILES_ONLY);
1599
        if (PEAR::isError($file_list)) {
1600
            return $file_list;
1601
        }
1602
        foreach ($file_list as $file_entry) {
1603
            $remote_file = $remote_path.$file_entry["name"];
1604
            $local_file  = $local_path.$file_entry["name"];
1605

    
1606
            // Check whether the file should be excluded
1607
            if (!in_array($remote_file, $excluded_paths)) {
1608
                $result = $this->get($remote_file, $local_file, $overwrite, $mode);
1609
                if ($this->isError($result) &&
1610
                    $result->getCode() != NET_FTP_ERR_OVERWRITELOCALFILE_FORBIDDEN) {
1611
                    return $result;
1612
                }
1613
            }
1614
        }
1615
        return true;
1616
    }
1617

    
1618
    /**
1619
     * This functionality allows you to transfer a whole directory-structure from
1620
     * your local host to the remote-ftp. You have to give a remote-directory
1621
     * (ending with '/') and the local directory (ending with '/') where to put the
1622
     * files you download. The remote path is automatically completed with the
1623
     * current-remote-dir, if you give a relative path to this function. You can
1624
     * give a relative path for the $local_path, too. Then the script-basedir will
1625
     * be used for comletion of the path.
1626
     * The parameter $overwrite will determine, whether to overwrite existing files
1627
     * or not.
1628
     * Standard for this is false. Fourth you can explicitly set a mode for all
1629
     * transfer actions done. If you do not set this, the method tries to determine
1630
     * the transfer mode by checking your mode-directory for the file-extension. If
1631
     * the extension is not inside the mode-directory, it will get your default
1632
     * mode.
1633
     *
1634
     * @param string $local_path     The path to download to
1635
     * @param string $remote_path    The path to download
1636
     * @param bool   $overwrite      (optional) Whether to overwrite existing files
1637
     *                               (true) or not (false, standard).
1638
     * @param int    $mode           (optional) The transfermode (either FTP_ASCII or
1639
     *                               FTP_BINARY).
1640
     * @param array  $excluded_paths (optional) List of local files or directories to
1641
     *                               exclude from the transfer. All files and 
1642
     *                               directories must be stated as absolute paths.
1643
     *                               Note: You must include a trailing slash on 
1644
     *                               directory names.
1645
     *
1646
     * @access public
1647
     * @return mixed True on succes, otherwise PEAR::Error
1648
     * @see NET_FTP_ERR_LOCALFILENOTEXIST,
1649
     *      NET_FTP_ERR_OVERWRITEREMOTEFILE_FORBIDDEN,
1650
     *      NET_FTP_ERR_UPLOADFILE_FAILED, NET_FTP_ERR_LOCALPATHNODIR,
1651
     *      NET_FTP_ERR_REMOTEPATHNODIR
1652
     */
1653
    function putRecursive($local_path, $remote_path, $overwrite = false,
1654
                          $mode = null, $excluded_paths = array())
1655
    {
1656
        $remote_path = $this->_constructPath($remote_path);
1657
        if (!file_exists($local_path) || !is_dir($local_path)) {
1658
            return $this->raiseError("Given local-path '".$local_path.
1659
                                     "' seems not to be a directory.",
1660
                                     NET_FTP_ERR_LOCALPATHNODIR);
1661
        }
1662
        // try to create directory if it doesn't exist
1663
        $old_path = $this->pwd();
1664
        if ($this->isError($this->cd($remote_path))) {
1665
            $res = $this->mkdir($remote_path);
1666
            if ($this->isError($res)) {
1667
                return $res;
1668
            }
1669
        }
1670
        $this->cd($old_path);
1671
        if ($this->_checkRemoteDir($remote_path) !== true) {
1672
            return $this->raiseError("Given remote-path '".$remote_path.
1673
                                     "' seems not to be a directory.",
1674
                                     NET_FTP_ERR_REMOTEPATHNODIR);
1675
        }
1676
        $dir_list = $this->_lsLocal($local_path);
1677
        foreach ($dir_list["dirs"] as $dir_entry) {
1678
            // local directories do not have arrays as entry
1679
            $remote_path_new = $remote_path.$dir_entry."/";
1680
            $local_path_new  = $local_path.$dir_entry."/";
1681

    
1682
            // Check whether the directory should be excluded
1683
            if (!in_array($local_path_new, $excluded_paths)) {
1684
                $result          = $this->putRecursive($local_path_new,
1685
                                   $remote_path_new, $overwrite, $mode,
1686
                                   $excluded_paths);
1687
                if ($this->isError($result)) {
1688
                    return $result;
1689
                }
1690
            }
1691
        }
1692

    
1693
        foreach ($dir_list["files"] as $file_entry) {
1694
            $remote_file = $remote_path.$file_entry;
1695
            $local_file  = $local_path.$file_entry;
1696

    
1697
            // Check whether the file should be excluded
1698
            if (!in_array($local_file, $excluded_paths)) {
1699
                $result = $this->put($local_file, $remote_file, $overwrite, $mode);
1700
                if ($this->isError($result)) {
1701
                    return $result;
1702
                }
1703
            }
1704
        }
1705
        return true;
1706
    }
1707

    
1708
    /**
1709
     * This checks, whether a file should be transfered in ascii- or binary-mode
1710
     * by it's file-extension. If the file-extension is not set or
1711
     * the extension is not inside one of the extension-dirs, the actual set
1712
     * transfer-mode is returned.
1713
     *
1714
     * @param string $filename The filename to be checked
1715
     *
1716
     * @access public
1717
     * @return int Either FTP_ASCII or FTP_BINARY
1718
     */
1719
    function checkFileExtension($filename)
1720
    {
1721
        if (($pos = strrpos($filename, '.')) === false) {
1722
            return $this->_mode;
1723
        } else {
1724
            $ext = substr($filename, $pos + 1);
1725
        }
1726
        
1727
        if (isset($this->_file_extensions[$ext])) {
1728
            return $this->_file_extensions[$ext];
1729
        }
1730
        
1731
        return $this->_mode;
1732
    }
1733

    
1734
    /**
1735
     * Set the hostname
1736
     *
1737
     * @param string $host The hostname to set
1738
     *
1739
     * @access public
1740
     * @return bool True on success, otherwise PEAR::Error
1741
     * @see NET_FTP_ERR_HOSTNAMENOSTRING
1742
     */
1743
    function setHostname($host)
1744
    {
1745
        if (!is_string($host)) {
1746
            return PEAR::raiseError("Hostname must be a string.",
1747
                                    NET_FTP_ERR_HOSTNAMENOSTRING);
1748
        }
1749
        $this->_hostname = $host;
1750
        return true;
1751
    }
1752

    
1753
    /**
1754
     * Set the Port
1755
     *
1756
     * @param int $port The port to set
1757
     *
1758
     * @access public
1759
     * @return bool True on success, otherwise PEAR::Error
1760
     * @see NET_FTP_ERR_PORTLESSZERO
1761
     */
1762
    function setPort($port)
1763
    {
1764
        if (!is_int($port) || ($port < 0)) {
1765
            PEAR::raiseError("Invalid port. Has to be integer >= 0",
1766
                             NET_FTP_ERR_PORTLESSZERO);
1767
        }
1768
        $this->_port = $port;
1769
        return true;
1770
    }
1771

    
1772
    /**
1773
     * Set to connect through secure SSL connection
1774
     *
1775
     * @access public
1776
     * @return bool True on success, otherwise PEAR::Error
1777
     */
1778
    function setSsl()
1779
    {
1780
        if (!function_exists('ftp_ssl_connect')) {
1781
            return PEAR::raiseError('SSL connection not supported. Function ftp_ssl_connect does not exist.',
1782
                   NET_FTP_ERR_NOSSL);
1783
        }
1784
        $this->_ssl = true;
1785
        return true;
1786
    }
1787

    
1788
    /**
1789
     * Set the Username
1790
     *
1791
     * @param string $user The username to set
1792
     *
1793
     * @access public
1794
     * @return mixed True on success, otherwise PEAR::Error
1795
     * @see NET_FTP_ERR_USERNAMENOSTRING
1796
     */
1797
    function setUsername($user)
1798
    {
1799
        if (empty($user) || !is_string($user)) {
1800
            return PEAR::raiseError('Username $user invalid.',
1801
                   NET_FTP_ERR_USERNAMENOSTRING);
1802
        }
1803
        $this->_username = $user;
1804
        return true;
1805
    }
1806

    
1807
    /**
1808
     * Set the password
1809
     *
1810
     * @param string $password The password to set
1811
     *
1812
     * @access private
1813
     * @return mixed True on success, otherwise PEAR::Error
1814
     * @see NET_FTP_ERR_PASSWORDNOSTRING
1815
     */
1816
    function setPassword($password)
1817
    {
1818
        if (empty($password) || !is_string($password)) {
1819
            return PEAR::raiseError('Password xxx invalid.',
1820
                                    NET_FTP_ERR_PASSWORDNOSTRING);
1821
        }
1822
        $this->_password = $password;
1823
        return true;
1824
    }
1825

    
1826
    /**
1827
     * Set the transfer-mode. You can use the predefined constants
1828
     * FTP_ASCII or FTP_BINARY. The mode will be stored for any further transfers.
1829
     *
1830
     * @param int $mode The mode to set
1831
     *
1832
     * @access public
1833
     * @return mixed True on success, otherwise PEAR::Error
1834
     * @see NET_FTP_ERR_NOMODECONST
1835
     */
1836
    function setMode($mode)
1837
    {
1838
        if (($mode == FTP_ASCII) || ($mode == FTP_BINARY)) {
1839
            $this->_mode = $mode;
1840
            return true;
1841
        } else {
1842
            return $this->raiseError('FTP-Mode has either to be FTP_ASCII or'.
1843
                                     'FTP_BINARY', NET_FTP_ERR_NOMODECONST);
1844
        }
1845
    }
1846

    
1847
    /**
1848
     * Set the transfer-method to passive mode
1849
     *
1850
     * @access public
1851
     * @return void
1852
     */
1853
    function setPassive()
1854
    {
1855
        $this->_passv = true;
1856
        if ($this->_handle != null && $this->_loggedin) {
1857
            @ftp_pasv($this->_handle, true);
1858
        }
1859
    }
1860

    
1861
    /**
1862
     * Set the transfer-method to active mode
1863
     *
1864
     * @access public
1865
     * @return void
1866
     */
1867
    function setActive()
1868
    {
1869
        $this->_passv = false;
1870
        if ($this->_handle != null && $this->_loggedin) {
1871
            @ftp_pasv($this->_handle, false);
1872
        }
1873
    }
1874

    
1875
    /**
1876
     * Set the timeout for FTP operations
1877
     *
1878
     * Use this method to set a timeout for FTP operation. Timeout has to be an
1879
     * integer.
1880
     *
1881
     * @param int $timeout the timeout to use
1882
     *
1883
     * @access public
1884
     * @return bool True on success, otherwise PEAR::Error
1885
     * @see NET_FTP_ERR_TIMEOUTLESSZERO, NET_FTP_ERR_SETTIMEOUT_FAILED
1886
     */
1887
    function setTimeout ( $timeout = 0 ) 
1888
    {
1889
        if (!is_int($timeout) || ($timeout < 0)) {
1890
            return PEAR::raiseError('Timeout '.$timeout.
1891
                                    ' is invalid, has to be an integer >= 0',
1892
                                    NET_FTP_ERR_TIMEOUTLESSZERO);
1893
        }
1894
        $this->_timeout = $timeout;
1895
        if (isset($this->_handle) && is_resource($this->_handle)) {
1896
            $res = @ftp_set_option($this->_handle, FTP_TIMEOUT_SEC, $timeout);
1897
        } else {
1898
            $res = true;
1899
        }
1900
        if (!$res) {
1901
            return PEAR::raiseError("Set timeout failed.",
1902
                                    NET_FTP_ERR_SETTIMEOUT_FAILED);
1903
        }
1904
        return true;
1905
    }        
1906

    
1907
    /**
1908
     * Adds an extension to a mode-directory
1909
     *
1910
     * The mode-directory saves file-extensions coresponding to filetypes
1911
     * (ascii e.g.: 'php', 'txt', 'htm',...; binary e.g.: 'jpg', 'gif', 'exe',...).
1912
     * The extensions have to be saved without the '.'. And
1913
     * can be predefined in an external file (see: getExtensionsFile()).
1914
     *
1915
     * The array is build like this: 'php' => FTP_ASCII, 'png' => FTP_BINARY
1916
     *
1917
     * To change the mode of an extension, just add it again with the new mode!
1918
     *
1919
     * @param int    $mode Either FTP_ASCII or FTP_BINARY
1920
     * @param string $ext  Extension
1921
     *
1922
     * @access public
1923
     * @return void
1924
     */
1925
    function addExtension($mode, $ext)
1926
    {
1927
        $this->_file_extensions[$ext] = $mode;
1928
    }
1929

    
1930
    /**
1931
     * This function removes an extension from the mode-directories 
1932
     * (described above).
1933
     *
1934
     * @param string $ext The extension to remove
1935
     *
1936
     * @access public
1937
     * @return void
1938
     */
1939
    function removeExtension($ext)
1940
    {
1941
        if (isset($this->_file_extensions[$ext])) {
1942
            unset($this->_file_extensions[$ext]);
1943
        }
1944
    }
1945

    
1946
    /**
1947
     * This get's both (ascii- and binary-mode-directories) from the given file.
1948
     * Beware, if you read a file into the mode-directory, all former set values 
1949
     * will be unset!
1950
     * 
1951
     * Example file contents:
1952
     * [ASCII]
1953
     * asc = 0
1954
     * txt = 0
1955
     * [BINARY]
1956
     * bin = 1
1957
     * jpg = 1
1958
     *
1959
     * @param string $filename The file to get from
1960
     *
1961
     * @access public
1962
     * @return mixed True on success, otherwise PEAR::Error
1963
     * @see NET_FTP_ERR_EXTFILENOTEXIST, NET_FTP_ERR_EXTFILEREAD_FAILED
1964
     */
1965
    function getExtensionsFile($filename)
1966
    {
1967
        if (!file_exists($filename)) {
1968
            return $this->raiseError("Extensions-file '$filename' does not exist",
1969
                                     NET_FTP_ERR_EXTFILENOTEXIST);
1970
        }
1971
        
1972
        if (!is_readable($filename)) {
1973
            return $this->raiseError("Extensions-file '$filename' is not readable",
1974
                                     NET_FTP_ERR_EXTFILEREAD_FAILED);
1975
        }
1976
        
1977
        $exts = @parse_ini_file($filename, true);
1978
        if (!is_array($exts)) {
1979
            return $this->raiseError("Extensions-file '$filename' could not be".
1980
                "loaded", NET_FTP_ERR_EXTFILELOAD_FAILED);
1981
        }
1982
        
1983
        $this->_file_extensions = array();
1984
        
1985
        if (isset($exts['ASCII'])) {
1986
            foreach (array_keys($exts['ASCII']) as $ext) {
1987
                $this->_file_extensions[$ext] = FTP_ASCII;
1988
            }
1989
        }
1990
        
1991
        if (isset($exts['BINARY'])) {
1992
            foreach (array_keys($exts['BINARY']) as $ext) {
1993
                $this->_file_extensions[$ext] = FTP_BINARY;
1994
            }
1995
        }
1996
        
1997
        return true;
1998
    }
1999

    
2000
    /**
2001
     * Returns the hostname
2002
     *
2003
     * @access public
2004
     * @return string The hostname
2005
     */
2006
    function getHostname()
2007
    {
2008
        return $this->_hostname;
2009
    }
2010

    
2011
    /**
2012
     * Returns the port
2013
     *
2014
     * @access public
2015
     * @return int The port
2016
     */
2017
    function getPort()
2018
    {
2019
        return $this->_port;
2020
    }
2021

    
2022
    /**
2023
     * Returns whether to connect through secure SSL connection
2024
     *
2025
     * @access public
2026
     * @return bool True if with SSL, false if without SSL
2027
     */
2028
    function getSsl()
2029
    {
2030
        return $this->_ssl;
2031
    }
2032

    
2033
    /**
2034
     * Returns the username
2035
     *
2036
     * @access public
2037
     * @return string The username
2038
     */
2039
    function getUsername()
2040
    {
2041
        return $this->_username;
2042
    }
2043

    
2044
    /**
2045
     * Returns the password
2046
     *
2047
     * @access public
2048
     * @return string The password
2049
     */
2050
    function getPassword()
2051
    {
2052
        return $this->_password;
2053
    }
2054

    
2055
    /**
2056
     * Returns the transfermode
2057
     *
2058
     * @access public
2059
     * @return int The transfermode, either FTP_ASCII or FTP_BINARY.
2060
     */
2061
    function getMode()
2062
    {
2063
        return $this->_mode;
2064
    }
2065

    
2066
    /**
2067
     * Returns, whether the connection is set to passive mode or not
2068
     *
2069
     * @access public
2070
     * @return bool True if passive-, false if active-mode
2071
     */
2072
    function isPassive()
2073
    {
2074
        return $this->_passv;
2075
    }
2076

    
2077
    /**
2078
     * Returns the mode set for a file-extension
2079
     *
2080
     * @param string $ext The extension you wanna ask for
2081
     *
2082
     * @return int Either FTP_ASCII, FTP_BINARY or NULL (if not set a mode for it)
2083
     * @access public
2084
     */
2085
    function getExtensionMode($ext)
2086
    {
2087
        return @$this->_file_extensions[$ext];
2088
    }
2089

    
2090
    /**
2091
     * Get the currently set timeout.
2092
     * Returns the actual timeout set.
2093
     *
2094
     * @access public
2095
     * @return int The actual timeout
2096
     */
2097
    function getTimeout()
2098
    {
2099
        return ftp_get_option($this->_handle, FTP_TIMEOUT_SEC);
2100
    }    
2101

    
2102
    /**
2103
     * Adds a Net_FTP_Observer instance to the list of observers 
2104
     * that are listening for messages emitted by this Net_FTP instance.
2105
     *
2106
     * @param object &$observer The Net_FTP_Observer instance to attach 
2107
     *                         as a listener.
2108
     *
2109
     * @return boolean True if the observer is successfully attached.
2110
     * @access public
2111
     * @since 1.3
2112
     */
2113
    function attach(&$observer)
2114
    {
2115
        if (!is_a($observer, 'Net_FTP_Observer')) {
2116
            return false;
2117
        }
2118

    
2119
        $this->_listeners[$observer->getId()] = &$observer;
2120
        return true;
2121
    }
2122

    
2123
    /**
2124
     * Removes a Net_FTP_Observer instance from the list of observers.
2125
     *
2126
     * @param object $observer The Net_FTP_Observer instance to detach 
2127
     *                         from the list of listeners.
2128
     *
2129
     * @return boolean True if the observer is successfully detached.
2130
     * @access public
2131
     * @since 1.3
2132
     */
2133
    function detach($observer)
2134
    {
2135
        if (!is_a($observer, 'Net_FTP_Observer') ||
2136
            !isset($this->_listeners[$observer->getId()])) {
2137
            return false;
2138
        }
2139

    
2140
        unset($this->_listeners[$observer->getId()]);
2141
        return true;
2142
    }
2143

    
2144
    /**
2145
     * Sets the directory listing matcher
2146
     *
2147
     * Use this method to set the directory listing matcher to a specific pattern.
2148
     * Indicate the pattern as a perl regular expression and give an array
2149
     * containing as keys the fields selected in the regular expression and as
2150
     * values the offset of the subpattern in the pattern.
2151
     *
2152
     * Example:
2153
     * $pattern = '/(?:(d)|.)([rwxt-]+)\s+(\w+)\s+([\w\d-]+)\s+([\w\d-]+)\s+(\w+)
2154
     *             \s+(\S+\s+\S+\s+\S+)\s+(.+)/',
2155
     * $matchmap = array(
2156
     *     'is_dir'        => 1,
2157
     *     'rights'        => 2,
2158
     *     'files_inside'  => 3,
2159
     *     'user'          => 4,
2160
     *     'group'         => 5,
2161
     *     'size'          => 6,
2162
     *     'date'          => 7,
2163
     *     'name'          => 8,
2164
     * )
2165
     *
2166
     * Make sure at least the is_dir and name keys are set. The is_dir key should
2167
     * point to a subpattern that is empty for non-directories and non-empty
2168
     * for directories.
2169
     *
2170
     * @param string $pattern  The new matcher pattern to use
2171
     * @param array  $matchmap An mapping from key to subpattern offset
2172
     *
2173
     * @since 1.4.0a1
2174
     * @access public
2175
     * @return bool|PEAR_Error True if matcher set successfully, PEAR_Error
2176
     *                         otherwise
2177
     * @see NET_FTP_ERR_ILLEGALPATTERN,
2178
     *      NET_FTP_ERR_ILLEGALMAP
2179
     *      NET_FTP_ERR_ILLEGALMAPVALUE
2180
     */
2181
    function setDirMatcher($pattern, $matchmap)
2182
    {
2183
        if (!is_string($pattern)) {
2184
            return $this->raiseError('The supplied pattern is not a string',
2185
                                     NET_FTP_ERR_ILLEGALPATTERN);
2186
        }
2187
        if (!is_array($matchmap)) {
2188
            return $this->raiseError('The supplied pattern is not an array',
2189
                                     NET_FTP_ERR_ILLEGALMAP);
2190
        } else {
2191
            foreach ($matchmap AS $val) {
2192
                if (!is_numeric($val)) {
2193
                    return $this->raiseError('The supplied pattern contains'.
2194
                                             'invalid value '.$val,
2195
                                     NET_FTP_ERR_ILLEGALMAPVALUE);
2196
                }
2197
            }
2198
        }
2199
        
2200
        $this->_matcher = array('pattern' => $pattern, 'map' => $matchmap);
2201
        
2202
        return true;
2203
    }
2204

    
2205
    /**
2206
     * Informs each registered observer instance that a new message has been
2207
     * sent.                                                                
2208
     *                                                                      
2209
     * @param mixed $event A hash describing the net event.
2210
     *  
2211
     * @access private                                                     
2212
     * @since 1.3      
2213
     * @return void                                                   
2214
     */
2215
    function _announce($event)
2216
    {
2217
        foreach ($this->_listeners as $listener) {
2218
            $listener->notify($event);
2219
        }
2220
    }
2221

    
2222
    /**
2223
     * Rebuild the path, if given relative
2224
     *
2225
     * This method will make a relative path absolute by prepending the current
2226
     * remote directory in front of it.
2227
     *
2228
     * @param string $path The path to check and construct
2229
     *
2230
     * @access private
2231
     * @return string The build path
2232
     */
2233
    function _constructPath($path)
2234
    {
2235
        if ((substr($path, 0, 1) != '/') && (substr($path, 0, 2) != './')) {
2236
            $actual_dir = @ftp_pwd($this->_handle);
2237
            if (substr($actual_dir, -1) != '/') {
2238
                $actual_dir .= '/';
2239
            }
2240
            $path = $actual_dir.$path;
2241
        }
2242
        return $path;
2243
    }
2244

    
2245
    /**
2246
     * Checks whether the given path is a remote directory by trying to
2247
     * chdir() into it (and back out)
2248
     *
2249
     * @param string $path Path to check
2250
     *
2251
     * @access private
2252
     * @return mixed True if $path is a directory, otherwise false, PEAR_Error
2253
     *               when an error occurs in determining path type
2254
     */
2255
    function _checkRemoteDir($path)
2256
    {
2257
        $pwd = $this->pwd();
2258
        if ($this->isError($pwd)) {
2259
            return $pwd;
2260
        }
2261
        $res = $this->cd($path);
2262
        $this->cd($pwd);
2263
        
2264
        return $this->isError($res, NET_FTP_ERR_DIRCHANGE_FAILED) === false; 
2265
    }
2266

    
2267
    /**
2268
     * This will remove a file
2269
     *
2270
     * @param string $file The file to delete
2271
     *
2272
     * @access private
2273
     * @return mixed True on success, otherwise PEAR::Error
2274
     * @see NET_FTP_ERR_DELETEFILE_FAILED
2275
     */
2276
    function _rmFile($file)
2277
    {
2278
        if (substr($file, 0, 1) != "/") {
2279
            $actual_dir = @ftp_pwd($this->_handle);
2280
            if (substr($actual_dir, (strlen($actual_dir) - 2), 1) != "/") {
2281
                $actual_dir .= "/";
2282
            }
2283
            $file = $actual_dir.$file;
2284
        }
2285
        $res = @ftp_delete($this->_handle, $file);
2286
        
2287
        if (!$res) {
2288
            return $this->raiseError("Could not delete file '$file'.",
2289
                                     NET_FTP_ERR_DELETEFILE_FAILED);
2290
        } else {
2291
            return true;
2292
        }
2293
    }
2294

    
2295
    /**
2296
     * This will remove a dir
2297
     *
2298
     * @param string $dir The dir to delete
2299
     *
2300
     * @access private
2301
     * @return mixed True on success, otherwise PEAR::Error
2302
     * @see NET_FTP_ERR_REMOTEPATHNODIR, NET_FTP_ERR_DELETEDIR_FAILED
2303
     */
2304
    function _rmDir($dir)
2305
    {
2306
        if (substr($dir, (strlen($dir) - 1), 1) != "/") {
2307
            return $this->raiseError("Directory name '".$dir.
2308
                                     "' is invalid, has to end with '/'",
2309
                                     NET_FTP_ERR_REMOTEPATHNODIR);
2310
        }
2311
        $res = @ftp_rmdir($this->_handle, $dir);
2312
        if (!$res) {
2313
            return $this->raiseError("Could not delete directory '$dir'.",
2314
                                     NET_FTP_ERR_DELETEDIR_FAILED);
2315
        } else {
2316
            return true;
2317
        }
2318
    }
2319

    
2320
    /**
2321
     * This will remove a dir and all subdirs and -files
2322
     *
2323
     * @param string $dir       The dir to delete recursively
2324
     * @param bool   $filesonly Only delete files so the directory structure is
2325
     *                          preserved 
2326
     *
2327
     * @access private
2328
     * @return mixed True on success, otherwise PEAR::Error
2329
     * @see NET_FTP_ERR_REMOTEPATHNODIR, NET_FTP_ERR_DELETEDIR_FAILED
2330
     */
2331
    function _rmDirRecursive($dir, $filesonly = false)
2332
    {
2333
        if (substr($dir, (strlen($dir) - 1), 1) != "/") {
2334
            return $this->raiseError("Directory name '".$dir.
2335
                                     "' is invalid, has to end with '/'",
2336
                                     NET_FTP_ERR_REMOTEPATHNODIR);
2337
        }
2338
        $file_list = $this->_lsFiles($dir);
2339
        if (PEAR::isError($file_list)) {
2340
            return $file_list;
2341
        }
2342
        
2343
        foreach ($file_list as $file) {
2344
            $file = $dir.$file["name"];
2345
            $res  = $this->rm($file);
2346
            if ($this->isError($res)) {
2347
                return $res;
2348
            }
2349
        }
2350
        
2351
        $dir_list = $this->_lsDirs($dir);
2352
        if (PEAR::isError($dir_list)) {
2353
            return $dir_list;
2354
        }
2355
        
2356
        foreach ($dir_list as $new_dir) {
2357
            if ($new_dir["name"] == '.' || $new_dir["name"] == '..') {
2358
                continue;
2359
            }
2360
            $new_dir = $dir.$new_dir["name"]."/";
2361
            $res     = $this->_rmDirRecursive($new_dir, $filesonly);
2362
            if ($this->isError($res)) {
2363
                return $res;
2364
            }
2365
        }
2366
        if (!$filesonly) {
2367
            $res = $this->_rmDir($dir);
2368
        }
2369
        if (PEAR::isError($res)) {
2370
            return $res;
2371
        } else {
2372
            return true;
2373
        }
2374
    }
2375

    
2376
    /**
2377
     * Lists up files and directories
2378
     *
2379
     * @param string $dir The directory to list up
2380
     *
2381
     * @access private
2382
     * @return array An array of dirs and files
2383
     */
2384
    function _lsBoth($dir)
2385
    {
2386
        $list_splitted = $this->_listAndParse($dir);
2387
        if (PEAR::isError($list_splitted)) {
2388
            return $list_splitted;
2389
        }
2390
        if (!is_array($list_splitted["files"])) {
2391
            $list_splitted["files"] = array();
2392
        }
2393
        if (!is_array($list_splitted["dirs"])) {
2394
            $list_splitted["dirs"] = array();
2395
        }
2396
        $res = array();
2397
        @array_splice($res, 0, 0, $list_splitted["files"]);
2398
        @array_splice($res, 0, 0, $list_splitted["dirs"]);
2399
        return $res;
2400
    }
2401

    
2402
    /**
2403
     * Lists up directories
2404
     *
2405
     * @param string $dir The directory to list up
2406
     *
2407
     * @access private
2408
     * @return array An array of dirs
2409
     */
2410
    function _lsDirs($dir)
2411
    {
2412
        $list = $this->_listAndParse($dir);
2413
        if (PEAR::isError($list)) {
2414
            return $list;
2415
        }
2416
        return $list["dirs"];
2417
    }
2418

    
2419
    /**
2420
     * Lists up files
2421
     *
2422
     * @param string $dir The directory to list up
2423
     *
2424
     * @access private
2425
     * @return array An array of files
2426
     */
2427
    function _lsFiles($dir)
2428
    {
2429
        $list = $this->_listAndParse($dir);
2430
        if (PEAR::isError($list)) {
2431
            return $list;
2432
        }
2433
        return $list["files"];
2434
    }
2435

    
2436
    /**
2437
     * This lists up the directory-content and parses the items into well-formated
2438
     * arrays.
2439
     * The results of this array are sorted (dirs on top, sorted by name;
2440
     * files below, sorted by name).
2441
     *
2442
     * @param string $dir The directory to parse
2443
     *
2444
     * @access private
2445
     * @return array Lists of dirs and files
2446
     * @see NET_FTP_ERR_RAWDIRLIST_FAILED
2447
     */
2448
    function _listAndParse($dir)
2449
    {
2450
        $dirs_list  = array();
2451
        $files_list = array();
2452
        $dir_list   = @ftp_rawlist($this->_handle, $dir);
2453
        if (!is_array($dir_list)) {
2454
            return PEAR::raiseError('Could not get raw directory listing.',
2455
                                    NET_FTP_ERR_RAWDIRLIST_FAILED);
2456
        }
2457
        
2458
        foreach ($dir_list AS $k=>$v) {
2459
            if (strncmp($v, 'total: ', 7) == 0 && preg_match('/total: \d+/', $v)) {
2460
                unset($dir_list[$k]);
2461
                break; // usually there is just one line like this
2462
            }
2463
        }
2464
        
2465
        // Handle empty directories
2466
        if (count($dir_list) == 0) {
2467
            return array('dirs' => $dirs_list, 'files' => $files_list);
2468
        }
2469

    
2470
        // Exception for some FTP servers seem to return this wiered result instead
2471
        // of an empty list
2472
        if (count($dirs_list) == 1 && $dirs_list[0] == 'total 0') {
2473
            return array('dirs' => array(), 'files' => $files_list);
2474
        }
2475
        
2476
        if (!isset($this->_matcher) || PEAR::isError($this->_matcher)) {
2477
            $this->_matcher = $this->_determineOSMatch($dir_list);
2478
            if (PEAR::isError($this->_matcher)) {
2479
                return $this->_matcher;
2480
            }
2481
        }
2482
        foreach ($dir_list as $entry) {
2483
            $m = array();
2484
            if (!preg_match($this->_matcher['pattern'], $entry, $m)) {
2485
                continue;
2486
            }
2487
            $entry = array();
2488
            foreach ($this->_matcher['map'] as $key=>$val) {
2489
                $entry[$key] = $m[$val];
2490
            }
2491
            $entry['stamp'] = $this->_parseDate($entry['date']);
2492

    
2493
            if ($entry['is_dir']) {
2494
                $dirs_list[] = $entry;
2495
            } else {
2496
                $files_list[] = $entry;
2497
            }
2498
        }
2499
        @usort($dirs_list, array("Net_FTP", "_natSort"));
2500
        @usort($files_list, array("Net_FTP", "_natSort"));
2501
        $res["dirs"]  = (is_array($dirs_list)) ? $dirs_list : array();
2502
        $res["files"] = (is_array($files_list)) ? $files_list : array();
2503
        return $res;
2504
    }
2505

    
2506
    /**
2507
     * Determine server OS
2508
     * This determines the server OS and returns a valid regex to parse
2509
     * ls() output.
2510
     *
2511
     * @param array &$dir_list The raw dir list to parse
2512
     *
2513
     * @access private
2514
     * @return mixed An array of 'pattern' and 'map' on success, otherwise
2515
     *               PEAR::Error
2516
     * @see NET_FTP_ERR_DIRLIST_UNSUPPORTED
2517
     */
2518
    function _determineOSMatch(&$dir_list)
2519
    {
2520
        foreach ($dir_list as $entry) {
2521
            foreach ($this->_ls_match as $match) {
2522
                if (preg_match($match['pattern'], $entry)) {
2523
                    return $match;
2524
                }
2525
            }
2526
        }
2527
        $error = 'The list style of your server seems not to be supported. Please'.
2528
                 'email a "$ftp->ls(NET_FTP_RAWLIST);" output plus info on the'.
2529
                 'server to the maintainer of this package to get it supported!'.
2530
                 'Thanks for your help!';
2531
        return PEAR::raiseError($error, NET_FTP_ERR_DIRLIST_UNSUPPORTED);
2532
    }
2533

    
2534
    /**
2535
     * Lists a local directory
2536
     *
2537
     * @param string $dir_path The dir to list
2538
     *
2539
     * @access private
2540
     * @return array The list of dirs and files
2541
     */
2542
    function _lsLocal($dir_path)
2543
    {
2544
        $dir       = dir($dir_path);
2545
        $dir_list  = array();
2546
        $file_list = array();
2547
        while (false !== ($entry = $dir->read())) {
2548
            if (($entry != '.') && ($entry != '..')) {
2549
                if (is_dir($dir_path.$entry)) {
2550
                    $dir_list[] = $entry;
2551
                } else {
2552
                    $file_list[] = $entry;
2553
                }
2554
            }
2555
        }
2556
        $dir->close();
2557
        $res['dirs']  = $dir_list;
2558
        $res['files'] = $file_list;
2559
        return $res;
2560
    }
2561

    
2562
    /**
2563
     * Function for use with usort().
2564
     * Compares the list-array-elements by name.
2565
     *
2566
     * @param string $item_1 first item to be compared
2567
     * @param string $item_2 second item to be compared
2568
     *
2569
     * @access private
2570
     * @return int < 0 if $item_1 is less than $item_2, 0 if equal and > 0 otherwise
2571
     */
2572
    function _natSort($item_1, $item_2)
2573
    {
2574
        return strnatcmp($item_1['name'], $item_2['name']);
2575
    }
2576

    
2577
    /**
2578
     * Parse dates to timestamps
2579
     *
2580
     * @param string $date Date
2581
     *
2582
     * @access private
2583
     * @return int Timestamp
2584
     * @see NET_FTP_ERR_DATEFORMAT_FAILED
2585
     */
2586
    function _parseDate($date)
2587
    {
2588
        // Sep 10 22:06 => Sep 10, <year> 22:06
2589
        $res = array();
2590
        if (preg_match('/([A-Za-z]+)[ ]+([0-9]+)[ ]+([0-9]+):([0-9]+)/', $date,
2591
                       $res)) {
2592
            $year    = date('Y');
2593
            $month   = $res[1];
2594
            $day     = $res[2];
2595
            $hour    = $res[3];
2596
            $minute  = $res[4];
2597
            $date    = "$month $day, $year $hour:$minute";
2598
            $tmpDate = strtotime($date);
2599
            if ($tmpDate > time()) {
2600
                $year--;
2601
                $date = "$month $day, $year $hour:$minute";
2602
            }
2603
        } elseif (preg_match('/^\d\d-\d\d-\d\d/', $date)) {
2604
            // 09-10-04 => 09/10/04
2605
            $date = str_replace('-', '/', $date);
2606
        }
2607
        $res = strtotime($date);
2608
        if (!$res) {
2609
            return $this->raiseError('Dateconversion failed.',
2610
                                     NET_FTP_ERR_DATEFORMAT_FAILED);
2611
        }
2612
        return $res;
2613
    }
2614
}
2615
?>