libcurl не может правильно загрузить файл изображения - PullRequest
0 голосов
/ 20 апреля 2020

Я создал эту самую базовую c оболочку для завитков и могу загрузить с нее html страницы, но у меня возникает проблема, когда я пытаюсь получить изображения (не пробовал другие файлы).

class BasicCurlWrapper
{
    CURL* m_curlHandle{ nullptr };
    std::string m_current_url{};
    std::string m_destinationFilePath{};
    std::ofstream m_outputFile{};
    std::ios_base::openmode m_fileOpenMode{ std::ios::out };
    bool m_verbose{ false };

public:
    BasicCurlWrapper()
    {
        m_curlHandle = curl_easy_init();
    }

    ~BasicCurlWrapper()
    {
        curl_easy_cleanup(m_curlHandle);
        //curl_global_cleanup();
    }

    void downloadUrl(const std::string& url, const std::string& destination, std::ios_base::openmode openmode = std::ios::out) 
    {
        if (m_outputFile.is_open()) {
            m_outputFile.close();
        }

        m_current_url = url;
        m_destinationFilePath = destination;
        m_fileOpenMode = openmode;
        char errbuf[CURL_ERROR_SIZE] = { 0 };

        curl_easy_setopt(m_curlHandle, CURLOPT_URL, url.data());        
        curl_easy_setopt(m_curlHandle, CURLOPT_VERBOSE, m_verbose ? 1L : 0L); //Switch on full protocol/debug output while testing        
        curl_easy_setopt(m_curlHandle, CURLOPT_NOPROGRESS, 1L); //disable progress meter, set to 0L to enable it
        curl_easy_setopt(m_curlHandle, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(m_curlHandle, CURLOPT_USERAGENT, "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36");
        curl_easy_setopt(m_curlHandle, CURLOPT_WRITEFUNCTION, BasicCurlWrapper::write_data);
        curl_easy_setopt(m_curlHandle, CURLOPT_WRITEDATA, this);
        curl_easy_setopt(m_curlHandle, CURLOPT_FAILONERROR, 1L);
        curl_easy_setopt(m_curlHandle, CURLOPT_ERRORBUFFER, errbuf);
        //curl_easy_setopt(m_curlHandle, CURLOPT_ACCEPT_ENCODING, "");
        //curl_easy_setopt(m_curlHandle, CURLOPT_SSLCERT, "C:/msys64/mingw64/ssl/certs/ca-bundle.crt");

        auto res = curl_easy_perform(m_curlHandle);

        if (m_outputFile.is_open()) {
            m_outputFile.close();
        }

        if (res == CURLE_OK) {
            std::cout << "Downloaded file\n";
        } else {
            std::cout << "ERROR: " << curl_easy_strerror(res) << '\n' << errbuf << '\n';
        }
    }


    void setVerbose(bool cond)
    {
        m_verbose = cond;
    }

    //https://curl.haxx.se/mail/lib-2008-09/0250.html
    static std::size_t write_data(const char* ptr, const std::size_t size, const std::size_t nmemb, void* classIntance)
    {

        if (nmemb > 0) {
            static_cast<BasicCurlWrapper*>(classIntance)->writeToFile(ptr, nmemb);
        }
        return nmemb;
    }

private:

    void writeToFile(const char* ptr, const std::size_t nmemb)
    {
        if (!m_outputFile.is_open()) {
            m_outputFile.open(m_destinationFilePath, m_fileOpenMode);
        }        

        if (m_outputFile.is_open()) {
            std::cout << "Writing data amount: " << nmemb << '\n';
            m_outputFile.write(ptr, nmemb);
        } else {
            auto errorMsg{ std::string{"Unable to open file: " + m_destinationFilePath } };
            throw std::runtime_error{ errorMsg };
        }
    }
};

Так что я использую это так:

