结合MIME C++ library与CURL发送带附件的邮件
使用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@<span style="font-family: Arial, Helvetica, sans-serif;">test</span>.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=<span style="font-family: Arial, Helvetica, sans-serif;">readme.txt</span>"));
- 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协议这应该不是太难的问题了。