Home » Php » PHP – Check if file exists on FTP server with no SIZE support

PHP – Check if file exists on FTP server with no SIZE support

Posted by: admin July 12, 2020 Leave a comment

Questions:

Yes, I know, it’s hard to believe those FTP servers still exist, but they actually do. IBM iSeries machines run such servers.

I’ve already got an answer which involves ftp_nlist and in_array but, as some of you may have guessed, this is slow when a directory contains a large number of items.

Due to the lack of support of SIZE, fopen always fails when used in read mode (remember that x isn’t supported by the FTP wrapper), while ftp_size always returns -1 (that was expected) and file_exists always returns false (maybe because it uses SIZE internally?).

  • ftp_get and ftp_fget do the trick, but they download the whole file if it exists. Not very good. One possible solution involves the use of ftp_fget passing an handler of a file opened in read mode only, and catching the raised warning. It’s different when the file doesn’t exist, but this solution feels uncouth, and I don’t really know if it’s feasible (maybe someone can give an example).

  • Another solution uses ftp_nb_get/ftp_nb_fget to try to retrieve the file. If the function returns 0 (FTP_FAILED) then the file presumably doesn’t exist. I’d still have to deal with a temporary local file, and it sucks to close and reopen the connection if FTP_MOREDATA is returned (or else other FTP commands couldn’t be issued).

Do you have any idea for this?

How to&Answers:

The SIZE command is not required. You can simply use the function ftp_nlist() for that because the FTP LIST command allows to pass a directory as well as file as it’s argument.

Although the PHP documentation is missing to mention that it is specified in RFC 959 (page 32) and is working. Here comes an example. (Thanks Debian!)

$server = 'ftp.us.debian.org';
$port   =  21; 
$user   = 'anonymous';
$pwd    = '[email protected]';

$conn = ftp_connect($server);
$ret = ftp_login($conn, $user, $pwd);

foreach(array(
    'debian/README.html',
    'NOT_FOUND.html'
) as $file) {
    $listing = ftp_nlist($conn, $file);
    if(empty($listing)) {
        echo "$file was not found on $server\n";
    } else {
        echo "$file was found on $server\n";
    }
}

Or, expressed as a function:

function ftp_file_exists(
    $server,
    $filename,
    $user = 'anonymous' ,
    $pwd = '',
    $port = 21
) {
    $conn = @ftp_connect($server);
    if($conn === FALSE) {
        die("Failed to connect to $server");
    }

    $ret = @ftp_login($conn, $user, $pwd);
    if($ret === FALSE) {
        die("Failed to login at $server");
    }

    $listing = @ftp_nlist($conn, $file);
    if($listing === FALSE) {
        die("Failed to obtain LIST response from $server");
    }

    return !empty($listing);
}

In comments there came up a discussion how useful and reliable the results of LIST can be. Let me say some additional sentences to that…

Creating files on server

Please be aware of the fact that you should avoid to rely on something like:

if(file_not_exists_on_server($filename)) {
    create_file_on_server($filename);
}

because it is possible that the file will be created by another client between the first and second function. While this is true on local file systems as well, it can happen in distributed client server applications more easily, because of longer response times compared to a local filesystem and because of possibly many, even anonymous clients (like in the example above)

When creating files remotely I would suggest to follow a strong naming scheme in public writable folders in order to avoid collisions. When following this scheme, then just write and don’t care. The worst thing which can happen is that you overwrite something which somebody other has created by accident. But who creates something like /client/id/file_name.txt by accident?


Downloading files from server or moving, deleting files on server

When attempting one of those operations, don’t care about if the files exists or not before the operation. Just do it. But if it fails, you need to handle the errors properly.

Answer:

You can use some kind of “dirty trick”: try to do ftp_rename() on tested file. Sample would be:

$result = ftp_rename($connection, $testedFile, $testedFile);

if it will fail, then we have most probably non-existent file. It’s not 100% unless you’re sure about permissions issues. But if it will succeed – then file definitely exists. I doubt that there’s another way which can be used and allow you to avoid file transferring to local FS or dealing with full directory listing.

Answer:

What about file_exists(), or other stat() family functions? FTP wrapper supports it.

http://www.php.net/manual/en/wrappers.ftp.php