C ++ Libcurl, вызывающий нарушение прав записи при обратном вызове функции чтения для более крупного контента - PullRequest
0 голосов
/ 02 июля 2018

Я работаю над проектом 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;
}

Его сбой на линии this->upload_ctx.lines_read++; после 5-го вызова (в векторе lineArray 6 строк, а upload_ctx-> lines_read равно 5.

Полное сообщение об ошибке:

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

Ответы [ 2 ]

0 голосов
/ 13 июля 2018

Краткий ответ: я думаю, что вам нужно добавить содержимое в переменную получателей .

Вы не предоставили достаточно информации для изучения вашей реализации на С ++, поэтому мало что можно сказать по этому поводу. Я немного перевел ваш код обратно на 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>
0 голосов
/ 09 июля 2018

Согласно документации 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, следовательно, вызывать undefined поведение.

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

...