Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
30 results

ftp_server.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ftp_server.cpp 6.37 KiB
    /*
     *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
     *
     *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #include "ftp_server.h"
    
    #include "logger.h"
    #include "string_utils.h"
    #include "manager.h"
    
    #include <algorithm>
    #include <array>
    #include <stdexcept>
    #include <iterator>
    #include <cstdlib> // strtoull
    
    namespace jami {
    
    //==============================================================================
    
    FtpServer::FtpServer(const std::string& account_id,
                         const std::string& peer_uri)
        : Stream()
        , accountId_ {account_id}
        , peerUri_ {peer_uri}
    {}
    
    DRing::DataTransferId
    FtpServer::getId() const
    {
        // Because FtpServer is just the protocol on the top of a stream so the id
        // of the stream is the id of out_.
        return out_.id;
    }
    
    void
    FtpServer::close() noexcept
    {
        closeCurrentFile();
        JAMI_WARN() << "[FTP] server closed";
    }
    
    bool
    FtpServer::startNewFile()
    {
        // Request filename from client (WARNING: synchrone call!)
        DRing::DataTransferInfo info {};
        info.accountId = accountId_;
        info.peer = peerUri_;
        info.displayName = displayName_;
        info.totalSize = fileSize_;
        info.bytesProgress = 0;
        rx_ = 0;
        out_ = Manager::instance().dataTransfers->onIncomingFileRequest(info); // we block here until answer from client
        if (!out_.stream) {
            JAMI_DBG() << "[FTP] transfer aborted by client";
            closed_ = true; // send NOK msg at next read()
        } else {
            go_ = true;
        }
        return bool(out_.stream);
    }
    
    void
    FtpServer::closeCurrentFile()
    {
        if (out_.stream) {
            out_.stream->close();
            out_.stream.reset();
            closed_ = true;
        }
    }
    
    bool
    FtpServer::read(std::vector<uint8_t>& buffer) const
    {
        if (!out_.stream) {
            if (closed_) {
                closed_ = false;
                if (rx_ < fileSize_) {
                    buffer.resize(4);
                    buffer[0] = 'N'; buffer[1] = 'G'; buffer[2] = 'O'; buffer[3] = '\n';
                    JAMI_DBG() << "[FTP] sending NGO (cancel) order";
                    return true;
                }
            }
            buffer.resize(0);
        } else if (go_) {
            go_ = false;
            buffer.resize(3);
            buffer[0] = 'G'; buffer[1] = 'O'; buffer[2] = '\n';
            JAMI_DBG() << "[FTP] sending GO order";
        } else {
            // Nothing to send. Avoid to have an useless buffer filled with 0.
            buffer.resize(0);
        }
        return true;
    }
    
    bool
    FtpServer::write(const std::vector<uint8_t>& buffer)
    {
        switch (state_) {
            case FtpState::PARSE_HEADERS:
                if (parseStream(buffer)) {
                    if (!startNewFile()) {
                        headerStream_.clear();
                        headerStream_.str({}); // reset
                        return true;
                    }
                    state_ = FtpState::READ_DATA;
                    while (headerStream_) {
                        headerStream_.read(&line_[0], line_.size());
                        std::size_t count = headerStream_.gcount();
                        if (!count)
                            break;
                        auto size_needed = fileSize_ - rx_;
                        count = std::min(count, size_needed);
                        if (out_.stream)
                            out_.stream->write(reinterpret_cast<const uint8_t*>(&line_[0]), count);
                        rx_ += count;
                        if (rx_ == fileSize_) {
                            closeCurrentFile();
                            state_ = FtpState::PARSE_HEADERS;
                            return true;
                        }
                    }
                    headerStream_.clear();
                    headerStream_.str({}); // reset
                }
                break;
    
            case FtpState::READ_DATA:
            {
                if (out_.stream)
                    out_.stream->write(&buffer[0], buffer.size());
                auto size_needed = fileSize_ - rx_;
                auto read_size = std::min(buffer.size(), size_needed);
                rx_ += read_size;
                if (rx_ == fileSize_) {
                    closeCurrentFile();
                    // data may remains into the buffer: copy into the header stream for next header parsing
                    if (read_size < buffer.size())
                        headerStream_ << std::string(std::begin(buffer) + read_size, std::end(buffer));
                    state_ = FtpState::PARSE_HEADERS;
                }
            }
            break;
    
            default: break;
        }
    
        return true; // server always alive
    }
    
    bool
    FtpServer::parseStream(const std::vector<uint8_t>& buffer)
    {
        headerStream_ << std::string(std::begin(buffer), std::end(buffer));
    
        // Simple line stream parser
        while (headerStream_.getline(&line_[0], line_.size())) {
            if (parseLine(std::string(&line_[0], headerStream_.gcount()-1)))
                return true; // headers EOF, data may remain in headerStream_
        }
    
        if (headerStream_.fail())
            throw std::runtime_error("[FTP] header parsing error");
    
        headerStream_.clear();
        return false; // need more data
    }
    
    bool
    FtpServer::parseLine(const std::string& line)
    {
        if (line.empty())
            return true;
    
        // Valid line found, parse it as "key: value" and store until end of headers detection
        const auto& sep_pos = line.find(':');
        if (sep_pos == std::string::npos)
            throw std::runtime_error("[FTP] stream protocol error: bad format");
    
        handleHeader(trim(line.substr(0, sep_pos)), trim(line.substr(sep_pos+1)));
        return false;
    }
    
    void
    FtpServer::handleHeader(const std::string& key, const std::string& value)
    {
        JAMI_DBG() << "[FTP] header: '" << key << "' = '"<< value << "'";
    
        if (key == "Content-Length") {
            fileSize_ = std::strtoull(&value[0], nullptr, 10);
        } else if (key == "Display-Name") {
            displayName_ = value;
        }
    }
    
    } // namespace jami