使用CURL发送不带附件的例子,在CURL的官方范例simplesmtp.cpp和smtp_tls.cpp中,认真看看,使用不难。而发送附件的例子则没有了,需要了解MIME协议,将邮件按照MIME编码,在CURLOPT_READFUNCTION发送出去即可。直接用字符串拼凑MIME有点麻烦,可以结合MIME C++ library完成这项工作。MIME C++ library的下载地址http://www.codesink.org/mimetic_mime_library.html。
#include "stdafx.h"
#include <string>
#include <iostream>
#include <mimetic/mimetic.h>
#include <sstream>
#include <iterator>
#include <stdio.h>
#include <fstream>
int _tmain(int argc, _TCHAR* argv[])
{
mimetic::MimeEntity me;
me.header().from("me <me@domain.com>");
me.header().to("you <you@domain.com>");
me.header().subject("my first mimetic msg");
me.body().assign("hello there!");
std::cout << me <<std::endl;
return 0;
}
显示如下:
From: me <me@domain.com> //From:简称<邮箱地址>
To: you <you@domain.com> //To:简称<邮箱地址>
Subject: my first mimetic msg //邮件标题
hello there! //正文内容
这是一个最简单的MIME 流了。如果是带附件的 那么:
#include "stdafx.h"
#include <string>
#include <iostream>
#include <mimetic/mimetic.h>
#include <sstream>
#include <iterator>
#include <stdio.h>
#include <fstream>
int _tmain(int argc, _TCHAR* argv[])
{
mimetic::MultipartMixed head;
//写件人
head.header().from("<me@sina.com.cn>");
//收件人
head.header().to("<you@sina.com.cn>");
//抄送
head.header().cc("<he@sina.com.cn>;<she@sina.com.cn>;");
//标题
head.header().subject("my first mimetic msg");
//MIME协议版本
head.header().push_back(mimetic::Field("Mime-Version","1.0"));
//正文,由于是MultipartMixed,这个内容是会被忽略的
head.body().assign("hello there!");
//创建一个新的块,在这里是附件
mimetic::MimeEntity* pMe = new mimetic::MimeEntity;
//下面的代码就是开始设置这一段邮件内容的头了
//记住这里千万不能写成
//pMe->header().push_back(mimetic::Field("Content-Type","pplication/octet-stream"));
//pMe->header().push_back(mimetic::Field("name","readme.txt"));
pMe->header().push_back(mimetic::Field("Content-Type: application/octet-stream; name=readme.txt"));
//这里和上一句需要注意的地方一样,这里说明了这个块是一个附件
pMe->header().push_back(mimetic::Field("Content-Disposition : attachment; filename=readme.txt"));
//说明这个块是以BASE64编码的
pMe->header().push_back(mimetic::Field("Content-Transfer-Encoding","base64"));
//设置完头以后,加载具体内容
FILE *pfile = fopen(".\\readme.txt","rb");
char buffer[4096];
uint32_t totalreadbytes = 0;
while (!feof(pfile))
{
//读文件
uint32_t readbytes = fread(buffer, 1, 4028, pfile);
if (ferror(pfile) || readbytes == 0)
{
break;
}
totalreadbytes += readbytes;
mimetic::Base64::Encoder b64;
std::stringstream temp;
std::ostreambuf_iterator<char> out(temp);
//转为BASE64编码,目标存放至std::stringstream中
mimetic::code(buffer, buffer + readbytes, b64, out);
std::string str = temp.str();
std::cout<<str;
//将转换后的内容放入块中,不要贪方便写成
//pMe->load(temp.str().begin(), temp.str().end(), mimetic::imNone);
//会崩溃的
pMe->load(str.begin(), str.end(), mimetic::imNone);
}
std::cout<<head;
return 0;
}
显示的MIME流如下:
Content-Type: multipart/mixed;
boundary="----lrsJRnnv8qrhVhSN0z5KGo2eQwzHwv_LA_MGCzpaJN87zTe1=_2_"
From: me@sina.com.cn
To: you@sina.com.cn
CC: <he@sina.com.cn>;<she@sina.com.cn>;
Subject: my first mimetic msg
Mime-Version: 1.0
------lrsJRnnv8qrhVhSN0z5KGo2eQwzHwv_LA_MGCzpaJN87zTe1=_2_
Content-Type: application/octet-stream; name="readme.txt"
Content-Disposition : attachment; filename=readme.txt
Content-Transfer-Encoding: base64
PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09
PT09PT09PT09PT09PT09DQogICAgQ09OU09MRSBBUFBMSUNBVElPTiA6IHRlc3RfbWltZXRpYyBQ
cm9qZWN0IE92ZXJ2aWV3DQo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09
PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KQXBwV2l6YXJkIGhhcyBjcmVhdGVk
IHRoaXMgdGVzdF9taW1ldGljIGFwcGxpY2F0aW9uIGZvciB5b3UuDQoNClRoaXMgZmlsZSBjb250
YWlucyBhIHN1bW1hcnkgb2Ygd2hhdCB5b3Ugd2lsbCBmaW5kIGluIGVhY2ggb2YgdGhlIGZpbGVz
IHRoYXQNCm1ha2UgdXAgeW91ciB0ZXN0X21pbWV0aWMgYXBwbGljYXRpb24uDQoNCg0KdGVzdF9t
aW1ldGljLnZjeHByb2oNCiAgICBUaGlzIGlzIHRoZSBtYWluIHByb2plY3QgZmlsZSBmb3IgVkMr
KyBwcm9qZWN0cyBnZW5lcmF0ZWQgdXNpbmcgYW4gQXBwbGljYXRpb24gV2l6YXJkLg0KICAgIEl0
IGNvbnRhaW5zIGluZm9ybWF0aW9uIGFib3V0IHRoZSB2ZXJzaW9uIG9mIFZpc3VhbCBDKysgdGhh
dCBnZW5lcmF0ZWQgdGhlIGZpbGUsIGFuZA0KICAgIGluZm9ybWF0aW9uIGFib3V0IHRoZSBwbGF0
Zm9ybXMsIGNvbmZpZ3VyYXRpb25zLCBhbmQgcHJvamVjdCBmZWF0dXJlcyBzZWxlY3RlZCB3aXRo
IHRoZQ0KICAgIEFwcGxpY2F0aW9uIFdpemFyZC4NCg0KdGVzdF9taW1ldGljLnZjeHByb2ouZmls
dGVycw0KICAgIFRoaXMgaXMgdGhlIGZpbHRlcnMgZmlsZSBmb3IgVkMrKyBwcm9qZWN0cyBnZW5l
cmF0ZWQgdXNpbmcgYW4gQXBwbGljYXRpb24gV2l6YXJkLiANCiAgICBJdCBjb250YWlucyBpbmZv
cm1hdGlvbiBhYm91dCB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiB0aGUgZmlsZXMgaW4geW91ciBw
cm9qZWN0IA0KICAgIGFuZCB0aGUgZmlsdGVycy4gVGhpcyBhc3NvY2lhdGlvbiBpcyB1c2VkIGlu
IHRoZSBJREUgdG8gc2hvdyBncm91cGluZyBvZiBmaWxlcyB3aXRoDQogICAgc2ltaWxhciBleHRl
bnNpb25zIHVuZGVyIGEgc3BlY2lmaWMgbm9kZSAoZm9yIGUuZy4gIi5jcHAiIGZpbGVzIGFyZSBh
c3NvY2lhdGVkIHdpdGggdGhlDQogICAgIlNvdXJjZSBGaWxlcyIgZmlsdGVyKS4NCg0KdGVzdF9t
aW1ldGljLmNwcA0KICAgIFRoaXMgaXMgdGhlIG1haW4gYXBwbGljYXRpb24gc291cmNlIGZpbGUu
DQoNCi8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vDQpPdGhlciBzdGFuZGFyZCBmaWxlczoNCg0KU3RkQWZ4
LmgsIFN0ZEFmeC5jcHANCiAgICBUaGVzZSBmaWxlcyBhcmUgdXNlZCB0byBidWlsZCBhIHByZWNv
bXBpbGVkIGhlYWRlciAoUENIKSBmaWxlDQogICAgbmFtZWQgdGVzdF9taW1ldGljLnBjaCBhbmQg
YSBwcmVjb21waWxlZCB0eXBlcyBmaWxlIG5hbWVkIFN0ZEFmeC5vYmouDQoNCi8vLy8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v
Ly8vLy8vLy8vDQpPdGhlciBub3RlczoNCg0KQXBwV2l6YXJkIHVzZXMgIlRPRE86IiBjb21tZW50
cyB0byBpbmRpY2F0ZSBwYXJ0cyBvZiB0aGUgc291cmNlIGNvZGUgeW91DQpzaG91bGQgYWRkIHRv
IG9yIGN1c3RvbWl6ZS4NCg0KLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8NCg==
------lrsJRnnv8qrhVhSN0z5KGo2eQwzHwv_LA_MGCzpaJN87zTe1=_2_--
在这里值得一说的是Content-Type: multipart/mixed;
boundary="----lrsJRnnv8qrhVhSN0z5KGo2eQwzHwv_LA_MGCzpaJN87zTe1=_2_"这里说明了邮件有多块,以----lrsJRnnv8qrhVhSN0z5KGo2eQwzHwv_LA_MGCzpaJN87zTe1=_2_为分割标识符。如果想给邮件中添加新的一个块,比如说,除了附件还需要个正文那么需要再new mimetic::MimeEntity* pMe
//添加一段正文
mimetic::MimeEntity* pMe2 = new mimetic::MimeEntity;
//说明块是一段文字,文字使用GB2312字符集
pMe2->header().push_back(mimetic::Field("Content-Type: text/plain; charset=GB2312"));
pMe2->header().push_back(mimetic::Field("Content-Transfer-Encoding","base64"));
//把这一块加入到整个邮件中
head.body().parts().push_back(pMe2);
到此为止所需要的流已经生成了,剩下的就是在CURL中发送出去。
// smtp_ex01.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <stdio.h>
#include <mimetic/mimetic.h>
#include <curl/curl.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <stdio.h>
/* This is a simple example showing how to send mail using libcurl's SMTP
* capabilities. It builds on the simplesmtp.c example, adding some
* authentication and transport security.
*/
#define FROM "<me@test.com.cn>"
#define TO "<you@test.com.cn>"
#define CC "<he@test.com.cn>"
#define BUFFERR_SIZE 4096
struct UserData
{
std::stringstream ss;
size_t total;
UserData()
:total(0)
{
}
};
static size_t my_read(void *ptr, size_t size, size_t nmemb, void *userp)
{
struct UserData * pstream = static_cast<struct UserData *>(userp);
//assert(pstream);
if (pstream->ss.eof())
{
//也是因为移动到末尾时tellg()返回的数据总是不对
return 0;
}
size_t before = pstream->ss.tellg();
pstream->ss.read((char*)ptr, size*nmemb);
size_t after = pstream->ss.tellg();
if (pstream->ss.eof())
{
//对std::stringstream不熟悉,不知道为什么移动到末尾后
//tellg()返回的数据总是不对
std::cout<<(pstream->total - before)<<std::endl;
return pstream->total - before;
}
std::cout<<(after - before)<<std::endl;
return after - before;
}
int main(void)
{
CURL *curl;
CURLcode res;
struct curl_slist *recipients = NULL;
mimetic::MultipartMixed head;
head.header().from("<me@test.com.cn>");
head.header().to("<you@test.com.cn>");
head.header().cc("<he@test.com.cn>");
head.header().subject("my first mimetic msg");
head.header().push_back(mimetic::Field("Mime-Version","1.0"));
head.header().push_back(mimetic::Field("Message-ID","<dcd7cb36-11db-487a-9f3a-e652a9458efd@rfcpedant.example.org>"));
head.body().assign("hello there!");
mimetic::MimeEntity* pMe = new mimetic::MimeEntity;
pMe->header().push_back(mimetic::Field("Content-Type: application/octet-stream; name=readme.txt"));
pMe->header().push_back(mimetic::Field("Content-Disposition : attachment; filename=readme.txt"));
pMe->header().push_back(mimetic::Field("Content-Transfer-Encoding","base64"));
FILE *pfile = fopen(".\\readme.txt","rb");
char buffer[BUFFERR_SIZE];
uint32_t totalreadbytes = 0;
while (!feof(pfile))
{
uint32_t readbytes = fread(buffer, 1, BUFFERR_SIZE - 1, pfile);
if (ferror(pfile) || readbytes == 0)
{
break;
}
totalreadbytes += readbytes;
mimetic::Base64::Encoder b64;
std::stringstream temp;
std::ostreambuf_iterator<char> out(temp);
buffer[readbytes] = '\0';
mimetic::code(buffer, buffer + readbytes, b64, out);
std::string str = temp.str();
pMe->load(str.begin(), str.end(), mimetic::imNone);
}
head.body().parts().push_back(pMe);
struct UserData ud;
ud.ss<<head;
ud.ss.seekg(0, std::ios::end);
ud.total = ud.ss.tellg();
ud.ss.seekg(0, std::ios::beg);
curl = curl_easy_init();
if (curl) {
/* This is the URL for your mailserver. Note the use of port 587 here,
* instead of the normal SMTP port (25). Port 587 is commonly used for
* secure mail submission (see RFC4403), but you should use whatever
* matches your server configuration. */
curl_easy_setopt(curl, CURLOPT_URL, "smtp://mainserver.example.net:25");//"smtp://mainserver.example.net:25");
/* In this example, we'll start with a plain text connection, and upgrade
* to Transport Layer Security (TLS) using the STARTTLS command. Be careful
* of using CURLUSESSL_TRY here, because if TLS upgrade fails, the transfer
* will continue anyway - see the security discussion in the libcurl
* tutorial for more details. */
//curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
/* If your server doesn't have a valid certificate, then you can disable
* part of the Transport Layer Security protection by setting the
* CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST options to 0 (false).
* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
* That is, in general, a bad idea. It is still better than sending your
* authentication details in plain text though.
* Instead, you should get the issuer certificate (or the host certificate
* if the certificate is self-signed) and add it to the set of certificates
* that are known to libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH. See
* docs/SSLCERTS for more information.
*/
//curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/certificate.pem");
/* A common reason for requiring transport security is to protect
* authentication details (user names and passwords) from being "snooped"
* on the network. Here is how the user name and password are provided: */
curl_easy_setopt(curl, CURLOPT_USERNAME, "test@test.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "*****");
/* value for envelope reverse-path */
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
/* Add two recipients, in this particular case they correspond to the
* To: and Cc: addressees in the header, but they could be any kind of
* recipient. */
recipients = curl_slist_append(recipients, TO);
recipients = curl_slist_append(recipients, CC);
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
/* In this case, we're using a callback function to specify the data. You
* could just use the CURLOPT_READDATA option to specify a FILE pointer to
* read from.
*/
curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read);
curl_easy_setopt(curl, CURLOPT_READDATA, &ud);
// curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
// curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
/* Since the traffic will be encrypted, it is very useful to turn on debug
* information within libcurl to see what is happening during the transfer.
*/
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
/* send the message (including headers) */
res = curl_easy_perform(curl);
/* free the list of recipients and clean up */
curl_slist_free_all(recipients);
curl_easy_cleanup(curl);
}
return 0;
}
test.com.cn>");
head.header().cc("<he@test.com.cn>");
head.header().subject("my first mimetic msg");
head.header().push_back(mimetic::Field("Mime-Version","1.0"));
head.header().push_back(mimetic::Field("Message-ID","<dcd7cb36-11db-487a-9f3a-e652a9458efd@rfcpedant.example.org>"));
head.body().assign("hello there!");
mimetic::MimeEntity* pMe = new mimetic::MimeEntity;
pMe->header().push_back(mimetic::Field("Content-Type: application/octet-stream; name=readme.txt"));
pMe->header().push_back(mimetic::Field("Content-Disposition : attachment; filename=readme.txt"));
pMe->header().push_back(mimetic::Field("Content-Transfer-Encoding","base64"));
FILE *pfile = fopen(".\\readme.txt","rb");
char buffer[BUFFERR_SIZE];
uint32_t totalreadbytes = 0;
while (!feof(pfile))
{
uint32_t readbytes = fread(buffer, 1, BUFFERR_SIZE - 1, pfile);
if (ferror(pfile) || readbytes == 0)
{
break;
}
totalreadbytes += readbytes;
mimetic::Base64::Encoder b64;
std::stringstream temp;
std::ostreambuf_iterator<char> out(temp);
buffer[readbytes] = '\0';
mimetic::code(buffer, buffer + readbytes, b64, out);
std::string str = temp.str();
pMe->load(str.begin(), str.end(), mimetic::imNone);
}
head.body().parts().push_back(pMe);
struct UserData ud;
ud.ss<<head;
ud.ss.seekg(0, std::ios::end);
ud.total = ud.ss.tellg();
ud.ss.seekg(0, std::ios::beg);
curl = curl_easy_init();
if (curl) {
/* This is the URL for your mailserver. Note the use of port 587 here,
* instead of the normal SMTP port (25). Port 587 is commonly used for
* secure mail submission (see RFC4403), but you should use whatever
* matches your server configuration. */
curl_easy_setopt(curl, CURLOPT_URL, "smtp://mainserver.example.net:25");//"smtp://mainserver.example.net:25");
/* In this example, we'll start with a plain text connection, and upgrade
* to Transport Layer Security (TLS) using the STARTTLS command. Be careful
* of using CURLUSESSL_TRY here, because if TLS upgrade fails, the transfer
* will continue anyway - see the security discussion in the libcurl
* tutorial for more details. */
//curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
/* If your server doesn't have a valid certificate, then you can disable
* part of the Transport Layer Security protection by setting the
* CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST options to 0 (false).
* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
* That is, in general, a bad idea. It is still better than sending your
* authentication details in plain text though.
* Instead, you should get the issuer certificate (or the host certificate
* if the certificate is self-signed) and add it to the set of certificates
* that are known to libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH. See
* docs/SSLCERTS for more information.
*/
//curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/certificate.pem");
/* A common reason for requiring transport security is to protect
* authentication details (user names and passwords) from being "snooped"
* on the network. Here is how the user name and password are provided: */
curl_easy_setopt(curl, CURLOPT_USERNAME, "test@test.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "*****");
/* value for envelope reverse-path */
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
/* Add two recipients, in this particular case they correspond to the
* To: and Cc: addressees in the header, but they could be any kind of
* recipient. */
recipients = curl_slist_append(recipients, TO);
recipients = curl_slist_append(recipients, CC);
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
/* In this case, we're using a callback function to specify the data. You
* could just use the CURLOPT_READDATA option to specify a FILE pointer to
* read from.
*/
curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read);
curl_easy_setopt(curl, CURLOPT_READDATA, &ud);
// curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
// curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
/* Since the traffic will be encrypted, it is very useful to turn on debug
* information within libcurl to see what is happening during the transfer.
*/
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
/* send the message (including headers) */
res = curl_easy_perform(curl);
/* free the list of recipients and clean up */
curl_slist_free_all(recipients);
curl_easy_cleanup(curl);
}
return 0;
}
需要注意的是MIME C++ library构造邮件流的时候是将所有内容加载至内存中的,对于大的附件还得自己拼凑MIME流,不过通过对MIME C++ library学习了MIME协议这应该不是太难的问题了。