    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     *
     * This file incorporates work covered by the following license notice:
     *
     *   Licensed to the Apache Software Foundation (ASF) under one or more
     *   contributor license agreements. See the NOTICE file distributed
     *   with this work for additional information regarding copyright
     *   ownership. The ASF licenses this file to you under the Apache
     *   License, Version 2.0 (the "License"); you may not use this file
     *   except in compliance with the License. You may obtain a copy of
     *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
     */

    /**************************************************************************
                                    TODO
     **************************************************************************

     *************************************************************************/

    #include <sal/config.h>
    #include <sal/log.hxx>

    #include <cstring>
    #include <vector>

    #include <rtl/ustrbuf.hxx>
    #include <com/sun/star/ucb/OpenMode.hpp>
    #include <string.h>
    #include <rtl/uri.hxx>
    #include <rtl/strbuf.hxx>

    #include "ftpurl.hxx"
    #include "ftpcontentprovider.hxx"
    #include "ftpcfunc.hxx"
    #include "ftpcontainer.hxx"
    #include <memory>

    using namespace ftp;
    using namespace com::sun::star::ucb;
    using namespace com::sun::star::uno;
    using namespace com::sun::star::io;

    namespace {

    OUString encodePathSegment(OUString const & decoded) {
        return rtl::Uri::encode(
            decoded, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
            RTL_TEXTENCODING_UTF8);
    }

    OUString decodePathSegment(OUString const & encoded) {
        return rtl::Uri::decode(
            encoded, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
    }

    }

    MemoryContainer::MemoryContainer()
        : m_nLen(0),
          m_nWritePos(0),
          m_pBuffer(nullptr)
    {
    }

    MemoryContainer::~MemoryContainer()
    {
        std::free(m_pBuffer);
    }

    int MemoryContainer::append(
        const void* pBuffer,
        size_t size,
        size_t nmemb
    ) throw()
    {
        sal_uInt32 nLen = size*nmemb;
        sal_uInt32 tmp(nLen + m_nWritePos);

        if(m_nLen < tmp) { // enlarge in steps of multiples of 1K
            do {
                m_nLen+=1024;
            } while(m_nLen < tmp);

            m_pBuffer = std::realloc(m_pBuffer,m_nLen);
        }

        memcpy(static_cast<sal_Int8*>(m_pBuffer)+m_nWritePos,
                       pBuffer,nLen);
        m_nWritePos = tmp;
        return nLen;
    }

    extern "C" {

        int memory_write(void *buffer,size_t size,size_t nmemb,void *stream)
        {
            MemoryContainer *_stream =
                static_cast<MemoryContainer*>(stream);

            if(!_stream)
                return 0;

            return _stream->append(buffer,size,nmemb);
        }

    }

    FTPURL::FTPURL(const FTPURL& r)
        : m_pFCP(r.m_pFCP),
          m_aUsername(r.m_aUsername),
          m_bShowPassword(r.m_bShowPassword),
          m_aHost(r.m_aHost),
          m_aPort(r.m_aPort),
          m_aPathSegmentVec(r.m_aPathSegmentVec)

    {
    }

    FTPURL::FTPURL(const OUString& url,
                   FTPContentProvider* pFCP)
        : m_pFCP(pFCP),
          m_aUsername("anonymous"),
          m_bShowPassword(false),
          m_aPort("21")
    {
        parse(url);  // can reset m_bShowPassword
    }

    FTPURL::~FTPURL()
    {
    }

    void FTPURL::parse(const OUString& url)
    {
        OUString aPassword, urlRest;

        if(url.getLength() < 6 || !url.startsWithIgnoreAsciiCase("ftp://", &urlRest))
            throw malformed_exception();

        // determine "username:password@host:port"
        OUString aExpr;
        sal_Int32 nIdx = urlRest.indexOf('/');
        if (nIdx == -1)
        {
            aExpr = urlRest;
            urlRest = "";
        }
        else
        {
            aExpr = urlRest.copy(0, nIdx);
            urlRest = urlRest.copy(nIdx + 1);
        }

        sal_Int32 l = aExpr.indexOf('@');
        m_aHost = aExpr.copy(1+l);

        if(l != -1) {
            // Now username and password.
            aExpr = aExpr.copy(0,l);
            l = aExpr.indexOf(':');
            if(l != -1) {
                aPassword = aExpr.copy(1+l);
                if(!aPassword.isEmpty())
                    m_bShowPassword = true;
            }
            if(l > 0)
                // Overwritten only if the username is not empty.
                m_aUsername = aExpr.copy(0,l);
            else if(!aExpr.isEmpty())
                m_aUsername = aExpr;
        }

        l = m_aHost.lastIndexOf(':');
        sal_Int32 ipv6Back = m_aHost.lastIndexOf(']');
        if((ipv6Back == -1 && l != -1) // not ipv6, but a port
           ||
           (ipv6Back != -1 && 1+ipv6Back == l) // ipv6, and a port
        )
        {
            if(1+l<m_aHost.getLength())
                m_aPort = m_aHost.copy(1+l);
            m_aHost = m_aHost.copy(0,l);
        }

        // now determine the pathsegments ...
        while(!urlRest.isEmpty())
        {
            nIdx = urlRest.indexOf('/');
            OUString segment;
            if(nIdx == -1)
            {
                segment = urlRest;
                urlRest = "";
            }
            else
            {
                segment = urlRest.copy(0, nIdx);
                urlRest = urlRest.copy(nIdx + 1);
            }
            if( segment == ".." && !m_aPathSegmentVec.empty() && m_aPathSegmentVec.back() != ".." )
                m_aPathSegmentVec.pop_back();
            else if( segment == "." )
                ; // Ignore
            else
                // This is a legal name.
                m_aPathSegmentVec.push_back( segment );
        }

        if(m_bShowPassword)
            m_pFCP->setHost(m_aHost,
                            m_aPort,
                            m_aUsername,
                            aPassword,
                            ""/*aAccount*/);

        // now check for something like ";type=i" at end of url
        if(m_aPathSegmentVec.size() &&
           (l = m_aPathSegmentVec.back().indexOf(';')) != -1) {
            m_aType = m_aPathSegmentVec.back().copy(l);
            m_aPathSegmentVec.back() = m_aPathSegmentVec.back().copy(0,l);
        }
    }

    OUString FTPURL::ident(bool withslash,bool internal) const
    {
        // rebuild the url as one without ellipses,
        // and more important, as one without username and
        // password. ( These are set together with the command. )

        OUStringBuffer bff;
        bff.append("ftp://");

        if( m_aUsername != "anonymous" ) {
            bff.append(m_aUsername);

            OUString aPassword,aAccount;
            m_pFCP->forHost(m_aHost,
                            m_aPort,
                            m_aUsername,
                            aPassword,
                            aAccount);

            if((m_bShowPassword || internal) &&
               !aPassword.isEmpty() )
                bff.append(':')
                    .append(aPassword);

            bff.append('@');
        }
        bff.append(m_aHost);

        if( m_aPort != "21" )
            bff.append(':')
                .append(m_aPort)
                .append('/');
        else
            bff.append('/');

        for(size_t i = 0; i < m_aPathSegmentVec.size(); ++i)
            if(i == 0)
                bff.append(m_aPathSegmentVec[i]);
            else
                bff.append('/').append(m_aPathSegmentVec[i]);
        if(withslash)
            if(!bff.isEmpty() && bff[bff.getLength()-1] != '/')
                bff.append('/');

        bff.append(m_aType);
        return bff.makeStringAndClear();
    }

    OUString FTPURL::parent(bool internal) const
    {
        OUStringBuffer bff;

        bff.append("ftp://");

        if( m_aUsername != "anonymous" ) {
            bff.append(m_aUsername);

            OUString aPassword,aAccount;
            m_pFCP->forHost(m_aHost,
                            m_aPort,
                            m_aUsername,
                            aPassword,
                            aAccount);

            if((internal || m_bShowPassword) && !aPassword.isEmpty())
                bff.append(':')
                    .append(aPassword);

            bff.append('@');
        }

        bff.append(m_aHost);

        if( m_aPort != "21" )
            bff.append(':')
                .append(m_aPort)
                .append('/');
        else
            bff.append('/');

        OUString last;

        for(size_t i = 0; i < m_aPathSegmentVec.size(); ++i)
            if(1+i == m_aPathSegmentVec.size())
                last = m_aPathSegmentVec[i];
            else if(i == 0)
                bff.append(m_aPathSegmentVec[i]);
            else
                bff.append('/').append(m_aPathSegmentVec[i]);

        if(last.isEmpty())
            bff.append("..");
        else if ( last == ".." )
            bff.append(last).append("/..");

        bff.append(m_aType);
        return bff.makeStringAndClear();
    }

    void FTPURL::child(const OUString& title)
    {
        m_aPathSegmentVec.push_back(encodePathSegment(title));
    }

    OUString FTPURL::child() const
    {
        return
            m_aPathSegmentVec.size() ?
            decodePathSegment(m_aPathSegmentVec.back()) : OUString();
    }

    /** Listing of a directory.
     */

    namespace ftp {

        enum OS {
            FTP_DOS,FTP_UNIX,FTP_VMS,FTP_UNKNOWN
        };

    }

    #define SET_CONTROL_CONTAINER \
        MemoryContainer control;                                      \
        curl_easy_setopt(curl,                                        \
                         CURLOPT_HEADERFUNCTION,                      \
                         memory_write);                               \
        curl_easy_setopt(curl,                                        \
                         CURLOPT_WRITEHEADER,                         \
                         &control)

    #define SET_URL(url)                                              \
        OString urlParAscii(url.getStr(),                        \
                                 url.getLength(),                     \
                                 RTL_TEXTENCODING_UTF8);              \
        curl_easy_setopt(curl,                                        \
                         CURLOPT_URL,                                 \
                         urlParAscii.getStr());

    oslFileHandle FTPURL::open()
    {
        if(m_aPathSegmentVec.empty())
            throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE);

        CURL *curl = m_pFCP->handle();

        SET_CONTROL_CONTAINER;
        OUString url(ident(false,true));
        SET_URL(url);

        oslFileHandle res( nullptr );
        if ( osl_createTempFile( nullptr, &res, nullptr ) == osl_File_E_None )
        {
            curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,file_write);
            curl_easy_setopt(curl,CURLOPT_WRITEDATA,res);

            curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);
            CURLcode err = curl_easy_perform(curl);

            if(err == CURLE_OK)
            {
                oslFileError rc = osl_setFilePos( res, osl_Pos_Absolut, 0 );
                SAL_WARN_IF(rc != osl_File_E_None, "ucb.ucp.ftp",
                        "osl_setFilePos failed");
            }
            else {
                osl_closeFile(res);
                res = nullptr;
                throw curl_exception(err);
            }
        }

        return res;
    }

    std::vector<FTPDirentry> FTPURL::list(
        sal_Int16 nMode
    ) const
    {
        CURL *curl = m_pFCP->handle();

        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,false);
        MemoryContainer data;
        curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write);
        curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data);

        OUString url(ident(true,true));
        SET_URL(url);
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);

        CURLcode err = curl_easy_perform(curl);
        if(err != CURLE_OK)
            throw curl_exception(err);

        // now evaluate the error messages

        sal_uInt32 len = data.m_nWritePos;
        char* fwd = static_cast<char*>(data.m_pBuffer);
        char *p1, *p2;
        p1 = p2 = fwd;

        OS osKind(FTP_UNKNOWN);
        std::vector<FTPDirentry> resvec;
        FTPDirentry aDirEntry;
        // ensure slash at the end
        OUString viewurl(ident(true,false));

        while(true) {
            while(p2-fwd < int(len) && *p2 != '\n') ++p2;
            if(p2-fwd == int(len)) break;

            *p2 = 0;
            switch(osKind) {
                // While FTP knows the 'system'-command,
                // which returns the operating system type,
                // this is not usable here: There are Windows-server
                // formatting the output like UNIX-ls command.
            case FTP_DOS:
                FTPDirectoryParser::parseDOS(aDirEntry,p1);
                break;
            case FTP_UNIX:
                FTPDirectoryParser::parseUNIX(aDirEntry,p1);
                break;
            case FTP_VMS:
                FTPDirectoryParser::parseVMS(aDirEntry,p1);
                break;
            default:
                if(FTPDirectoryParser::parseUNIX(aDirEntry,p1))
                    osKind = FTP_UNIX;
                else if(FTPDirectoryParser::parseDOS(aDirEntry,p1))
                    osKind = FTP_DOS;
                else if(FTPDirectoryParser::parseVMS(aDirEntry,p1))
                    osKind = FTP_VMS;
            }
            aDirEntry.m_aName = aDirEntry.m_aName.trim();
            if( osKind != int(FTP_UNKNOWN) && aDirEntry.m_aName != ".." && aDirEntry.m_aName != "." ) {
                aDirEntry.m_aURL = viewurl + encodePathSegment(aDirEntry.m_aName);

                bool isDir = (aDirEntry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) == INETCOREFTP_FILEMODE_ISDIR;
                switch(nMode) {
                    case OpenMode::DOCUMENTS:
                        if(!isDir)
                            resvec.push_back(aDirEntry);
                        break;
                    case OpenMode::FOLDERS:
                        if(isDir)
                            resvec.push_back(aDirEntry);
                        break;
                    default:
                        resvec.push_back(aDirEntry);
                };
            }
            aDirEntry.clear();
            p1 = p2 + 1;
        }

        return resvec;
    }

    OUString FTPURL::net_title() const
    {
        CURL *curl = m_pFCP->handle();

        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,true);       // no data => no transfer
        struct curl_slist *slist = nullptr;
        // post request
        slist = curl_slist_append(slist,"PWD");
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);

        bool try_more(true);
        CURLcode err;
        OUString aNetTitle;

        while(true) {
            OUString url(ident(false,true));

            if(try_more && !url.endsWith("/"))
                url += "/";  // add end-slash
            else if(!try_more && url.endsWith("/"))
                url = url.copy(0,url.getLength()-1);         // remove end-slash

            SET_URL(url);
            err = curl_easy_perform(curl);

            if(err == CURLE_OK) {       // get the title from the server
                char* fwd = static_cast<char*>(control.m_pBuffer);
                sal_uInt32 len = control.m_nWritePos;

                aNetTitle = OUString(fwd,len,RTL_TEXTENCODING_UTF8);
                // the buffer now contains the name of the file;
                // analyze the output:
                // Format of current working directory:
                // 257 "/bla/bla" is current directory
                sal_Int32 index1 = aNetTitle.lastIndexOf("257");
                index1 = aNetTitle.indexOf('"', index1 + std::strlen("257")) + 1;
                sal_Int32 index2 = aNetTitle.indexOf('"', index1);
                aNetTitle = index2 > index1
                    ? aNetTitle.copy(index1, index2 - index1) : OUString();
                if( aNetTitle != "/" ) {
                    index1 = aNetTitle.lastIndexOf('/');
                    aNetTitle = aNetTitle.copy(1+index1);
                }
                try_more = false;
            } else if(err == CURLE_BAD_PASSWORD_ENTERED)
                // the client should retry after getting the correct
                // username + password
                throw curl_exception(err);
    #if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
            else if(err == CURLE_LOGIN_DENIED)
                // the client should retry after getting the correct
                // username + password
                throw curl_exception(err);
    #endif
            else if(try_more && err == CURLE_FTP_ACCESS_DENIED) {
                // We  were  either denied access when trying to login to
                //  an FTP server or when trying to change working directory
                //  to the one given in the URL.
                if(!m_aPathSegmentVec.empty())
                    // determine title form url
                    aNetTitle = decodePathSegment(m_aPathSegmentVec.back());
                else
                    // must be root
                    aNetTitle = "/";
                try_more = false;
            }

            if(try_more)
                try_more = false;
            else
                break;
        }

        curl_slist_free_all(slist);
        return aNetTitle;
    }

    FTPDirentry FTPURL::direntry() const
    {
        OUString nettitle = net_title();
        FTPDirentry aDirentry;

        aDirentry.m_aName = nettitle;                 // init aDirentry
        if( nettitle == "/" || nettitle == ".." )
            aDirentry.m_nMode = INETCOREFTP_FILEMODE_ISDIR;
        else
            aDirentry.m_nMode = INETCOREFTP_FILEMODE_UNKNOWN;

        aDirentry.m_nSize = 0;

        if( nettitle != "/" ) {
            // try to open the parent directory
            FTPURL aURL(parent(),m_pFCP);

            std::vector<FTPDirentry> aList = aURL.list(OpenMode::ALL);

            for(FTPDirentry & d : aList) {
                if(d.m_aName == nettitle) { // the relevant file is found
                    aDirentry = d;
                    break;
                }
            }
        }
        return aDirentry;
    }

    extern "C" {

        size_t memory_read(void *ptr,size_t size,size_t nmemb,void *stream)
        {
            sal_Int32 nRequested = sal_Int32(size*nmemb);
            CurlInput *curlInput = static_cast<CurlInput*>(stream);
            if(curlInput)
                return size_t(curlInput->read(static_cast<sal_Int8*>(ptr),nRequested));
            else
                return 0;
        }

    }

    void FTPURL::insert(bool replaceExisting,void* stream) const
    {
        if(!replaceExisting) {
    //          FTPDirentry aDirentry(direntry());
    //          if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
            // throw curl_exception(FILE_EXIST_DURING_INSERT);
            throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT);
        } // else
        // overwrite is default in libcurl

        CURL *curl = m_pFCP->handle();

        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,false);    // no data => no transfer
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);
        curl_easy_setopt(curl,CURLOPT_QUOTE,0);
        curl_easy_setopt(curl,CURLOPT_READFUNCTION,memory_read);
        curl_easy_setopt(curl,CURLOPT_READDATA,stream);
        curl_easy_setopt(curl, CURLOPT_UPLOAD,1);

        OUString url(ident(false,true));
        SET_URL(url);

        CURLcode err = curl_easy_perform(curl);
        curl_easy_setopt(curl, CURLOPT_UPLOAD,false);

        if(err != CURLE_OK)
            throw curl_exception(err);
    }

    void FTPURL::mkdir(bool ReplaceExisting) const
    {
        OString title;
        if(!m_aPathSegmentVec.empty()) {
            OUString titleOU = m_aPathSegmentVec.back();
            titleOU = decodePathSegment(titleOU);
            title = OString(titleOU.getStr(),
                                titleOU.getLength(),
                                RTL_TEXTENCODING_UTF8);
        }
        else
            // will give an error
            title = OString("/");

        OString aDel("del "); aDel += title;
        OString mkd("mkd "); mkd += title;

        struct curl_slist *slist = nullptr;

        FTPDirentry aDirentry(direntry());
        if(!ReplaceExisting) {
    //          if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
    //              throw curl_exception(FOLDER_EXIST_DURING_INSERT);
            throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT);
        } else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
            slist = curl_slist_append(slist,aDel.getStr());

        slist = curl_slist_append(slist,mkd.getStr());

        CURL *curl = m_pFCP->handle();
        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,true);       // no data => no transfer
        curl_easy_setopt(curl,CURLOPT_QUOTE,0);

        // post request
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);

        OUString url(parent(true));
        if(!url.endsWith("/"))
            url += "/";
        SET_URL(url);

        CURLcode err = curl_easy_perform(curl);
        curl_slist_free_all(slist);
        if(err != CURLE_OK)
            throw curl_exception(err);
    }

    OUString FTPURL::ren(const OUString& NewTitle)
    {
        CURL *curl = m_pFCP->handle();

        // post request
        OString renamefrom("RNFR ");
        OUString OldTitle = net_title();
        renamefrom +=
            OString(OldTitle.getStr(),
                         OldTitle.getLength(),
                         RTL_TEXTENCODING_UTF8);

        OString renameto("RNTO ");
        renameto +=
            OString(NewTitle.getStr(),
                         NewTitle.getLength(),
                         RTL_TEXTENCODING_UTF8);

        struct curl_slist *slist = nullptr;
        slist = curl_slist_append(slist,renamefrom.getStr());
        slist = curl_slist_append(slist,renameto.getStr());
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);

        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,true);       // no data => no transfer
        curl_easy_setopt(curl,CURLOPT_QUOTE,0);

        OUString url(parent(true));
        if(!url.endsWith("/"))
            url += "/";
        SET_URL(url);

        CURLcode err = curl_easy_perform(curl);
        curl_slist_free_all(slist);
        if(err != CURLE_OK)
            throw curl_exception(err);
        else if( m_aPathSegmentVec.size() && m_aPathSegmentVec.back() != ".." )
            m_aPathSegmentVec.back() = encodePathSegment(NewTitle);
        return OldTitle;
    }

    void FTPURL::del() const
    {
        FTPDirentry aDirentry(direntry());

        OString dele(aDirentry.m_aName.getStr(),
                          aDirentry.m_aName.getLength(),
                          RTL_TEXTENCODING_UTF8);

        if(aDirentry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) {
            std::vector<FTPDirentry> vec = list(sal_Int16(OpenMode::ALL));
            for(const FTPDirentry & i : vec)
            {
                try {
                    FTPURL url(i.m_aURL,m_pFCP);
                    url.del();
                } catch(const curl_exception&) {
                }
            }
            dele = OString("RMD ") + dele;
        }
        else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
            dele = OString("DELE ") + dele;
        else
            return;

        // post request
        CURL *curl = m_pFCP->handle();
        struct curl_slist *slist = nullptr;
        slist = curl_slist_append(slist,dele.getStr());
        curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);

        SET_CONTROL_CONTAINER;
        curl_easy_setopt(curl,CURLOPT_NOBODY,true);       // no data => no transfer
        curl_easy_setopt(curl,CURLOPT_QUOTE,0);

        OUString url(parent(true));
        if(!url.endsWith("/"))
            url += "/";
        SET_URL(url);

        CURLcode err = curl_easy_perform(curl);
        curl_slist_free_all(slist);
        if(err != CURLE_OK)
            throw curl_exception(err);
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer 'm_pBuffer' is lost. Consider assigning realloc() to a temporary pointer.