Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

ftp_put returns false always - mkdir(): Permission denied 1

Status
Not open for further replies.

Sensibilium

Programmer
Apr 6, 2000
310
GB
Hi,

Not sure if anyone can resolve this, but I've been scratching my head all day.

I am simply trying to upload a file to a remote server using 'ftp_put', I am running PHP 8.2 and using the Symfony 6.2 framework.

The error I received is not related to remote server permissions, as I have code that executes the FTP stuff inside the Controller class which works as expected, I feel this is something to do with the FtpHelper Service class, but I'm not sure how or why. I include both the non-working code (which includes a cut down FtpHelper class), and a follow up piece of code that works, but does not utilise the functions within the FtpHelper class.

Any thoughts on why this might be occuring would be amazing.

This is the failing class (FtpHelper) and calling class (CronDailyController):

PHP:
<?php

namespace App\Service;

/**
 * FtpHelper
 * Provides required information for FTP access
 * to various scripts
 */
class FtpHelper {
    const WEBSITE = 0;
    const WEBSITE2 = 1;

    private static array $websiteFTP = [
        'remote_path' => '',
        'remote_file' => 'thefile.csv',
        'local_path' => '/var/[URL unfurl="true"]www/temp/',[/URL]
        'local_file' => 'thefile2.csv',
        'hostname' => 'somehost.local',
    ];
    private static array $website2FTP = [
        'remote_path' => '',
        'remote_file' => 'thefile2.csv',
        'local_path' => '/var/[URL unfurl="true"]www/temp/',[/URL]
        'local_file' => 'thefile2.csv',
        'hostname' => 'somehost2.local',
    ];

    final public static function getWebsiteFTP()
    {
        return self::$websiteFTP;
    }

    final public static function getWebsite2FTP()
    {
        return self::$website2FTP;
    }

    final public function connectToServer(int $theServer, string &$theUser, string &$thePass): \FTP\Connection|false
    { 
        $serverDetails = self::getServerDetails($theServer);
        $ftpConnect = ftp_connect($serverDetails['hostname']);
        unset($serverDetails);
        if ($ftpConnect) {
            if (ftp_login($ftpConnect, $theUser, $thePass)) {
                return $ftpConnect;
            }
        }

        return false;
    }

    final public function setPassiveMode(\FTP\Connection &$conn): bool
    {
        // Set FTP Passive mode
        return ftp_pasv($conn, true);
    }

    final public function uploadToServer(int $theServer, \FTP\Connection &$conn): bool
    {
        $serverDetails = self::getServerDetails($theServer);
        $filepath = $serverDetails['remote_path'].'/'.$serverDetails['remote_file'];
        $localfile = $serverDetails['local_file'];
        unset($serverDetails);
        // Upload the temp csv file via FTP
        // TODO Upload fails here with 'mkdir(): Permission denied'
        return ftp_put($conn, $filepath, $localfile, FTP_ASCII);
    }

    final public function closeConnection(\FTP\Connection &$conn): bool
    {
        // Close the FTP connection
        return ftp_close($conn);
    }

    private function getServerDetails(int &$theServer): array
    {
        return match($theServer) {
            self::WEBSITE => self::$websiteFTP,
            self::WEBSITE2 => self::$website2FTP,
        };
    }
}

class CronDailyController extends AbstractController
{
    // See config/services.yaml when adding more secrets
    private $websiteFtpPassword;
    private $websiteFtpUsername;
    private $website2FtpPassword;
    private $website2FtpUsername;

    public function __construct(
        $websiteFtpPassword, $websiteFtpUsername,
        $website2FtpPassword, $website2FtpUsername,
        ) {
        $this->websiteFtpPassword = $websiteFtpPassword[0];
        $this->websiteFtpUsername = $websiteFtpUsername[0];
        $this->website2FtpPassword = $website2FtpPassword[0];
        $this->website2FtpUsername = $website2FtpUsername[0];
    }

