【问题标题】:C++ Libcurl causing write access violation in readfunction callback on larger contentC++ Libcurl 在较大内容的 readfunction 回调中导致写访问冲突
【发布时间】:2018-12-11 02:16:51
【问题描述】:

我正在开发一个 C++ 项目,我正在使用 libcurl 通过 SMTP 发送电子邮件。该代码几乎适用于小内容,但是,在较大的电子邮件中,它会引发写访问冲突,我看不出任何原因。

以下是我如何使用 curl 功能发送邮件:

curl = curl_easy_init();
        //curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
        if (curl)
        {
            if (this->useVerboseOutput)
            {
                curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
            }
            curl_easy_setopt(curl, CURLOPT_URL, smtpAddress.c_str());


            if (this->useTLS)
            {
                curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
            }
            if (this->useAuthentication)
            {
                if (this->username.empty() || this->password.empty())
                {
                    throw logic_error("SMTP username or password has not been set but authentication is enabled");
                }
                curl_easy_setopt(curl, CURLOPT_USERNAME, this->username.c_str());
                curl_easy_setopt(curl, CURLOPT_PASSWORD, this->password.c_str());
            }

            curl_easy_setopt(curl, CURLOPT_MAIL_FROM, this->fromAddress.c_str());
            curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
            curl_easy_setopt(curl, CURLOPT_READDATA, this);
            curl_easy_setopt(curl, CURLOPT_READFUNCTION, &EmailSender::invoke_write_data);
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

            //Send the message
            res = curl_easy_perform(curl);

下面是read函数回调

size_t EmailSender::invoke_write_data(void *data, size_t size, size_t nmemb, void* pInstance)
{
    return ((EmailSender*)pInstance)->payload_source(data, size, nmemb);
}

size_t EmailSender::payload_source(void *ptr, size_t size, size_t nmemb)
{
    //struct upload_status *upload_ctx = (struct upload_status*)userp;
    const char *data;

    if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) {
        return 0;
    }

    if (this->upload_ctx.lines_read < this->lineArray.size())
    {
        data = this->lineArray.at(this->upload_ctx.lines_read).c_str();
    }
    else
    {
        return 0;
    }

    if (data) {
        size_t len = strlen(data);
        memcpy(ptr, data, len);
        this->upload_ctx.lines_read++;

        return len;
    }

    return 0;
}

在第 5 次调用后,它在this-&gt;upload_ctx.lines_read++; 线上崩溃(向量 lineArray 中有 6 行,upload_ctx->lines_read 为 5。

完整的错误信息是:

Exception thrown at 0x00007FFF4E8F16D7 (vcruntime140d.dll) in myapp.exe: 0xC0000005: Access violation writing location 0x00000205CC8AC000.

【问题讨论】:

  • 正如您在代码中看到的,我有一个静态方法,由 libcurl 读取函数调用,然后将 userp 指针转换回类实例以调用我的函数来完成工作。如果它与此相关,它将不适用于任何请求,而不仅仅是大请求会有问题

标签: c++ smtp libcurl


【解决方案1】:

根据the documentation of CURLOPT_READFUNCTION

概要

#include <curl/curl.h>
size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_READFUNCTION, read_callback);

说明

传递一个指向回调函数的指针,如上面的原型所示。

libcurl 在需要读取数据以将其发送到对等方时立即调用此回调函数 - 就像您要求它向服务器上传或发布数据一样。 指针缓冲区所指向的数据区域应该被最多 size 乘以nitems 函数的字节数

你写道:

size_t len = strlen(data);
memcpy(ptr, data, len);

由于len 仅取决于您要发送的数据,并且由于您不检查它是否小于size*nitems(为您提供nmemb),因此您可能会写出libcurl分配的缓冲区,因此调用未定义行为。

由于您按行工作,而 libcurl 按字节工作,因此您需要重新设计应用程序以跟踪部分写入的行,或者完全放弃行的概念。

