TEST(FileTransfer, DetectsUploadConnectionError)
{
    CURL *curl;

    std::string source_file = "/accounts/1000/shared/documents/filetransfer_test.txt";
    std::string target_url = "http://127.0.0.1/uploader.php";

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, source_file.c_str(), 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, target_url.c_str(), 0), 0));

    std::string expected = "upload error 3 " + source_escaped + " " + target_escaped + " 0";

    int file_result = createTestFile(source_file.c_str());
    EXPECT_EQ(0, file_result);

    webworks::FileUploadInfo upload_info;
    upload_info.sourceFile = source_file;
    upload_info.targetURL = target_url;
    upload_info.mimeType = "text/plain";
    upload_info.fileKey = "file";
    upload_info.fileName = "test_file.txt";
    upload_info.chunkedMode = 0;

    webworks::FileTransferCurl file_transfer;
    std::string result = file_transfer.Upload(&upload_info);
    EXPECT_EQ(expected, result);

    remove(source_file.c_str());
}
TEST(FileTransfer, DetectsIncorrectUploadFilePath)
{
    CURL *curl;

    std::string source_file = "/accounts/1000/shared/camera/abcdefg.hij";
    std::string target_url = "http://bojap.com/omg/uploader.php";

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, source_file.c_str(), 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, target_url.c_str(), 0), 0));

    std::string expected = "upload error 1 " + source_escaped + " " + target_escaped + " 0";

    webworks::FileUploadInfo upload_info;
    upload_info.sourceFile = source_file;
    upload_info.targetURL = target_url;
    upload_info.mimeType = "image/jpeg";
    upload_info.fileKey = "image";
    upload_info.fileName = "new_image.jpg";
    upload_info.chunkedMode = 1;

    webworks::FileTransferCurl file_transfer;
    std::string result = file_transfer.Upload(&upload_info);

    EXPECT_EQ(expected, result);
}
// Tests for invalid target (permissions error)
TEST(FileTransfer, DetectsInvalidDownloadTargetPermissions)
{
    CURL *curl;

    std::string source = "http://www.google.ca/ig/images/jfk/google_color.png";
    std::string target = "/accounts/hello.jpg";

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, source.c_str(), 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, target.c_str(), 0), 0));

    std::string expected = "download error 4 " + source_escaped + " " + target_escaped + " 0";

    webworks::FileDownloadInfo download_info;
    download_info.source = source;
    download_info.target = target;

    webworks::FileTransferCurl file_transfer;
    std::string result = file_transfer.Download(&download_info);

    EXPECT_EQ(expected, result);
}
// Tests for connetion error
TEST(FileTransfer, DetectsIncorrectDownloadSource)
{
    CURL *curl;

    std::string source = "http://domain.does.not.exist/hello.jpg";
    std::string target = "/accounts/1000/shared/camera/hello.jpg";

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, source.c_str(), 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, target.c_str(), 0), 0));

    std::string expected = "download error 3 " + source_escaped + " " + target_escaped + " 0";

    webworks::FileDownloadInfo download_info;
    download_info.source = source;
    download_info.target = target;

    webworks::FileTransferCurl file_transfer;
    std::string result = file_transfer.Download(&download_info);

    EXPECT_EQ(expected, result);
}
std::string FileTransferCurl::Download(FileDownloadInfo *downloadInfo)
{
    CURL *curl;
    FILE *fp;
    CURLcode result;
    std::string result_string;
    bool error = 0;
    int http_status = 0;

    const char *source = (downloadInfo->source).c_str();
    const char *target = (downloadInfo->target).c_str();

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, source, 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, target, 0), 0));

    const char *targetDir = downloadInfo->target.substr(0, downloadInfo->target.find_last_of('/')).c_str();

    // Check if target directory exists with write permissions
    if (access(targetDir, R_OK)) {
        if (mkdir_p(targetDir, S_IRWXU | S_IRWXG)) {
            return buildDownloadErrorString(PERMISSIONS_ERR, source_escaped, target_escaped, http_status);
        }
    }

    curl = curl_easy_init();

    if (!curl) {
        return buildDownloadErrorString(CONNECTION_ERR, source_escaped, target_escaped, http_status);
    }

    fp = fopen(target, "wb");
    curl_easy_setopt(curl, CURLOPT_URL, source);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DownloadWriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

    // Check domain
    bool blockedDomain = false;
    const std::string parsedDomain(parseDomain(downloadInfo->source.c_str()));
    const DomainVerifyMap::iterator findDomain = m_pVerifyMap->find(parsedDomain);

    if (findDomain != m_pVerifyMap->end()) {
        if (findDomain->second) {
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        } else {
            blockedDomain = true;
        }
    }

    result = curl_easy_perform(curl);

    if (result == CURLE_SSL_CACERT) {
        if (!blockedDomain) {
            result = openDialog(curl, downloadInfo->windowGroup, parsedDomain);
        }
    }

    if (result == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);

        if (http_status >= 200 && http_status < 300) {
            result_string = buildDownloadSuccessString(true, false, downloadInfo->source.substr(downloadInfo->source.find_last_of('/')+1), downloadInfo->target);
        } else if (http_status == 404) {
            error = 1;
            result_string = buildDownloadErrorString(FILE_NOT_FOUND_ERR, source_escaped, target_escaped, http_status);
        } else if (http_status >= 400 && http_status < 500) {
            error = 1;
            result_string = buildDownloadErrorString(INVALID_URL_ERR, source_escaped, target_escaped, http_status);
        } else {
            error = 1;
            result_string = buildDownloadErrorString(CONNECTION_ERR, source_escaped, target_escaped, http_status);
        }
    } else {
        FileTransferErrorCodes error_code;

        switch (result)
        {
            case CURLE_READ_ERROR:
            case CURLE_FILE_COULDNT_READ_FILE:
                error_code = FILE_NOT_FOUND_ERR;
                break;
            case CURLE_URL_MALFORMAT:
                error_code = INVALID_URL_ERR;
                break;
            default:
                error_code = CONNECTION_ERR;
                break;
        }

        error = 1;
        result_string = buildDownloadErrorString(error_code, source_escaped, target_escaped, http_status);
    }

    fclose(fp);

    if (error) {
        remove(downloadInfo->target.c_str());
    }

    curl_easy_cleanup(curl);

    return result_string;
}
std::string FileTransferCurl::Upload(FileUploadInfo *uploadInfo)
{
    CURL *curl;
    CURLcode result;
    std::string result_string;

    struct curl_httppost *formpost = NULL;
    struct curl_httppost *lastptr = NULL;
    struct curl_slist *headerlist = NULL;

    FILE *upload_file = NULL;

    std::string source_escaped(curl_easy_escape(curl, curl_easy_escape(curl, uploadInfo->sourceFile.c_str(), 0), 0));
    std::string target_escaped(curl_easy_escape(curl, curl_easy_escape(curl, uploadInfo->targetURL.c_str(), 0), 0));

    int http_status = 0;

    // Initialize the easy interface for curl
    curl = curl_easy_init();
    if (!curl) {
        return buildUploadErrorString(CONNECTION_ERR, source_escaped, target_escaped, http_status);
    }

    // Set up the form and fill in the file upload fields
    if (uploadInfo->chunkedMode) {
        upload_file = fopen(uploadInfo->sourceFile.c_str(), "r");

        if (!upload_file) {
            return buildUploadErrorString(FILE_NOT_FOUND_ERR, source_escaped, target_escaped, http_status);
        }

        // Find the file size
        fseek(upload_file, 0L, SEEK_END);
        int file_size = ftell(upload_file);
        rewind(upload_file);

        uploadAttributes uploadAtt;

        uploadAtt.file = upload_file;
        uploadAtt.max_chunk_size = uploadInfo->chunkSize;

        curl_formadd(&formpost,
                     &lastptr,
                     CURLFORM_STREAM, &uploadAtt,
                     CURLFORM_CONTENTSLENGTH, file_size,
                     CURLFORM_COPYNAME, uploadInfo->fileKey.c_str(),
                     CURLFORM_FILENAME, uploadInfo->fileName.c_str(),
                     CURLFORM_CONTENTTYPE, uploadInfo->mimeType.c_str(),
                     CURLFORM_END);
    } else {
        curl_formadd(&formpost,
                     &lastptr,
                     CURLFORM_FILE, uploadInfo->sourceFile.c_str(),
                     CURLFORM_COPYNAME, uploadInfo->fileKey.c_str(),
                     CURLFORM_FILENAME, uploadInfo->fileName.c_str(),
                     CURLFORM_CONTENTTYPE, uploadInfo->mimeType.c_str(),
                     CURLFORM_END);
    }

    if (uploadInfo->params.size() > 0) {
        std::vector<std::string>::const_iterator it;

        for (it = uploadInfo->params.begin(); it < uploadInfo->params.end(); it++) {
            const char *key = it->c_str();
            it++;
            const char *value = it->c_str();

            curl_formadd(&formpost,
                         &lastptr,
                         CURLFORM_COPYNAME, key,
                         CURLFORM_COPYCONTENTS, value,
                         CURLFORM_END);
        }
    }

    // Set up the headers
    headerlist = curl_slist_append(headerlist, "Expect:");

    if (uploadInfo->chunkedMode) {
        headerlist = curl_slist_append(headerlist, "Transfer-Encoding: chunked");
    }

    // Set up the callbacks
    std::string write_data;
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void *>(&write_data));
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, UploadWriteCallback);

    if (uploadInfo->chunkedMode) {
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, UploadReadCallback);
    }

    // Allow redirects
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);

    // Attach the different components
    curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
    curl_easy_setopt(curl, CURLOPT_URL, uploadInfo->targetURL.c_str());

    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);

    // Check domain
    bool blockedDomain = false;
    const std::string parsedDomain(parseDomain(uploadInfo->targetURL.c_str()));
    const DomainVerifyMap::iterator findDomain = m_pVerifyMap->find(parsedDomain);

    if (findDomain != m_pVerifyMap->end()) {
        if (findDomain->second) {
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        } else {
            blockedDomain = true;
        }
    }

    // Perform file transfer (blocking)
    result = curl_easy_perform(curl);

    if (result == CURLE_SSL_CACERT) {
        if (!blockedDomain) {
            result = openDialog(curl, uploadInfo->windowGroup, parsedDomain);
        }
    }

    if (result == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
        if (http_status >= 200 && http_status < 300) {
            double bytes_sent;
            curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &bytes_sent);

            result_string = buildUploadSuccessString(bytes_sent, http_status, write_data);
        } else if (http_status == 404) {
            result_string = buildUploadErrorString(INVALID_URL_ERR, source_escaped, target_escaped, http_status);
        } else {
            result_string = buildUploadErrorString(CONNECTION_ERR, source_escaped, target_escaped, http_status);
        }
    } else {
        FileTransferErrorCodes error_code;
        switch (result)
        {
            case CURLE_READ_ERROR:
            case CURLE_FILE_COULDNT_READ_FILE:
                error_code = FILE_NOT_FOUND_ERR;
                break;
            case CURLE_URL_MALFORMAT:
                error_code = INVALID_URL_ERR;
                break;
            default:
                error_code = CONNECTION_ERR;
                break;
        }

        result_string = buildUploadErrorString(error_code, source_escaped, target_escaped, http_status);
    }

    // Clean up
    if (uploadInfo->chunkedMode) {
        fclose(upload_file);
    }

    curl_easy_cleanup(curl);
    curl_formfree(formpost);
    curl_slist_free_all(headerlist);

    return result_string;
}