1 //
2 // VMime library (http://www.vmime.org)
3 // Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 3 of
8 // the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // Linking this library statically or dynamically with other modules is making
20 // a combined work based on this library.  Thus, the terms and conditions of
21 // the GNU General Public License cover the whole combination.
22 //
23 
24 #include "tests/testUtils.hpp"
25 
26 #include "vmime/platform.hpp"
27 
28 #include "vmime/net/maildir/maildirStore.hpp"
29 #include "vmime/net/maildir/maildirFormat.hpp"
30 
31 
32 // Shortcuts and helpers
33 typedef vmime::utility::file::path fspath;
34 typedef vmime::utility::file::path::component fspathc;
35 
36 typedef vmime::net::folder::path fpath;
37 typedef vmime::net::folder::path::component fpathc;
38 
39 
operator /(const fpath & path,const std::string & c)40 const fpath operator/(const fpath& path, const std::string& c)
41 {
42 	return path / fpathc(c);
43 }
44 
45 
46 /** Test messages */
47 static const vmime::string TEST_MESSAGE_1 =
48 	"From: <test@vmime.org>\r\n"
49 	"Subject: VMime Test\r\n"
50 	"Date: Thu, 01 Mar 2007 09:49:35 +0100\r\n"
51 	"\r\n"
52 	"Hello, world!";
53 
54 
55 /** Maildir trees used in tests.
56   * Structure:
57   *
58   *  .
59   *  |-- Folder
60   *  |   `-- SubFolder
61   *  |       |-- SubSubFolder1
62   *  |       `-- SubSubFolder2
63   *  `-- Folder2
64   *
65   */
66 
67 // KMail format
68 static const vmime::string TEST_MAILDIR_KMAIL[] =  // directories to create
69 {
70 	"/Folder",
71 	"/Folder/new",
72 	"/Folder/tmp",
73 	"/Folder/cur",
74 	"/.Folder.directory",
75 	"/.Folder.directory/SubFolder",
76 	"/.Folder.directory/SubFolder/new",
77 	"/.Folder.directory/SubFolder/tmp",
78 	"/.Folder.directory/SubFolder/cur",
79 	"/.Folder.directory/.SubFolder.directory",
80 	"/.Folder.directory/.SubFolder.directory/SubSubFolder1",
81 	"/.Folder.directory/.SubFolder.directory/SubSubFolder1/new",
82 	"/.Folder.directory/.SubFolder.directory/SubSubFolder1/tmp",
83 	"/.Folder.directory/.SubFolder.directory/SubSubFolder1/cur",
84 	"/.Folder.directory/.SubFolder.directory/SubSubFolder2",
85 	"/.Folder.directory/.SubFolder.directory/SubSubFolder2/new",
86 	"/.Folder.directory/.SubFolder.directory/SubSubFolder2/tmp",
87 	"/.Folder.directory/.SubFolder.directory/SubSubFolder2/cur",
88 	"/Folder2",
89 	"/Folder2/new",
90 	"/Folder2/tmp",
91 	"/Folder2/cur",
92 	"*"  // end
93 };
94 
95 static const vmime::string TEST_MAILDIRFILES_KMAIL[] =  // files to create and their contents
96 {
97 	"/.Folder.directory/.SubFolder.directory/SubSubFolder2/cur/1043236113.351.EmqD:S", TEST_MESSAGE_1,
98 	"*"  // end
99 };
100 
101 // Courier format
102 static const vmime::string TEST_MAILDIR_COURIER[] =  // directories to create
103 {
104 	"/.Folder",
105 	"/.Folder/new",
106 	"/.Folder/tmp",
107 	"/.Folder/cur",
108 	"/.Folder.SubFolder",
109 	"/.Folder.SubFolder",
110 	"/.Folder.SubFolder/new",
111 	"/.Folder.SubFolder/tmp",
112 	"/.Folder.SubFolder/cur",
113 	"/.Folder.SubFolder.SubSubFolder1",
114 	"/.Folder.SubFolder.SubSubFolder1/new",
115 	"/.Folder.SubFolder.SubSubFolder1/tmp",
116 	"/.Folder.SubFolder.SubSubFolder1/cur",
117 	"/.Folder.SubFolder.SubSubFolder2",
118 	"/.Folder.SubFolder.SubSubFolder2/new",
119 	"/.Folder.SubFolder.SubSubFolder2/tmp",
120 	"/.Folder.SubFolder.SubSubFolder2/cur",
121 	"/.Folder2",
122 	"/.Folder2/new",
123 	"/.Folder2/tmp",
124 	"/.Folder2/cur",
125 	"*"  // end
126 };
127 
128 static const vmime::string TEST_MAILDIRFILES_COURIER[] =  // files to create and their contents
129 {
130 	"/.Folder/maildirfolder", "",
131 	"/.Folder.SubFolder/maildirfolder", "",
132 	"/.Folder.SubFolder.SubSubFolder1/maildirfolder", "",
133 	"/.Folder.SubFolder.SubSubFolder2/maildirfolder", "",
134 	"/.Folder.SubFolder.SubSubFolder2/cur/1043236113.351.EmqD:S", TEST_MESSAGE_1,
135 	"/.Folder2/maildirfolder", "",
136 	"*"  // end
137 };
138 
139 
140 
141 VMIME_TEST_SUITE_BEGIN(maildirStoreTest)
142 
143 	VMIME_TEST_LIST_BEGIN
144 		VMIME_TEST(testDetectFormat_KMail)
145 		VMIME_TEST(testDetectFormat_Courier)
146 
147 		VMIME_TEST(testListRootFolders_KMail)
148 		VMIME_TEST(testListAllFolders_KMail)
149 
150 		VMIME_TEST(testListRootFolders_Courier)
151 		VMIME_TEST(testListAllFolders_Courier)
152 
153 		VMIME_TEST(testListMessages_KMail)
154 		VMIME_TEST(testListMessages_Courier)
155 
156 		VMIME_TEST(testRenameFolder_KMail)
157 		VMIME_TEST(testRenameFolder_Courier)
158 
159 		VMIME_TEST(testDestroyFolder_KMail)
160 		VMIME_TEST(testDestroyFolder_Courier)
161 
162 		VMIME_TEST(testFolderExists_KMail)
163 		VMIME_TEST(testFolderExists_Courier)
164 
165 		VMIME_TEST(testCreateFolder_KMail)
166 		VMIME_TEST(testCreateFolder_Courier)
167 	VMIME_TEST_LIST_END
168 
169 
170 public:
171 
172 	maildirStoreTest()
173 	{
174 		// Temporary directory
175 		m_tempPath = fspath() / fspathc("tmp")   // Use /tmp
176 			/ fspathc("vmime" + vmime::utility::stringUtils::toString(std::time(NULL))
177 				+ vmime::utility::stringUtils::toString(std::rand()));
178 	}
179 
180 	void tearDown()
181 	{
182 		// In case of an uncaught exception
183 		destroyMaildir();
184 	}
185 
186 	void testDetectFormat_KMail()
187 	{
188 		createMaildir(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
189 
190 		vmime::shared_ptr <vmime::net::maildir::maildirStore> store =
191 			vmime::dynamicCast <vmime::net::maildir::maildirStore>(createAndConnectStore());
192 
193 		VASSERT_EQ("*", "kmail", store->getFormat()->getName());
194 
195 		destroyMaildir();
196 	}
197 
198 	void testDetectFormat_Courier()
199 	{
200 		createMaildir(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
201 
202 		vmime::shared_ptr <vmime::net::maildir::maildirStore> store =
203 			vmime::dynamicCast <vmime::net::maildir::maildirStore>(createAndConnectStore());
204 
205 		VASSERT_EQ("*", "courier", store->getFormat()->getName());
206 
207 		destroyMaildir();
208 	}
209 
210 
211 	void testListRootFolders_KMail()
212 	{
213 		testListRootFoldersImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
214 	}
215 
216 	void testListRootFolders_Courier()
217 	{
218 		testListRootFoldersImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
219 	}
220 
221 	void testListRootFoldersImpl(const vmime::string* const dirs, const vmime::string* const files)
222 	{
223 		createMaildir(dirs, files);
224 
225 		// Connect to store
226 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
227 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
228 
229 		// Get root folders, not recursive
230 		const std::vector <vmime::shared_ptr <vmime::net::folder> >
231 			rootFolders = rootFolder->getFolders(false);
232 
233 		VASSERT_EQ("1", 2, rootFolders.size());
234 		VASSERT("2", findFolder(rootFolders, fpath() / "Folder") != NULL);
235 		VASSERT("3", findFolder(rootFolders, fpath() / "Folder2") != NULL);
236 
237 		destroyMaildir();
238 	}
239 
240 
241 	void testListAllFolders_KMail()
242 	{
243 		testListAllFoldersImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
244 	}
245 
246 	void testListAllFolders_Courier()
247 	{
248 		testListAllFoldersImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
249 	}
250 
251 	void testListAllFoldersImpl(const vmime::string* const dirs, const vmime::string* const files)
252 	{
253 		createMaildir(dirs, files);
254 
255 		// Connect to store
256 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
257 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
258 
259 		// Get all folders, recursive
260 		const std::vector <vmime::shared_ptr <vmime::net::folder> >
261 			allFolders = rootFolder->getFolders(true);
262 
263 		VASSERT_EQ("1", 5, allFolders.size());
264 		VASSERT("2", findFolder(allFolders, fpath() / "Folder") != NULL);
265 		VASSERT("3", findFolder(allFolders, fpath() / "Folder" / "SubFolder") != NULL);
266 		VASSERT("4", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder1") != NULL);
267 		VASSERT("5", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder2") != NULL);
268 		VASSERT("6", findFolder(allFolders, fpath() / "Folder2") != NULL);
269 
270 		destroyMaildir();
271 	}
272 
273 
274 	void testListMessages_KMail()
275 	{
276 		testListMessagesImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
277 	}
278 
279 	void testListMessages_Courier()
280 	{
281 		testListMessagesImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
282 	}
283 
284 	void testListMessagesImpl(const vmime::string* const dirs, const vmime::string* const files)
285 	{
286 		createMaildir(dirs, files);
287 
288 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
289 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
290 
291 		vmime::shared_ptr <vmime::net::folder> folder = store->getFolder
292 			(fpath() / "Folder" / "SubFolder" / "SubSubFolder2");
293 
294 		vmime::size_t count, unseen;
295 		folder->status(count, unseen);
296 
297 		VASSERT_EQ("Message count", 1, count);
298 
299 		folder->open(vmime::net::folder::MODE_READ_ONLY);
300 
301 		vmime::shared_ptr <vmime::net::message> msg = folder->getMessage(1);
302 
303 		folder->fetchMessage(msg, vmime::net::fetchAttributes::SIZE);
304 
305 		VASSERT_EQ("Message size", TEST_MESSAGE_1.length(), msg->getSize());
306 
307 		std::ostringstream oss;
308 		vmime::utility::outputStreamAdapter os(oss);
309 		msg->extract(os);
310 
311 		VASSERT_EQ("Message contents", TEST_MESSAGE_1, oss.str());
312 
313 		folder->close(false);
314 
315 		destroyMaildir();
316 	}
317 
318 
319 	void testRenameFolder_KMail()
320 	{
321 		try
322 		{
323 			testRenameFolderImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
324 		}
325 		catch (vmime::exception& e)
326 		{
327 			std::cerr << e;
328 			throw e;
329 		}
330 	}
331 
332 	void testRenameFolder_Courier()
333 	{
334 		try
335 		{
336 			testRenameFolderImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
337 		}
338 		catch (vmime::exception& e)
339 		{
340 			std::cerr << e;
341 			throw e;
342 		}
343 	}
344 
345 	void testRenameFolderImpl(const vmime::string* const dirs, const vmime::string* const files)
346 	{
347 		createMaildir(dirs, files);
348 
349 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
350 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
351 
352 		// Rename "Folder/SubFolder" to "Folder/foo"
353 		vmime::shared_ptr <vmime::net::folder> folder = store->getFolder
354 			(fpath() / "Folder" / "SubFolder");
355 
356 		folder->rename(fpath() / "Folder" / "foo");
357 
358 		// Ensure folder and its subfolders have been renamed
359 		const std::vector <vmime::shared_ptr <vmime::net::folder> >
360 			allFolders = rootFolder->getFolders(true);
361 
362 		VASSERT_EQ("1", 5, allFolders.size());
363 		VASSERT("2", findFolder(allFolders, fpath() / "Folder") != NULL);
364 		VASSERT("3", findFolder(allFolders, fpath() / "Folder" / "SubFolder") == NULL);
365 		VASSERT("4", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder1") == NULL);
366 		VASSERT("5", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder2") == NULL);
367 		VASSERT("6", findFolder(allFolders, fpath() / "Folder2") != NULL);
368 		VASSERT("7", findFolder(allFolders, fpath() / "Folder" / "foo") != NULL);
369 		VASSERT("8", findFolder(allFolders, fpath() / "Folder" / "foo" / "SubSubFolder1") != NULL);
370 		VASSERT("9", findFolder(allFolders, fpath() / "Folder" / "foo" / "SubSubFolder2") != NULL);
371 
372 		destroyMaildir();
373 	}
374 
375 
376 	void testDestroyFolder_KMail()
377 	{
378 		testDestroyFolderImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
379 	}
380 
381 	void testDestroyFolder_Courier()
382 	{
383 		testDestroyFolderImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
384 	}
385 
386 	void testDestroyFolderImpl(const vmime::string* const dirs, const vmime::string* const files)
387 	{
388 		createMaildir(dirs, files);
389 
390 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
391 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
392 
393 		// Destroy "Folder/SubFolder" (total: 3 folders)
394 		vmime::shared_ptr <vmime::net::folder> folder = store->getFolder
395 			(fpath() / "Folder" / "SubFolder");
396 
397 		folder->destroy();
398 
399 		// Ensure folder and its subfolders have been deleted and other folders still exist
400 		const std::vector <vmime::shared_ptr <vmime::net::folder> >
401 			allFolders = rootFolder->getFolders(true);
402 
403 		VASSERT_EQ("1", 2, allFolders.size());
404 		VASSERT("2", findFolder(allFolders, fpath() / "Folder") != NULL);
405 		VASSERT("3", findFolder(allFolders, fpath() / "Folder" / "SubFolder") == NULL);
406 		VASSERT("4", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder1") == NULL);
407 		VASSERT("5", findFolder(allFolders, fpath() / "Folder" / "SubFolder" / "SubSubFolder2") == NULL);
408 		VASSERT("6", findFolder(allFolders, fpath() / "Folder2") != NULL);
409 
410 		destroyMaildir();
411 	}
412 
413 
414 	void testFolderExists_KMail()
415 	{
416 		testFolderExistsImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
417 	}
418 
419 	void testFolderExists_Courier()
420 	{
421 		testFolderExistsImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
422 	}
423 
424 	void testFolderExistsImpl(const vmime::string* const dirs, const vmime::string* const files)
425 	{
426 		createMaildir(dirs, files);
427 
428 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
429 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
430 
431 		VASSERT("1",  store->getFolder(fpath() / "Folder" / "SubFolder")->exists());
432 		VASSERT("2", !store->getFolder(fpath() / "Folder" / "SubSubFolder1")->exists());
433 		VASSERT("3",  store->getFolder(fpath() / "Folder2")->exists());
434 		VASSERT("4",  store->getFolder(fpath() / "Folder" / "SubFolder" / "SubSubFolder2")->exists());
435 
436 		destroyMaildir();
437 	}
438 
439 
440 	void testCreateFolder_KMail()
441 	{
442 		testCreateFolderImpl(TEST_MAILDIR_KMAIL, TEST_MAILDIRFILES_KMAIL);
443 	}
444 
445 	void testCreateFolder_Courier()
446 	{
447 		testCreateFolderImpl(TEST_MAILDIR_COURIER, TEST_MAILDIRFILES_COURIER);
448 	}
449 
450 	void testCreateFolderImpl(const vmime::string* const dirs, const vmime::string* const files)
451 	{
452 		createMaildir(dirs, files);
453 
454 		vmime::shared_ptr <vmime::net::store> store = createAndConnectStore();
455 		vmime::shared_ptr <vmime::net::folder> rootFolder = store->getRootFolder();
456 
457 		VASSERT("Before", !store->getFolder(fpath() / "Folder" / "NewFolder")->exists());
458 
459 		vmime::net::folderAttributes attribs;
460 		attribs.setType(vmime::net::folderAttributes::TYPE_CONTAINS_MESSAGES);
461 
462 		VASSERT_NO_THROW("Creation", store->getFolder(fpath() / "Folder" / "NewFolder")->create(attribs));
463 
464 		VASSERT("After", store->getFolder(fpath() / "Folder" / "NewFolder")->exists());
465 
466 		destroyMaildir();
467 	}
468 
469 private:
470 
471 	vmime::utility::file::path m_tempPath;
472 
473 
474 	vmime::shared_ptr <vmime::net::store> createAndConnectStore()
475 	{
476 		vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create();
477 
478 		vmime::shared_ptr <vmime::net::store> store =
479 			session->getStore(getStoreURL());
480 
481 		store->connect();
482 
483 		return store;
484 	}
485 
486 	const vmime::shared_ptr <vmime::net::folder> findFolder
487 		(const std::vector <vmime::shared_ptr <vmime::net::folder> >& folders,
488 		 const vmime::net::folder::path& path)
489 	{
490 		for (size_t i = 0, n = folders.size() ; i < n ; ++i)
491 		{
492 			if (folders[i]->getFullPath() == path)
493 				return folders[i];
494 		}
495 
496 		return vmime::null;
497 	}
498 
499 	const vmime::utility::url getStoreURL()
500 	{
501 		vmime::shared_ptr <vmime::utility::fileSystemFactory> fsf =
502 			vmime::platform::getHandler()->getFileSystemFactory();
503 
504 		vmime::utility::url url(std::string("maildir://localhost")
505 			+ fsf->pathToString(m_tempPath));
506 
507 		return url;
508 	}
509 
510 	void createMaildir(const vmime::string* const dirs, const vmime::string* const files)
511 	{
512 		vmime::shared_ptr <vmime::utility::fileSystemFactory> fsf =
513 			vmime::platform::getHandler()->getFileSystemFactory();
514 
515 		vmime::shared_ptr <vmime::utility::file> rootDir = fsf->create(m_tempPath);
516 		rootDir->createDirectory(false);
517 
518 		for (vmime::string const* dir = dirs ; *dir != "*" ; ++dir)
519 		{
520 			vmime::shared_ptr <vmime::utility::file> fdir = fsf->create(m_tempPath / fsf->stringToPath(*dir));
521 			fdir->createDirectory(false);
522 		}
523 
524 		for (vmime::string const* file = files ; *file != "*" ; file += 2)
525 		{
526 			const vmime::string& contents = *(file + 1);
527 
528 			vmime::shared_ptr <vmime::utility::file> ffile = fsf->create(m_tempPath / fsf->stringToPath(*file));
529 			ffile->createFile();
530 
531 			vmime::shared_ptr <vmime::utility::fileWriter> fileWriter = ffile->getFileWriter();
532 			vmime::shared_ptr <vmime::utility::outputStream> os = fileWriter->getOutputStream();
533 
534 			os->write(contents.data(), contents.length());
535 			os->flush();
536 
537 			fileWriter = vmime::null;
538 		}
539 
540 	}
541 
542 	void destroyMaildir()
543 	{
544 		vmime::shared_ptr <vmime::utility::fileSystemFactory> fsf =
545 			vmime::platform::getHandler()->getFileSystemFactory();
546 
547 		recursiveDelete(fsf->create(m_tempPath));
548 	}
549 
550 	void recursiveDelete(vmime::shared_ptr <vmime::utility::file> dir)
551 	{
552 		if (!dir->exists() || !dir->isDirectory())
553 			return;
554 
555 		vmime::shared_ptr <vmime::utility::fileIterator> files = dir->getFiles();
556 
557 		// First, delete files and subdirectories in this directory
558 		while (files->hasMoreElements())
559 		{
560 			vmime::shared_ptr <vmime::utility::file> file = files->nextElement();
561 
562 			if (file->isDirectory())
563 			{
564 				recursiveDelete(file);
565 			}
566 			else
567 			{
568 				try
569 				{
570 					file->remove();
571 				}
572 				catch (vmime::exceptions::filesystem_exception&)
573 				{
574 					// Ignore
575 				}
576 			}
577 		}
578 
579 		// Then, delete this (empty) directory
580 		try
581 		{
582 			dir->remove();
583 		}
584 		catch (vmime::exceptions::filesystem_exception&)
585 		{
586 			// Ignore
587 		}
588 	}
589 
590 VMIME_TEST_SUITE_END
591 
592