【讨论】:

    【解决方案2】:

    简短回答:我认为您需要向 recipients 变量添加内容。

    您没有提供足够的信息来检查您的 c++ 实现,因此对此无话可说。我将您的代码稍微翻译回 C 并确认它按 cURL smtp-mail.c 示例中的预期工作。

    soquestsmtp-mail.cpp

    #include "pch.h"
    
    enum optionuses : uint32_t
    {
        useVerboseOutput    = 1 << 1, // 0x02
        useTLS              = 1 << 2, // 0x04
        useAuthentication   = 1 << 3, // 0x08
    };
    
    struct upload_status {
        int lines_read;
    };
    typedef struct upload_status* pupload_status;
    
    static size_t __cdecl invoke_write_data(void* buffer, size_t size, size_t nmemb, void* pInstance);
    static size_t __cdecl payload_source(void* buffer, size_t size, size_t nmemb);
    static size_t __cdecl read_callback(void* buffer, size_t size, size_t nitems, void* instream);
    
    static const char* payload_text[] = {
        "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n",
        "To: <addressee@example.net>\r\n",
        "From: <sender@example.org>\r\n",
        "Cc: <info@example.org>\r\n",
        "Message-ID: <dcd7cb36-11db-487a-9f3a-e652a9458efd@"
        "rfcpedant.example.org>\r\n",
        "Subject: SMTP example message\r\n",
        "\r\n", /* empty line to divide headers from body, see RFC5322 */
        "The body of the message starts here.\r\n",
        "\r\n",
        "It could be a lot of lines, could be MIME encoded, whatever.\r\n",
        "Check RFC5322.\r\n",
        nullptr
    };
    
    
    extern "C" int __cdecl 
    wmain(_In_ int argc,_In_reads_(argc) _Pre_z_ wchar_t** argv,_In_z_ wchar_t** envp)
    {
        argc;argv;envp;
    
        CURL* curl = nullptr;
        int res = CURLE_OK;
        uint32_t options = useVerboseOutput|useTLS|useAuthentication;
    
        std::string smtpAddress = "smtp://mail.example.com";
        std::string fromAddress = "<sender@example.org>";
        std::string toAddress = "<addressee@example.net>";
        std::string ccAddress = "<info@example.org>";
    
        std::string username = "sockerconny";
        std::string password = "love_mom";
    
        struct curl_slist* recipients = nullptr;
        struct upload_status upload_ctx;
        upload_ctx.lines_read = 0;
    
        curl = curl_easy_init();
        //curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
        if (curl)
        {
            if (options & useVerboseOutput)
            {
                curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
            }
            curl_easy_setopt(curl, CURLOPT_URL, smtpAddress.c_str());
            if (options & useTLS)
            {
                curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
            }
            if (options & useAuthentication)
            {
                if (username.empty() || password.empty())
                {
                    // throw std::logic_error("SMTP username or password has not been set but authentication is enabled");
                }
                curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
                curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
            }
    
            curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromAddress.c_str());
            recipients = curl_slist_append(recipients, toAddress.c_str());
            recipients = curl_slist_append(recipients, ccAddress.c_str());
            curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
            curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
            curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    
            //Send the message
            res = curl_easy_perform(curl);
    
            curl_slist_free_all(recipients);
            curl_easy_cleanup(curl);
        }
    }
    
    size_t __cdecl read_callback(void* buffer, size_t size, size_t nitems, void *instream)
    {
        pupload_status pupload_ctx = (pupload_status)instream;
        const char* data = nullptr;
    
        if((size == 0) || (nitems == 0) || ((size*nitems) < 1)) {
            return 0;
        }
    
        data = payload_text[pupload_ctx->lines_read];
    
        if(data) {
            size_t len = strlen(data);
            memcpy(buffer, data, len);
            pupload_ctx->lines_read++;
    
            return len;
        }
    
        return 0;
    }
    

    pch.h

    // pch.h - precompiled header with standard includes and definitions
    
    #pragma once
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    #define NOMINMAX
    
    #define DISABLE_WARNING_PUSH(x) \
        __pragma(warning(push));    __pragma(warning(disable: x))
    #define DISABLE_WARNING_POP     __pragma(warning(pop))
    
    #define  _WIN32_WINNT   0x0601  // minimum Windows 7
    #include <winsdkver.h>
    #include <sdkddkver.h>
    #ifndef WINAPI_FAMILY
    #define WINAPI_FAMILY WINAPI_FAMILY_DESKTOP_APP
    #endif
    
    // disable useless MSVC warnings when compiling with -Wall
    #pragma warning(disable: 4514 4710 4711)
    // comment out for diagnostic messages, usually safe to ignore
    #pragma warning(disable: 4625 4626 4820)
    
    // temporary disable warnings when compiling with -Wall
    DISABLE_WARNING_PUSH(4191 4350 4365 4774 4571 4640 5026 5027 5039)
    #include <cstddef>
    #include <cstdint>
    #include <memory>
    #include <string>
    #include <vector>
    #include <stdexcept>
    
    #include <windows.h>
    #include <ws2tcpip.h>
    DISABLE_WARNING_POP
    
    #define CURL_STATICLIB
    #define USE_LIBSSH2
    #define HAVE_LIBSSH2_H
    #define USE_SCHANNEL
    #define USE_WINDOWS_SSPI
    #define USE_WIN32_IDN
    #define WANT_IDN_PROTOTYPES
    
    #include <curl/curl.h>
    

    【讨论】:

      猜你喜欢
      • 2023-01-13
      • 1970-01-01
      • 2012-05-22
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-05
      相关资源
      最近更新 更多