首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用libcurl发送电子邮件-跟进2

使用libcurl发送电子邮件-跟进2
EN

Code Review用户
提问于 2016-09-08 00:12:10
回答 2查看 790关注 0票数 1

这个问题是:-跟踪发送电子邮件使用libcurl的延续。

这段代码有什么问题吗?我能改进一下吗?

代码语言:javascript
复制
#include <string>
#include <sstream>
#include <iostream>
#include <ctime>
#include <algorithm>
#include <random>
#include <curl/curl.h>

class EmailAddress 
{
public:
    EmailAddress(const char *email)
        : email_{std::string(email)}
        {
        }

    EmailAddress(const std::string &email)
        : email_{email.size() ? "<"  + email + ">" : ""}
        {
        }

    EmailAddress(const std::string &email, const std::string &displayName)
        : email_{email.size() ? "<" + email + ">" : ""},
          name_{"\"" + displayName + "\""}
        {
        }

    std::string domain() const
        {
            return email_.substr(email_.find('@') + 1);
        }

    explicit operator const char *() const
        {
            return email_.c_str();
        }

    friend std::ostream &operator<<(std::ostream &out, const EmailAddress &email)
        {
            return out << email.name_ << " " << email.email_;
        }

private:
    std::string email_;
    std::string name_;
};

typedef std::vector<EmailAddress> EmailAddresses;
std::ostream &operator<<(std::ostream &out, const EmailAddresses &emailAddresses);

class Email
{
public:
    Email(const EmailAddress   &from,
          const EmailAddress   &to,
          const std::string    &subject,
          const std::string    &body,
          const EmailAddresses &cc = EmailAddresses())                
        : from_{from}
        , to_{ to }
        , cc_{cc.empty() ? EmailAddresses(1, to) : cc}
        , subject_{subject}
        , body_{body}
        {
        }

    CURLcode send(const std::string &url,
                  const std::string &userName, 
                  const std::string &password);

private:
    struct StringData {
            std::string msg;
            size_t bytesLeft;
            StringData(std::string &&m) : msg{m}, bytesLeft{msg.size()} {}
            StringData(std::string  &m) = delete;
        };

    static std::string dateTimeNow_();
    static size_t payloadSource_(void *ptr, size_t size, size_t nmemb, void *userp);
    std::string generateMessageId_() const;
    std::string setPayloadText_();

    EmailAddress from_, to_;
    EmailAddresses cc_;
    std::string subject_, body_;
};

CURLcode Email::send(const std::string &url,
                     const std::string &userName,
                     const std::string &password)
{
    CURLcode ret = CURLE_OK;

    struct curl_slist *recipients = NULL;

    CURL *curl = curl_easy_init();

    StringData textData { setPayloadText_() };

    if (curl) {
        std::ostringstream cc;
        cc << cc_;

        curl_easy_setopt(curl, CURLOPT_USERNAME,     userName.c_str());
        curl_easy_setopt(curl, CURLOPT_PASSWORD,     password.c_str());
        curl_easy_setopt(curl, CURLOPT_URL,          url     .c_str());

        curl_easy_setopt(curl, CURLOPT_USE_SSL,      (long)CURLUSESSL_ALL);
        //curl_easy_setopt(curl, CURLOPT_CAINFO,       "/path/to/certificate.pem");

        curl_easy_setopt(curl, CURLOPT_MAIL_FROM,    (const char *)from_);
        recipients = curl_slist_append(recipients,   (const char *)to_);
        recipients = curl_slist_append(recipients,   cc.str().c_str());

        curl_easy_setopt(curl, CURLOPT_MAIL_RCPT,    recipients);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, payloadSource_);
        curl_easy_setopt(curl, CURLOPT_READDATA,     &textData);
        curl_easy_setopt(curl, CURLOPT_UPLOAD,       1L);
        curl_easy_setopt(curl, CURLOPT_VERBOSE,      1L);

        ret = curl_easy_perform(curl);

        if (ret != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: "
                      << curl_easy_strerror(ret)
                      << std::endl;
        }

        curl_slist_free_all(recipients);
        curl_easy_cleanup(curl);
    }

    return ret;
}

std::string Email::dateTimeNow_()
{
    const int RFC5322_TIME_LEN = 32;

    std::string ret;
    ret.resize(RFC5322_TIME_LEN);

    time_t tt;

#ifdef _MSC_VER
    time(&tt);
    tm *t = localtime(&tt);
#else
    tm tv, *t = &tv;
    tt = time(&tt);
    localtime_r(&tt, t);
#endif

    strftime(&ret[0], RFC5322_TIME_LEN, "%a, %d %b %Y %H:%M:%S %z", t);

    return ret;
}

