Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/src/imap4.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 133 - (show annotations) (download) (as text)
Sat Nov 11 14:29:19 2017 UTC (6 years, 6 months ago) by z0rac
File MIME type: text/x-c++src
File size: 9426 byte(s)
Fixed fetching header fields on IMAP4.
1 /*
2 * Copyright (C) 2009-2017 TSUBAKIMOTO Hiroya <z0rac@users.sourceforge.jp>
3 *
4 * This software comes with ABSOLUTELY NO WARRANTY; for details of
5 * the license terms, see the LICENSE.txt file included with the program.
6 */
7 #include "mailbox.h"
8 #include "win32.h"
9 #include <cassert>
10 #include <cstdlib>
11 #include <cstring>
12
13 #if _DEBUG >= 2
14 #include <iostream>
15 #define DBG(s) s
16 #define LOG(s) (cout << s)
17 #else
18 #define DBG(s)
19 #define LOG(s)
20 #endif
21
22 /** imap4 - imap4 protocol backend
23 * This class is a mailbox::backend for IMAP4 protocol.
24 */
25 class imap4 : public mailbox::backend {
26 unsigned _seq; // sequencial number for the tag.
27
28 // parser - imap4 response parser.
29 struct parser : public tokenizer {
30 parser() {}
31 parser(const string& s) : tokenizer(s) {}
32 string token(bool open = false);
33 };
34
35 // response - imap4 response type.
36 struct response { string tag, type, data; };
37
38 static string _utf7m(const string& s);
39 string _tag();
40 static string _arg(const string& arg);
41 string _command(const char* cmd, const char* res = NULL);
42 string _command(const string& cmd, const char* res = NULL)
43 { return _command(cmd.c_str(), res); }
44 response _response();
45 string _read();
46 unsigned _seqinit() const { return unsigned(ptrdiff_t(this)) + unsigned(time(NULL)); }
47 #ifdef _DEBUG
48 using backend::read;
49 string read()
50 {
51 string line = backend::read();
52 LOG("R: " << line << endl);
53 return line;
54 }
55 #endif
56 public:
57 imap4() : _seq(_seqinit()) {}
58 void login(const uri& uri, const string& passwd);
59 void logout();
60 size_t fetch(mailbox& mbox, const uri& uri);
61 };
62
63 void
64 imap4::login(const uri& uri, const string& passwd)
65 {
66 static const char notimap[] = "server not IMAP4 compliant";
67 response resp = _response();
68 bool preauth = resp.type == "PREAUTH";
69 if (resp.tag != "*" || (!preauth && resp.type != "OK")) {
70 throw mailbox::error(notimap);
71 }
72 bool imap = false;
73 bool stls = false;
74 static const char CAPABILITY[] = "CAPABILITY";
75 static const char STARTTLS[] = "STARTTLS";
76 string cap = _command(CAPABILITY, CAPABILITY);
77 for (parser caps(cap); caps;) {
78 string s = caps.token();
79 if (s == "IMAP4" || s == "IMAP4REV1") imap = true;
80 else if (s == STARTTLS) stls = true;
81 }
82 if (!imap) throw mailbox::error(notimap);
83 if (!preauth) {
84 if (stls && !tls()) {
85 _command(STARTTLS);
86 starttls(uri[uri::host]);
87 cap = _command(CAPABILITY, CAPABILITY);
88 }
89 for (parser caps(cap); caps;) {
90 if (caps.token() == "LOGINDISABLED") {
91 throw mailbox::error("login disabled");
92 }
93 }
94 _command("LOGIN" + _arg(uri[uri::user]) + _arg(passwd));
95 }
96 }
97
98 void
99 imap4::logout()
100 {
101 _command("LOGOUT");
102 }
103
104 size_t
105 imap4::fetch(mailbox& mbox, const uri& uri)
106 {
107 const string& path = uri[uri::path];
108 _command("EXAMINE" + _arg(!path.empty() ? _utf7m(path) : "INBOX"));
109 list<mail> mails;
110 list<mail> recents;
111 for (parser ids(_command("UID SEARCH UNSEEN", "SEARCH")); ids;) {
112 string uid = ids.token();
113 const mail* p = mbox.find(uid);
114 if (p) {
115 mails.push_back(*p);
116 continue;
117 }
118 LOG("Fetch mail: " << uid << endl);
119 parser parse(_command("UID FETCH " + uid +
120 " BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]",
121 "FETCH"));
122 parse.token(); // drop sequence#
123 if (parse.peek() != '(') throw mailbox::error(parse.data());
124 for (parse = parse.token(true); parse;) {
125 string item = parse.token();
126 string value = parse.token();
127 if (item.size() > 20 && item.substr(0, 20) == "BODY[HEADER.FIELDS (") {
128 mail m(uid);
129 m.header(value);
130 recents.push_back(m);
131 break;
132 }
133 }
134 }
135 size_t count = recents.size();
136 mails.splice(mails.end(), recents);
137 mbox.mails(mails);
138 return count;
139 }
140
141 string
142 imap4::_utf7m(const string& s)
143 {
144 string::size_type i = 0;
145 while (i < s.size() && s[i] >= ' ' && s[i] <= '~' && s[i] != '&') ++i;
146 if (i == s.size()) return s;
147
148 // encode path by modified UTF-7.
149 string result = s.substr(0, i);
150 win32::wstr ws = s.c_str() + i;
151 for (LPCWSTR p = ws; *p;) {
152 result += '&';
153 if (*p != '&') {
154 static const char b64[] =
155 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
156 unsigned wc = 0;
157 int n = 0;
158 while (*p && (*p < ' ' || *p > '~')) {
159 wc = (wc << 16) + *p++, n += 16;
160 for (; n >= 6; n -= 6) result += b64[(wc >> (n - 6)) & 63];
161 }
162 if (n) result += b64[(wc << (6 - n)) & 63];
163 } else ++p;
164 result += '-';
165 for (; *p && *p >= ' ' && *p <= '~' && *p != '&'; ++p) {
166 result += static_cast<string::value_type>(*p);
167 }
168 }
169 return result;
170 }
171
172 string
173 imap4::_tag()
174 {
175 unsigned n = _seq++;
176 char s[4];
177 s[3] = '0' + n % 10, n /= 10;
178 for (int i = 3; i--;) s[i] = 'A' + (n & 15), n >>= 4;
179 return string(s, 4);
180 }
181
182 string
183 imap4::_arg(const string& arg)
184 {
185 if (!arg.empty()) {
186 string::const_iterator p = arg.begin();
187 while (p != arg.end() &&
188 *p > 32 && *p < 127 && !strchr("(){%*\"\\", *p)) ++p;
189 if (p == arg.end()) return ' ' + arg;
190 }
191 string esc(" \"");
192 for (string::size_type i = 0;;) {
193 string::size_type n = arg.find_first_of("\"\\", i);
194 esc.append(arg, i, n - i);
195 if (n == string::npos) break;
196 char qst[] = { '\\', arg[n] };
197 esc.append(qst, 2);
198 i = n + 1;
199 }
200 return esc + '"';
201 }
202
203 string
204 imap4::_command(const char* cmd, const char* res)
205 {
206 const string tag = _tag();
207 // send a command message to the server.
208 write(tag + ' ' + cmd);
209 LOG("S: " << tag << " " << cmd << endl);
210
211 response resp;
212 string untagged;
213 bool bye = false;
214 for (;;) {
215 resp = _response();
216 if (res && resp.type == "OK") {
217 parser parse(resp.data);
218 if (parse.peek() == '[') {
219 parse = parse.token(true);
220 if (parse.token() == res) untagged = parse.remain();
221 }
222 }
223 if (resp.tag != "*") break;
224 if (resp.type == "BYE") bye = true;
225 if (res && resp.type == res) untagged = resp.data;
226 }
227 if (resp.tag != tag) {
228 throw mailbox::error("unexpected tagged response");
229 }
230 if (bye && _stricmp(cmd, "LOGOUT") != 0) throw mailbox::error("bye");
231 if (resp.type != "OK") {
232 throw mailbox::error(resp.type + ' ' + resp.data);
233 }
234 return untagged;
235 }
236
237 imap4::response
238 imap4::_response()
239 {
240 parser parse(_read());
241 response resp;
242 resp.tag = parse.token();
243 if (resp.tag == "+") { // continuation
244 resp.data = parse.remain();
245 return resp;
246 }
247 resp.type = parse.token();
248 if (resp.tag.empty() || resp.type.empty()) {
249 throw mailbox::error("unexpected response: " + parse.data());
250 }
251 if (parse && parser::digit(resp.type)) {
252 resp.data = resp.type, resp.type = parse.token();
253 }
254 if (parse) {
255 if (!resp.data.empty()) resp.data += ' ';
256 resp.data += parse.remain();
257 }
258 return resp;
259 }
260
261 string
262 imap4::_read()
263 {
264 string line = read();
265 if (!line.empty() && line[0] != '+') {
266 while (line[line.size() - 1] == '}') {
267 string::size_type i = line.find_last_of('{');
268 if (i == string::npos) break;
269 const char* p = line.c_str();
270 char* end;
271 size_t size = strtoul(p + i + 1, &end, 10);
272 if (string::size_type(end - p) != line.size() - 1) break;
273 if (size) { // read literal data.
274 string literal = read(size);
275 LOG(literal);
276 line += literal;
277 }
278 line += read(); // read a following line.
279 }
280 }
281 return line;
282 }
283
284 /*
285 * Functions of the class imap4::parser
286 */
287 string
288 imap4::parser::token(bool open)
289 {
290 string result;
291 if (*this) {
292 char delim[] = " [(\"{";
293 string::size_type i = findf(delim);
294 result = uppercase(i);
295 if (i == string::npos) {
296 _next = _s.size();
297 } else if (_s[i] == ' ') {
298 _next = i + 1;
299 } else if (_s[i] == '[' || result.empty()) {
300 result += _s[i];
301 for (string st(1, _s[i++]); !st.empty();) {
302 _next = i;
303 if (_next >= _s.size()) {
304 throw mailbox::error("invalid token: " + _s);
305 }
306 string::size_type sp = st.size() - 1;
307 switch (st[sp]) {
308 case '"':
309 i = findq("\"", i);
310 if (i != string::npos) {
311 result.append(_s, _next, ++i - _next);
312 st.erase(sp);
313 }
314 break;
315 case '{':
316 {
317 const char* p = _s.c_str();
318 char* end;
319 i = strtoul(p + i, &end, 10);
320 i += string::size_type(end - p) + 1;
321 if (*end == '}' && i <= _s.size()) {
322 result.append(_s, _next, i - _next);
323 st.erase(sp);
324 } else {
325 i = string::npos;
326 }
327 }
328 break;
329 default:
330 delim[0] = st[sp] == '[' ? ']' : ')';
331 i = findf(delim, i);
332 if (i != string::npos) {
333 if (_s[i] == delim[0]) st.erase(sp);
334 else st.push_back(_s[i]);
335 result += uppercase(++i);
336 }
337 break;
338 }
339 }
340 _next = i < _s.size() && _s[i] == ' ' ? i + 1 : i;
341 if (result.size() > 1) {
342 switch (result[0]) {
343 default:
344 if (!open || (result[0] != '(' && result[0] != '[')) break;
345 case '"':
346 result.assign(result, 1, result.size() - 2);
347 break;
348 case '{':
349 assert(result.find_first_of('}') != string::npos);
350 result.erase(0, result.find_first_of('}') + 1);
351 break;
352 }
353 }
354 }
355 }
356 return result;
357 }
358
359 mailbox::backend* backendIMAP4() { return new imap4; }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26