代码之家  ›  专栏  ›  技术社区  ›  Boardy

C++ LbCURL在较大内容的读写回调中造成写入访问冲突

  •  5
  • Boardy  · 技术社区  · 8 年前

    我正在研究一个C++项目,我在这里使用LIcURL通过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;
    }
    

    它撞到了绳子上 this->upload_ctx.lines_read++; 在第5次调用之后(矢量线性阵列中有6行,upload_ctx->line_read为5)。

    Exception thrown at 0x00007FFF4E8F16D7 (vcruntime140d.dll) in myapp.exe: 0xC0000005: Access violation writing location 0x00000205CC8AC000.
    
    2 回复  |  直到 8 年前
        1
  •  3
  •   YSC    8 年前

    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分配的缓冲区,因此调用未定义的行为。

        2
  •  0
  •   BeErikk    8 年前

    简而言之:我认为你需要在

    你没有提供足够的信息来检查你的c++实现,所以没人能说太多。我将您的代码翻译回了C语言,并确认它可以像cURL smtp mail.C示例中那样正常工作。

    浸水SMTP-邮件.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 - 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>
    
    推荐文章