Source for file ftp.php
Documentation is available at ftp.php
* VFS implementation for an FTP server.
* Required values for $params:<pre>
* 'username' The username with which to connect to the ftp server.
* 'password' The password with which to connect to the ftp server.
* 'hostspec' The ftp server to connect to.</pre>
* Optional values for $params:<pre>
* 'lsformat' The return formatting from the 'ls' command).
* Values: 'aix', 'standard' (default)
* 'maplocalids' If true and the POSIX extension is available, the
* driver will map the user and group IDs returned from
* the FTP server with the local IDs from the local
* password file. This is useful only if the FTP server
* is running on localhost or if the local user/group
* IDs are identical to the remote FTP server.
* 'pasv' If true, connection will be set to passive mode.
* 'port' The port used to connect to the ftp server if other
* 'ssl' If true, and PHP had been compiled with OpenSSL
* support, TLS transport-level encryption will be
* negotiated with the server.
* 'timeout' If defined, use this value as the timeout for the
* 'type' The type of the remote FTP server.
* Possible values: 'unix', 'win', 'netware'
* By default, we attempt to auto-detect type.</pre>
* $Horde: framework/VFS/lib/VFS/ftp.php,v 1.1.2.7 2009/10/15 17:18:48 jan Exp $
* Copyright 2002-2009 The Horde Project (http://www.horde.org/)
* Copyright 2002-2007 Michael Varghese <mike.varghese@ascellatech.com>
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Michael Varghese <mike.varghese@ascellatech.com>
* List of additional credentials required for this VFS backend.
var $_credentials = array ('username', 'password');
* List of permissions and if they can be changed in this VFS backend.
var $_permissions = array (
'owner' => array ('read' => true , 'write' => true , 'execute' => true ),
'group' => array ('read' => true , 'write' => true , 'execute' => true ),
'all' => array ('read' => true , 'write' => true , 'execute' => true ));
* Variable holding the connection to the ftp server.
* Local cache array for user IDs.
* Local cache array for group IDs.
* Returns the size of a file.
* @param string $path The path of the file.
* @param string $name The filename.
* @return integer The size of the file in bytes or PEAR_Error on
function size($path, $name)
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
if (($size = @ftp_size($this->_stream, $this->_getPath($path, $name))) === false ) {
return PEAR ::raiseError (sprintf(_("Unable to check file size of \"%s\"."), $this->_getPath($path, $name)));
* Retrieves a file from the VFS.
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
* @return string The file data.
function read($path, $name)
if (is_a($file, 'PEAR_Error')) {
* Retrieves a file from the VFS as an on-disk local file.
* This function provides a file on local disk with the data of a VFS file
* in it. This file <em>cannot</em> be modified! The behavior if you do
* modify it is undefined. It will be removed at the end of the request.
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
* @return string A local filename.
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
// Create a temporary file and register it for deletion at the
return PEAR ::raiseError (_("Unable to create temporary file."));
return PEAR ::raiseError (sprintf(_("Unable to open VFS file \"%s\"."), $this->_getPath($path, $name)));
* Open a stream to a file in the VFS.
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
* @return resource The stream.
if (is_a($file, 'PEAR_Error')) {
$mode = OS_WINDOWS ? 'rb' : 'r';
return fopen($file, $mode);
* Stores a file in the VFS.
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $tmpFile The temporary file containing the data to
* @param boolean $autocreate Automatically create directories?
* @return mixed True on success or a PEAR_Error object on failure.
function write($path, $name, $tmpFile, $autocreate = false )
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
$res = $this->_checkQuotaWrite ('file', $tmpFile);
if (is_a($res, 'PEAR_Error')) {
if (!@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY )) {
if (is_a($result, 'PEAR_Error')) {
if (!@ftp_put($this->_stream, $this->_getPath($path, $name), $tmpFile, FTP_BINARY )) {
return PEAR ::raiseError (sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
return PEAR ::raiseError (sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
* Stores a file in the VFS from raw data.
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $data The file data.
* @param boolean $autocreate Automatically create directories?
* @return mixed True on success or a PEAR_Error object on failure.
function writeData($path, $name, $data, $autocreate = false )
$res = $this->_checkQuotaWrite ('string', $data);
if (is_a($res, 'PEAR_Error')) {
$fp = fopen($tmpFile, 'wb');
$result = $this->write($path, $name, $tmpFile, $autocreate);
* Deletes a file from the VFS.
* @param string $path The path to delete the file from.
* @param string $name The filename to delete.
* @return mixed True on success or a PEAR_Error object on failure.
$res = $this->_checkQuotaDelete ($path, $name);
if (is_a($res, 'PEAR_Error')) {
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
return PEAR ::raiseError (sprintf(_("Unable to delete VFS file \"%s\"."), $this->_getPath($path, $name)));
* Checks if a given item is a folder.
* @param string $path The parent folder.
* @param string $name The item name.
* @return boolean True if it is a folder, false otherwise.
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
/* See if we can change to the given path. */
$this->_setPath ($olddir);
* Deletes a folder from the VFS.
* @param string $path The parent folder.
* @param string $name The name of the folder to delete.
* @param boolean $recursive Force a recursive delete?
* @return mixed True on success or a PEAR_Error object on failure.
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
foreach ($dirCheck as $file) {
if ($file['name'] == $name && $file['type'] == '**dir') {
if (is_a($file_list, 'PEAR_Error')) {
if (count($file_list) && !$recursive) {
return PEAR ::raiseError (sprintf(_("Unable to delete \"%s\", the directory is not empty."),
foreach ($file_list as $file) {
if ($file['type'] == '**dir') {
if (is_a($result, 'PEAR_Error')) {
return PEAR ::raiseError (sprintf(_("Cannot remove directory \"%s\"."), $this->_getPath($path, $name)));
return PEAR ::raiseError (sprintf(_("Cannot delete file \"%s\"."), $this->_getPath($path, $name)));
* Renames a file in the VFS.
* @param string $oldpath The old path to the file.
* @param string $oldname The old filename.
* @param string $newpath The new path of the file.
* @param string $newname The new filename.
* @return mixed True on success or a PEAR_Error object on failure.
function rename($oldpath, $oldname, $newpath, $newname)
if (is_a($conn = $this->_connect (), 'PEAR_Error')) {
return PEAR ::raiseError (sprintf(_("Unable to rename VFS file \"%s\"."), $this->_getPath($oldpath, $oldname)));
* Creates a folder on the VFS.
* @param string $path The parent folder.
* @param string $name The name of the new folder.
* @return mixed True on success or a PEAR_Error object on failure.
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
return PEAR ::raiseError (sprintf(_("Unable to create VFS directory \"%s\"."), $this->_getPath($path, $name)));
* Changes permissions for an item on the VFS.
* @param string $path The parent folder of the item.
* @param string $name The name of the item.
* @param string $permission The permission to set.
* @return mixed True on success or a PEAR_Error object on failure.
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
if (!@ftp_site($this->_stream, 'CHMOD ' . $permission . ' ' . $this->_getPath($path, $name))) {
return PEAR ::raiseError (sprintf(_("Unable to change permission for VFS file \"%s\"."), $this->_getPath($path, $name)));
* Returns an an unsorted file list of the specified directory.
* @param string $path The path of the directory.
* @param mixed $filter String/hash to filter file/dirname on.
* @param boolean $dotfiles Show dotfiles?
* @param boolean $dironly Show only directories?
* @return array File list on success or PEAR_Error on failure.
function _listFolder ($path = '', $filter = null , $dotfiles = true ,
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
if (empty ($this->_type)) {
if (!empty ($this->_params['type'])) {
$this->_type = $this->_params['type'];
if ($type == 'unknown') {
// Go with unix-style listings by default.
} elseif (strpos($type, 'win') !== false ) {
} elseif (strpos($type, 'netware') !== false ) {
$res = $this->_setPath ($path);
if (is_a($res, 'PEAR_Error')) {
if ($this->_type == 'unix') {
// If we don't want dotfiles, We can save work here by not
// doing an ls -a and then not doing the check later (by
// setting $dotfiles to true, the if is short-circuited).
$res = $this->_setPath ($olddir);
if (is_a($res, 'PEAR_Error')) {
/* If 'maplocalids' is set, check for the POSIX extension. */
if (!empty ($this->_params['maplocalids']) &&
foreach ($list as $line) {
if (($this->_type == 'unix') ||
(($this->_type == 'win') && !preg_match('|\d\d-\d\d-\d\d|', $item[0 ]))) {
if (count($item) < 8 || substr($line, 0 , 5 ) == 'total') {
$file['perms'] = $item[0 ];
if (!isset ($this->_uids[$item[2 ]])) {
$this->_uids[$item[2 ]] = (empty ($entry)) ? $item[2 ] : $entry['name'];
$file['owner'] = $this->_uids[$item[2 ]];
if (!isset ($this->_uids[$item[3 ]])) {
$this->_uids[$item[3 ]] = (empty ($entry)) ? $item[3 ] : $entry['name'];
$file['group'] = $this->_uids[$item[3 ]];
$file['owner'] = $item[2 ];
$file['group'] = $item[3 ];
if (!empty ($this->_params['lsformat']) &&
($this->_params['lsformat'] == 'aix')) {
$file['name'] = substr($line, strpos($line, sprintf("%s %2s %-5s", $item[5 ], $item[6 ], $item[7 ])) + 13 );
$file['name'] = substr($line, strpos($line, sprintf("%s %2s %5s", $item[5 ], $item[6 ], $item[7 ])) + 13 );
// Filter out '.' and '..' entries.
// Filter out dotfiles if they aren't wanted.
if (!$dotfiles && substr($file['name'], 0 , 1 ) == '.') {
$p1 = substr($file['perms'], 0 , 1 );
$file['link'] = substr($file['name'], strpos($file['name'], '->') + 3 );
$file['name'] = substr($file['name'], 0 , strpos($file['name'], '->') - 1 );
if ($this->isFolder('', $file['link'])) {
$file['linktype'] = '**dir';
$parts = explode('/', $file['link']);
if (count($name) == 1 || ($name[0 ] === '' && count($name) == 2 )) {
$file['linktype'] = '**none';
$name = explode('.', $file['name']);
if (count($name) == 1 || (substr($file['name'], 0 , 1 ) === '.' && count($name) == 2 )) {
$file['type'] = '**none';
if ($file['type'] == '**dir') {
$file['size'] = $item[4 ];
if (strpos($item[7 ], ':') !== false ) {
$file['date'] = strtotime($item[7 ] . ':00' . $item[5 ] . ' ' . $item[6 ] . ' ' . date('Y', $currtime));
// If the ftp server reports a file modification date more
// less than one day in the future, don't try to subtract
// a year from the date. There is no way to know, for
// example, if the VFS server and the ftp server reside
// in different timezones. We should simply report to the
// user what the FTP server is returning.
if ($file['date'] > ($currtime + 86400 )) {
$file['date'] = strtotime($item[7 ] . ':00' . $item[5 ] . ' ' . $item[6 ] . ' ' . (date('Y', $currtime) - 1 ));
$file['date'] = strtotime('00:00:00' . $item[5 ] . ' ' . $item[6 ] . ' ' . $item[7 ]);
} elseif ($this->_type == 'netware') {
if (count($item) < 8 || substr($line, 0 , 5 ) == 'total') {
$file['perms'] = $item[1 ];
$file['owner'] = $item[2 ];
$file['type'] = '**none';
$file['size'] = $item[3 ];
// We don't know the timezone here. Just report what the FTP server says.
if (strpos($item[6 ], ':') !== false ) {
$file['date'] = strtotime($item[6 ] . ':00 ' . $item[5 ] . ' ' . $item[4 ] . ' ' . date('Y'));
$file['date'] = strtotime('00:00:00 ' . $item[5 ] . ' ' . $item[4 ] . ' ' . $item[6 ]);
$file['name'] = $item[7 ];
while ($index < count($item)) {
$file['name'] .= ' ' . $item[$index];
/* Handle Windows FTP servers returning DOS-style file
$file['name'] = $item[3 ];
while ($index < count($item)) {
$file['name'] .= ' ' . $item[$index];
$file['date'] = strtotime($item[0 ] . ' ' . $item[1 ]);
if ($item[2 ] == '<DIR>') {
$file['size'] = $item[2 ];
$name = explode('.', $file['name']);
if (count($name) == 1 || (substr($file['name'], 0 , 1 ) === '.' && count($name) == 2 )) {
$file['type'] = '**none';
if ($this->_filterMatch ($filter, $file['name'])) {
if ($dironly && $file['type'] !== '**dir') {
$files[$file['name']] = $file;
$res = $this->_setPath ($olddir);
if (is_a($res, 'PEAR_Error')) {
* Returns a sorted list of folders in the specified directory.
* @param string $path The path of the directory to get the
* @param mixed $filter Hash of items to filter based on folderlist.
* @param boolean $dotfolders Include dotfolders?
* @return mixed Folder list on success or a PEAR_Error object on failure.
function listFolders($path = '', $filter = null , $dotfolders = true )
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
$folderList = $this->listFolder($path, null , $dotfolders, true );
if (is_a($folderList, 'PEAR_Error')) {
$folder['val'] = $this->_parentDir ($path);
$folder['abbrev'] = '..';
$folders[$folder['val']] = $folder;
foreach ($folderList as $files) {
$folder['val'] = $this->_getPath($path, $files['name']);
$folder['abbrev'] = $files['name'];
$folder['label'] = $folder['val'];
$folders[$folder['val']] = $folder;
* Copies a file through the backend.
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The name of the destination directory.
* @param boolean $autocreate Automatically create directories?
* @return mixed True on success or a PEAR_Error object on failure.
function copy($path, $name, $dest, $autocreate = false )
return PEAR ::raiseError (_("Cannot copy file(s) - source and destination are the same."));
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
if (is_a($result, 'PEAR_Error')) {
$fileCheck = $this->listFolder($dest, null , true );
if (is_a($fileCheck, 'PEAR_Error')) {
foreach ($fileCheck as $file) {
if ($file['name'] == $name) {
return PEAR ::raiseError (sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
$fetch = @ftp_get($this->_stream, $tmpFile, $orig, FTP_BINARY );
return PEAR ::raiseError (sprintf(_("Failed to copy from \"%s\"."), $orig));
$res = $this->_checkQuotaWrite ('file', $tmpFile);
if (is_a($res, 'PEAR_Error')) {
if (!@ftp_put($this->_stream, $this->_getPath($dest, $name), $tmpFile, FTP_BINARY )) {
return PEAR ::raiseError (sprintf(_("Failed to copy to \"%s\"."), $this->_getPath($dest, $name)));
* Moves a file through the backend.
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The destination file name.
* @param boolean $autocreate Automatically create directories?
* @return mixed True on success or a PEAR_Error object on failure.
function move($path, $name, $dest, $autocreate = false )
return PEAR ::raiseError (_("Cannot move file(s) - destination is within source."));
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
if (is_a($result, 'PEAR_Error')) {
$fileCheck = $this->listFolder($dest, null , true );
if (is_a($fileCheck, 'PEAR_Error')) {
foreach ($fileCheck as $file) {
if ($file['name'] == $name) {
return PEAR ::raiseError (sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
return PEAR ::raiseError (sprintf(_("Failed to move to \"%s\"."), $this->_getPath($dest, $name)));
* Returns the current working directory on the FTP server.
* @return string The current working directory.
if (is_a($connected = $this->_connect (), 'PEAR_Error')) {
* Changes the current directory on the server.
* @param string $path The path to change to.
* @return mixed True on success, or a PEAR_Error on failure.
return PEAR ::raiseError (sprintf(_("Unable to change to %s."), $path));
* Returns the parent directory of the specified path.
* @param string $path The path to get the parent of.
* @return string The parent directory (string) on success or a PEAR_Error
function _parentDir ($path)
$conn = $this->_connect ();
if (is_a($conn, 'PEAR_Error')) {
$this->_setPath ($olddir);
return PEAR ::raiseError (_("Unable to determine current directory."));
* Attempts to open a connection to the FTP server.
* @return mixed True on success or a PEAR_Error object on failure.
if ($this->_stream === false ) {
return PEAR ::raiseError (_("The FTP extension is not available."));
return PEAR ::raiseError (_("No configuration information specified for FTP VFS."));
$required = array ('hostspec', 'username', 'password');
foreach ($required as $val) {
if (!isset ($this->_params[$val])) {
return PEAR ::raiseError (sprintf(_("Required \"%s\" not specified in VFS configuration."), $val));
/* Connect to the ftp server using the supplied parameters. */
if (!empty ($this->_params['ssl'])) {
$this->_stream = @ftp_ssl_connect($this->_params['hostspec'], $this->_params['port']);
return PEAR ::raiseError (_("Unable to connect with SSL."));
$this->_stream = @ftp_connect($this->_params['hostspec'], $this->_params['port']);
return PEAR ::raiseError (_("Connection to FTP server failed."));
$connected = @ftp_login($this->_stream, $this->_params['username'], $this->_params['password']);
return PEAR ::raiseError (_("Authentication to FTP server failed."));
if (!empty ($this->_params['pasv'])) {
if (!empty ($this->_params['timeout'])) {
ftp_set_option($this->_stream, FTP_TIMEOUT_SEC , $this->_params['timeout']);
Documentation generated on Mon, 11 Mar 2019 15:34:53 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|