    #[Route(path: '/cron/update-stock', name: 'app_cron_update_stock', methods: ['GET'])]
    public function updateStock(
        try {
            $ftpUser = $this->websiteFtpUsername;
            $ftpPass = $this->websiteFtpPassword;
            // Prepare to FTP file to server
            $ftpResult = $ftpHelper->connectToServer(FtpHelper::WEBSITE, $ftpUser, $ftpPass);
            if (!$ftpResult) {
                $output .= '[ERROR] stocks failed ';
                $output .= '(Could not connect to FTP server)'."\n";

                return new Response($output);
            }
            $ftpPassive = $ftpHelper->setPassiveMode($ftpResult);
            if (!$ftpPassive) {
                $output .= '[ERROR] stocks failed ';
                $output .= '(Could not enable FTP passive mode)'."\n";

                return new Response($output);
            }
            $ftpUpload = $ftpHelper->uploadToServer(FtpHelper::WEBSITE, $ftpResult);
            $ftpClosed = $ftpHelper->closeConnection($ftpResult);
        } catch (\Exception) {
            $output .= '[ERROR] stocks failed ';
            $output .= '(General FTP Exception)'."\n";

            return new Response($output);
        }
    }
}

Here is the working code from the Controller class:

PHP:
class CronDailyController extends AbstractController
{
    #[Route(path: '/cron/update-stock', name: 'app_cron_update_stock', methods: ['GET'])]
    public function updateStock(
        try {
            $ftpLogin = false;
            $ftpConnect = ftp_connect($ftpHost);
            if ($ftpConnect) {
                $ftpLogin = ftp_login($ftpConnect, $ftpUser, $ftpPass);
            }
            // Set FTP Passive mode
            ftp_pasv($ftpConnect, true);
            // Upload the temp csv file via FTP
            $ftpUpload = ftp_put($ftpConnect, $ftpRemoteDir.'/'.$ftpRemoteFile, $ftpLocalFile, FTP_ASCII);
            if (!$ftpUpload) {
                ftp_close($ftpConnect);
                $output .= '[ERROR] stocks failed ';
                $output .= '(Failed to upload file to FTP server)'."\n";

                return new Response($output);
            }
            // Close our FTP connection
            ftp_close($ftpConnect);
        } catch (\Exception) {
            $output .= '[ERROR] stocks failed ';
            $output .= '(General FTP Exception)'."\n";

            return new Response($output);
        }
        $output .= '[OK] stocks uploaded '."\n";

        return new Response($output);
    }
}

When I output all the variables from the first example code, everything is as expected (variables, FTP Connection and login), when I call 'dump(error_get_last());' I get the error message as noted in the thread title, 'mkdir(): Permission denied'.

Very strange. I even tried FtpHelper as a class purely containing static functions, but changed it to instantiated in the hope it was related to that, it wasn't.

TIA

Sensibilium
 
Finally nailed down this issue, it was something daft in the end, I should have spotted it earlier. In fact the solution was sat in the working code (which wasn't included in my shortened version).

Essentially, I wasn't including the local file path in the ftp_put() call. The error raised by this action was not entirely clear because it was trying to upload a file that didn't exist, so perhaps when a non-existent file is being uploaded the current local path of the script is passed (maybe), not entirely sure, but anyway, the solution is shown below.

PHP:
        $localfile = $serverDetails['local_path'].$serverDetails['local_file'];

I include the corrected uploadToServer function below.

PHP:
    final public function uploadToServer(int $theServer, \FTP\Connection &$conn): bool
    {
        $serverDetails = self::getServerDetails($theServer);
        $filepath = $serverDetails['remote_path'].'/'.$serverDetails['remote_file'];
        $localfile = $serverDetails['local_path'].$serverDetails['local_file'];
        unset($serverDetails);

        // Upload the temp csv file via FTP
        return ftp_put($conn, $filepath, $localfile, FTP_ASCII);
    }

Sensibilium
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top