C ++ Libcurl在较大内容上的readfunction回调中导致写访问冲突

时间:2018-07-02 20:16:25

标签: c++ smtp libcurl

我正在一个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);

下面是读取函数的回叫

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->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.

2 个答案:

答案 0 :(得分:3)

根据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个字节数。< / p>

您写道:

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

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

由于您按行工作,但libcurl按字节工作,因此您将需要重新编写应用程序以跟踪部分编写的行,或者完全删除行的概念。

答案 1 :(得分:0)

简短的回答:我认为您需要向收件人变量添加内容。

您没有提供足够的信息来检查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>