 BasicCurlWrapper cr;
 cr.setVerbose(true);
 cr.downloadUrl("https://icons.iconarchive.com/icons/google/noto-emoji-activities/512/52730-soccer-ball-icon.png", "ball.png", std::ios::out | std::ios::binary);

Это что-то загружает:

‰PNG

¾M&S»Á€>öÝÀKþ駟ªC²²²Ð½{wÕ5–-[†…*7Þx½zõ¢C˜ž––L›6
555ŠÛŽ1þ³ºÂr­­­'­Å·Íê>ð^ùpAmèÀŽãœ.—«–@èEÀŒ±yJÛ)©éâàÔóÚÄ™ÄA]]¦NŠ¦æfÅ÷uÍ5Tò—+Ö­[‡¾òŠªúÕ×^CvŸ>gtò'­É·ý›œü¹QYñÇÝér¹þmöçpÁð^¯w€AJÛFâR€–tîܹ=Ï cä`íÚµX»v­âëÙív,X°€ªþa…$I¸ë®»T•¾ðÂqß}÷µÏàÛÖä:„ŠŠ
Šbª$€Ðÿ.

И хотя он начинается с PNG, это недопустимо PNG, а также оригинальный файл 39 КБ. Нужно ли отправлять дополнительные заголовки или что-то еще? Я хотел бы иметь возможность загрузить любой указанный файл.

Я использовал vcpkg для получения libcurl:

curl:x64-windows                                   7.68.0

РЕДАКТИРОВАТЬ:

Я обновил код, чтобы отразить ответ @Some программиста, чувак, который я сейчас использую write для вывода данных в файл. Это исправлено для примера изображения, которое я использовал.

Проблема, с которой я столкнулся, связана с другим изображением, которое я пытаюсь загрузить.

cr.downloadUrl("https://v217.mangabeast.com/manga/Onepunch-Man/0130-007.png", "image.png", std::ios::out | std::ios::binary);

Файл image.png теперь содержит текст :

error code: 1010

Я могу загрузить это изображение, просто используя команду:

curl -O <url>

Так что я ничего не передаю с помощью команды curl, так что мне нужно передать в libcurl ??

Вот результат запроса:

 * STATE: INIT => CONNECT handle 0x24781b66728; line 1605 (connection #-5000)
 * Added connection 0. The cache now contains 1 members
 * STATE: CONNECT => WAITRESOLVE handle 0x24781b66728; line 1646 (connection #0)
 *   Trying 104.31.15.158:443...
 * TCP_NODELAY set
 * STATE: WAITRESOLVE => WAITCONNECT handle 0x24781b66728; line 1725 (connection #0)
 * Connected to v217.mangabeast.com (104.31.15.158) port 443 (#0)
 * STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x24781b66728; line 1781 (connection #0)
 * Marked for [keep alive]: HTTP default
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 1/3)
 * schannel: checking server certificate revocation
 * schannel: sending initial handshake data: sending 184 bytes...
 * schannel: sent initial handshake data: sent 184 bytes
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: failed to receive handshake, need more data
 * STATE: SENDPROTOCONNECT => PROTOCONNECT handle 0x24781b66728; line 1796 (connection #0)
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: encrypted data got 2709
 * schannel: encrypted data buffer: offset 2709 length 4096
 * schannel: sending next handshake data: sending 93 bytes...
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: encrypted data got 258
 * schannel: encrypted data buffer: offset 258 length 4096
 * schannel: SSL/TLS handshake complete
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 3/3)
 * schannel: stored credential handle in session cache
 * STATE: PROTOCONNECT => DO handle 0x24781b66728; line 1815 (connection #0)
> GET /manga/Onepunch-Man/0130-007.png HTTP/1.1
Host: v217.mangabeast.com
User-Agent: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36
Accept: */*

 * STATE: DO => DO_DONE handle 0x24781b66728; line 1870 (connection #0)
 * STATE: DO_DONE => PERFORM handle 0x24781b66728; line 1991 (connection #0)
 * schannel: client wants to read 16384 bytes
 * schannel: encdata_buffer resized 17408
 * schannel: encrypted data buffer: offset 0 length 17408
 * schannel: encrypted data got 674
 * schannel: encrypted data buffer: offset 674 length 17408
 * schannel: decrypted data length: 611
 * schannel: decrypted data added: 611
 * schannel: decrypted cached: offset 611 length 16384
 * schannel: encrypted data length: 34
 * schannel: encrypted cached: offset 34 length 17408
 * schannel: decrypted data length: 5

EDIT2:

Теперь я добавил проверку ошибок, а также ошибку по ошибке. Я получил следующее:

ERROR: HTTP response code said error
The requested URL returned error: 403 Forbidden

Я не понимаю, как я получаю 403, так как использование cURL через командную строку дает мне изображение.

EDIT 3:

Только что заметил, что строка пользовательского агента имеет User-Agent:, после ввода действительного пользовательского агента я получил файл!

1 Ответ

1 голос
/ 20 апреля 2020

У вас есть две проблемы, обе из-за того, что вы обрабатываете полученные данные как текст.

Первая проблема заключается в том, что вы открываете файл в текстовом режиме, что может означать, что определенные байты переведены в другие байты (или даже несколько других байтов). Наиболее распространенным таким переводом является перевод строки '\n', который на Windows обычно переводится в двухсимвольную последовательность '\r' и '\n'.

Вторая проблема заключается в том, что ваша функция writeToFile Предполагается, что данные являются строкой с нулевым символом в конце, а это не так. Терминатор null, используемый для строк, представляет собой просто байт со значением 0. Произвольные двоичные данные (например, изображение PNG) будут содержать ноль байтов. Вам необходимо записать данные, используя функцию write, передав фактическую длину в байтах данных, которые вы получаете через аргумент size, в функцию cURL "write data". *

Чтобы решить вашу первую проблему, вам нужно открыть файл в двоичном режиме, добавив флаг std::ios::bin при открытии файла. И вторую проблему можно решить с помощью функции write, как уже упоминалось.

...