std::string Email::generateMessageId_() const
{
    const size_t MESSAGE_ID_LEN = 37;

    tm t;
    time_t tt;
    time(&tt);

#ifdef _MSC_VER
    gmtime_s(&t, &tt);
#else
    gmtime_r(&tt, &t);
#endif

    std::string ret;
    ret.resize(MESSAGE_ID_LEN);
    size_t dateLen = std::strftime(&ret[0], MESSAGE_ID_LEN, "%Y%m%d%H%M%S", &t);

    static const std::string alphaNum {
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz" };

    std::mt19937 gen;
    std::uniform_int_distribution<> distr(0, alphaNum.length() - 1);
    std::generate_n(ret.begin() + dateLen,
                    MESSAGE_ID_LEN - dateLen,
                    [&]() { return alphaNum[distr(gen)]; });

    return ret;
}

size_t Email::payloadSource_(void *ptr, size_t size, size_t nmemb, void *userp)
{
    StringData *text = reinterpret_cast<StringData *>(userp);

    if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1) || (text->bytesLeft == 0)) {
        return 0;
    }

    if ((nmemb * size) >= text->msg.size()) {
        text->bytesLeft = 0;
        return text->msg.copy(reinterpret_cast<char *>(ptr), text->msg.size());
    }

    return 0;
}

std::string Email::setPayloadText_()
{
    std::string ret = "Date: " + dateTimeNow_() + "\r\n";

    std::ostringstream oss;
    oss << "To: "   << to_   << "\r\n"
           "From: " << from_ << "\r\n"
           "Cc: "   << cc_   << "\r\n";
    ret += oss.str();

    ret +=
        "Message-ID: <" + generateMessageId_() + "@" + from_.domain() + ">\r\n"
        "Subject: " + subject_ + "\r\n"
        "\r\n" +
        body_ + "\r\n"
        "\r\n";

    return ret;
}

std::ostream &operator<<(std::ostream &out, const EmailAddresses &emailAddresses)
{
    if (!emailAddresses.empty()) {
        auto &it = emailAddresses.begin();
        out << *it;

        while (++it != emailAddresses.end()) {
            out << "," << *it;
        }
    }

    return out;
}

int main()
{
    Email email({ "from@email.x", "FromName" },
                  "to@email.x",
                  "Subject",
                  "Body"  );

    email.send (  "smtp://smtp.email.x:25",
                  "UserName",
                  "Password"  );
}
EN

回答 2

Code Review用户

发布于 2016-10-14 09:11:48

关于payloadSource_函数中的长消息处理,我无意中遇到了同样的问题,这是一个简短的说明。问题是,如果您的消息太长(例如,base64编码中的长正文或附件),那么(nmemb * size) >= text->msg.size()始终是false,并且返回0而不将任何内容复制到curl缓冲区。然后,您发送一个空邮件而不是您的消息。

我设法用一个可以读取字节的缓冲区替换text->msg,并将payloadSource_函数更改为如下所示:

代码语言:javascript
复制
StringData*text=reinterpret_cast<StringData*>(userp);
size_t len=std::min(size*nmemb,text->bytesleft);
if(size*nmemb<1){return 0;}
if(text->bytesleft>0){
    text->bytesleft-=std::min(len,text->bytesleft);
    text->in.read(reinterpret_cast<byte*>(ptr),len);
    return len;}
return 0;

text->in是一个对象,包含带有消息的缓冲区(如char*)和指向该缓冲区中位置的指针(首先,该缓冲区的开头)。

in.readin读取len字节,并将当前位置存储在其位置指针中,以便下一次对payloadSource的调用将在该位置从in开始读取。

现在您可以多次调用payloadSource,直到您将所有消息复制到curl缓冲区中并返回0(这会向库发出结束文件的信号)。

我看了一下这里的read_callback函数https://curl.haxx.se/libcurl/c/post-callback.html来提供帮助。

希望这能帮上忙!祝你好运,干杯

V。

票数 3
EN

Code Review用户

发布于 2016-10-14 11:37:47

次要的nit是,在构造函数中,通过隐式size() == 0检查所提供的字符串是否为空。

代码语言:javascript
复制
EmailAddress(const std::string &email)
    : email_{email.size() ? "<"  + email + ">" : ""}
    {
    }

使用empty()会更有描述性

代码语言:javascript
复制
EmailAddress(const std::string &email)
    : email_{email.empty() ? "" : "<"  + email + ">"}
    {
    }
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/140805

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档