/Source/MBoxItemsController.m

http://google-email-uploader-mac.googlecode.com/ · Objective C · 787 lines · 504 code · 171 blank · 112 comment · 111 complexity · 89e0bb85354f1a59017784d669c7b0b8 MD5 · raw file

  1. /* Copyright (c) 2009 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #import "MBoxItemsController.h"
  16. #import "EmUpUtilities.h"
  17. static const char* memsearch(const char* needle, unsigned long long needleLen,
  18. const char* haystack, unsigned long long haystackLen);
  19. @interface MBoxItemsController (PrivateMethods)
  20. - (void)loadMailItems;
  21. - (NSString *)endOfLineForFileData:(NSData *)fileData;
  22. - (NSString *)fromStringForFileData:(NSData *)fileData;
  23. - (void)setLastUploadedItem:(OutlineViewItemMBox *)item;
  24. - (NSString *)dateStringForMessageFirstLine:(NSString *)firstLine;
  25. - (NSArray *)mailItemPropertiesForHeaders:(NSString *)message
  26. endOfLine:(NSString *)endOfLine;
  27. @end
  28. @implementation OutlineViewItemMBox
  29. - (void)dealloc {
  30. [messageOffsets_ release];
  31. [endOfLine_ release];
  32. [super dealloc];
  33. }
  34. - (void)setMessageOffsets:(NSArray *)array {
  35. [messageOffsets_ autorelease];
  36. messageOffsets_ = [array retain];
  37. }
  38. - (NSArray *)messageOffsets {
  39. return messageOffsets_;
  40. }
  41. - (void)setEndOfLine:(NSString *)str {
  42. [endOfLine_ autorelease];
  43. endOfLine_ = [str copy];
  44. }
  45. - (NSString *)endOfLine {
  46. return endOfLine_;
  47. }
  48. @end
  49. @implementation MBoxItemsController
  50. - (id)initWithMailFolderPath:(NSString *)path
  51. rootName:(NSString *)rootName
  52. {
  53. self = [super init];
  54. if (self) {
  55. mailFolderPath_ = [path copy];
  56. rootName_ = [rootName copy];
  57. [self loadMailItems];
  58. }
  59. return self;
  60. }
  61. - (void)dealloc {
  62. [mailFolderPath_ release];
  63. [rootName_ release];
  64. [rootItem_ release];
  65. [lastUploadedItem_ release];
  66. [uploadingData_ release];
  67. [super dealloc];
  68. }
  69. #pragma mark -
  70. - (NSString *)defaultMailFolderPath {
  71. [self doesNotRecognizeSelector:_cmd];
  72. return nil;
  73. }
  74. - (NSString *)defaultRootName {
  75. [self doesNotRecognizeSelector:_cmd];
  76. return nil;
  77. }
  78. - (const char *)messagePrefix {
  79. [self doesNotRecognizeSelector:_cmd];
  80. return nil;
  81. }
  82. - (OutlineViewItem *)rootItem {
  83. return rootItem_;
  84. }
  85. - (NSString *)mailFolderPath {
  86. return mailFolderPath_;
  87. }
  88. - (NSString *)byteStringReportForAddress:(const unsigned char *)ptr
  89. length:(unsigned long long)length {
  90. NSMutableString *resultStr = [NSMutableString string];
  91. const int charsPerLine = 16;
  92. BOOL isDone = NO;
  93. for (int lineNum = 0; lineNum < 20; lineNum++) {
  94. int lineOffset = lineNum * charsPerLine;
  95. NSMutableString *ascii = [NSMutableString string];
  96. NSMutableString *hex = [NSMutableString string];
  97. for (int charNum = 0; charNum < charsPerLine; charNum++) {
  98. if (lineOffset + charNum >= length) {
  99. isDone = YES;
  100. }
  101. if (!isDone) {
  102. // not done; create the ascii and hex parts
  103. if (*ptr >= 0x20 && *ptr <= 0x7f) {
  104. [ascii appendFormat:@"%c", *ptr];
  105. } else {
  106. [ascii appendString:@"."];
  107. }
  108. [hex appendFormat:@"%02X", *ptr];
  109. if (charNum % 2 == 1) [hex appendString:@" "];
  110. ++ptr;
  111. } else {
  112. // keep padding the ascii part
  113. [ascii appendString:@" "];
  114. }
  115. }
  116. [resultStr appendFormat:@"%04X: %@ %@\n", lineOffset, ascii, hex];
  117. if (isDone) break;
  118. }
  119. return resultStr;
  120. }
  121. - (void)loadMailItems {
  122. // build a dictionary for all mailboxes
  123. NSFileManager *fileMgr = [NSFileManager defaultManager];
  124. BOOL isDir = NO;
  125. if (![fileMgr fileExistsAtPath:mailFolderPath_ isDirectory:&isDir] || !isDir) {
  126. // no MBox mail folder found; leave the root item ivar nil
  127. return;
  128. }
  129. NSDirectoryEnumerator *enumerator = [fileMgr enumeratorAtPath:mailFolderPath_];
  130. rootItem_ = [[OutlineViewItemMBox itemWithName:rootName_
  131. level:0] retain];
  132. NSString *partialPath;
  133. OutlineViewItemMBox *lastOutlineItem = rootItem_;
  134. NSDate *lastDisplayDate = [NSDate date];
  135. // all messages begin with "\nFrom " or "\rFrom ", except the first
  136. // message in the file
  137. const char *kFrom = "From ";
  138. const int kFromLen = strlen(kFrom);
  139. // make a map of partial paths to created outline items
  140. NSMutableDictionary *itemMap = [NSMutableDictionary dictionaryWithObject:rootItem_
  141. forKey:@""];
  142. while ((partialPath = [enumerator nextObject]) != nil) {
  143. NSDictionary *pathAttrs = [enumerator fileAttributes];
  144. NSString *fullPath = [mailFolderPath_ stringByAppendingPathComponent:partialPath];
  145. NSString *partialPathParent = [partialPath stringByDeletingLastPathComponent];
  146. NSString *pathExtension = [partialPath pathExtension];
  147. NSString *lastPathComponent = [partialPath lastPathComponent];
  148. // display the path periodically when stepping through mailboxes so the user
  149. // knows we're making progress
  150. if ([lastDisplayDate timeIntervalSinceNow] < -0.2) {
  151. lastDisplayDate = [NSDate date];
  152. [[NSNotificationCenter defaultCenter] postNotificationName:kEmUpLoadingMailbox
  153. object:fullPath];
  154. }
  155. BOOL isDir = [[pathAttrs objectForKey:NSFileType] isEqual:NSFileTypeDirectory];
  156. // skip invisible files and dirs
  157. if ([lastPathComponent hasPrefix:@"."]) {
  158. if (isDir) [enumerator skipDescendents];
  159. continue;
  160. }
  161. NSArray *partialPathParts = [partialPath componentsSeparatedByString:@"/"];
  162. if (isDir) {
  163. // add this directory path to the tree
  164. OutlineViewItemMBox *dirItem = [itemMap objectForKey:partialPath];
  165. if (dirItem == nil) {
  166. // new directory; create the outline item, store it in the map,
  167. // and save it as a child of its parent
  168. int level = [partialPathParts count];
  169. dirItem = [OutlineViewItemMBox itemWithName:lastPathComponent
  170. level:level];
  171. [itemMap setObject:dirItem forKey:partialPath];
  172. OutlineViewItemMBox *parentItem = [itemMap objectForKey:partialPathParent];
  173. NSAssert1(parentItem != nil, @"missing parent for %@", partialPath);
  174. [parentItem addChild:dirItem];
  175. } else {
  176. // we've seen this directory before
  177. }
  178. } else if (![pathExtension isEqual:@"toc"] // Eudora metadata
  179. && ![pathExtension isEqual:@"msf"]) { // Tbird metadata
  180. // we'll try to count the messages in files without blowing chunks on
  181. // gigabyte-size mbox files
  182. // make a no-copy NSString from a memory-mapped NSData
  183. //
  184. // hopefully, this will let us access huge files without killing
  185. // the machine
  186. // we'll explicitly release fileData below to free up the memory-mapped
  187. // file
  188. NSData *fileData = [[NSData alloc] initWithContentsOfMappedFile:fullPath];
  189. unsigned int dataLen = [fileData length];
  190. const char *fileDataPtr = [fileData bytes];
  191. // before attempting to use the file, check its first bytes are "From"
  192. if (dataLen > kFromLen
  193. && strncmp(fileDataPtr, kFrom, kFromLen) == 0) {
  194. // figure out the line endings for this mbox file
  195. NSString *endOfLine = [self endOfLineForFileData:fileData];
  196. if (endOfLine == nil) {
  197. NSLog(@"could not determine eol for file: %@", fullPath);
  198. endOfLine = @"\n";
  199. }
  200. unsigned int eolLen = [endOfLine length];
  201. // get the "From " string we'll use as a message separator for this file
  202. NSString *fromString = [self fromStringForFileData:fileData];
  203. const char *fromAfterReturn = [[NSString stringWithFormat:@"%@%@",
  204. endOfLine, fromString] UTF8String];
  205. size_t fromAfterReturnLen = strlen(fromAfterReturn);
  206. // locate the message separators; the first one is at byte zero
  207. unsigned long long offset = 0;
  208. NSNumber *offset0 = [NSNumber numberWithUnsignedLongLong:0];
  209. NSMutableArray *offsetsArray = [NSMutableArray arrayWithObject:offset0];
  210. while (1) {
  211. // display the path periodically when stepping through messages of a
  212. // mailbox so we look busy
  213. if ([lastDisplayDate timeIntervalSinceNow] < -0.2) {
  214. lastDisplayDate = [NSDate date];
  215. [[NSNotificationCenter defaultCenter] postNotificationName:kEmUpLoadingMailbox
  216. object:fullPath];
  217. }
  218. // we use our own memsearch rather than strnstr in case messages
  219. // contain nulls
  220. const char *foundPtr = memsearch(fromAfterReturn, fromAfterReturnLen,
  221. fileDataPtr + offset, dataLen - offset);
  222. if (!foundPtr) break;
  223. // skip the initial return or newline char when calculating the
  224. // offset of each message
  225. offset = foundPtr + eolLen - fileDataPtr;
  226. // add the offset of this message to our array
  227. [offsetsArray addObject:[NSNumber numberWithUnsignedLongLong:offset]];
  228. // search again from after the last found "\rFrom" prefix
  229. offset += fromAfterReturnLen;
  230. }
  231. // add this mailbox to the outline view
  232. unsigned int level = [partialPathParts count];
  233. OutlineViewItemMBox *childItem
  234. = [OutlineViewItemMBox itemWithName:lastPathComponent
  235. level:level];
  236. [childItem setNumberOfMessages:[offsetsArray count]];
  237. [childItem setMessageOffsets:offsetsArray];
  238. [childItem setPath:fullPath];
  239. [childItem setEndOfLine:endOfLine];
  240. // add this new item as a child of the most recently-visited folder
  241. OutlineViewItemMBox *parentItem = [itemMap objectForKey:partialPathParent];
  242. NSAssert1(parentItem != nil, @"missing parent for %@", partialPath);
  243. [parentItem addChild:childItem];
  244. // add this to the linked list of outline items to be uploaded
  245. [lastOutlineItem setNextOutlineItem:childItem];
  246. lastOutlineItem = childItem;
  247. [itemMap setObject:childItem forKey:partialPath];
  248. }
  249. [fileData release];
  250. fileData = nil;
  251. }
  252. }
  253. [rootItem_ setState:NSOnState];
  254. // clear the name of the mailbox being loaded
  255. [[NSNotificationCenter defaultCenter] postNotificationName:kEmUpLoadingMailbox
  256. object:@""];
  257. }
  258. - (NSString *)endOfLineForFileData:(NSData *)fileData {
  259. // since an mbox file should start with a header, scan until we find
  260. // a \r, \n, or \r\n, and assume that end-of-line marker applies for the
  261. // whole file
  262. //
  263. // as a sanity check, limit our scan to 1000 chars
  264. unsigned int numberOfBytesToTest = MIN([fileData length], 1000);
  265. char *fileBytes = (char *) [fileData bytes];
  266. for (unsigned int idx = 0; idx < numberOfBytesToTest; idx++) {
  267. char c = fileBytes[idx];
  268. if (c == '\n') {
  269. return @"\n";
  270. }
  271. if (c == '\r') {
  272. // check if the next character after this return is a newline
  273. if (idx + 1 < numberOfBytesToTest
  274. && fileBytes[idx+1] == '\n') {
  275. return @"\r\n";
  276. } else {
  277. return @"\r";
  278. }
  279. }
  280. }
  281. return nil;
  282. }
  283. - (NSString *)fromStringForFileData:(NSData *)fileData {
  284. // Eudora mbox files may not reliably change From strings at the beginning
  285. // of lines inside message bodies to >From, leading to mistakes in determining
  286. // where messages end. So if the first message in the file is From ???@???
  287. // we'll use that as the From string.
  288. const char *const kEudoraFromBytes = "From ???@???";
  289. int eudoraFromLen = strlen(kEudoraFromBytes);
  290. if ([fileData length] > eudoraFromLen) {
  291. if (memcmp(kEudoraFromBytes, [fileData bytes], eudoraFromLen) == 0) {
  292. // this file starts with the Eudora-style From
  293. return @"From ???@???";
  294. }
  295. }
  296. // for generic mbox, we'll just search for the traditional "From "
  297. return @"From ";
  298. }
  299. - (unsigned int)countSelectedMessages {
  300. unsigned int count = [rootItem_ recursiveNumberOfCheckedMessages];
  301. return count;
  302. }
  303. - (void)resetUpload {
  304. [self setLastUploadedItem:nil];
  305. }
  306. - (void)failUploadWithMessageIndex:(unsigned int)index
  307. path:(NSString *)path
  308. range:(NSRange)range
  309. errorString:(NSString *)errorStr {
  310. // report an upload failure to the user by notifying the main controller
  311. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
  312. path, kEmUpMessagePathKey,
  313. [NSNumber numberWithInt:index], kEmUpMessageIndexKey,
  314. [NSValue valueWithRange:range], kEmUpMessageRangeKey,
  315. errorStr, kEmUpMessageErrorStringKey,
  316. nil];
  317. [[NSNotificationCenter defaultCenter] postNotificationName:kEmUpMessageParsingFailed
  318. object:dict];
  319. }
  320. - (GDataEntryMailItem *)nextUploadItem {
  321. // find the next view item to upload
  322. if (lastUploadedItem_ == nil) {
  323. // starting from scratch
  324. [self setLastUploadedItem:[rootItem_ nextOutlineItem]];
  325. lastUploadedIndex_ = -1;
  326. }
  327. while (lastUploadedItem_ != nil) {
  328. ++lastUploadedIndex_;
  329. NSArray *messageOffsets = [lastUploadedItem_ messageOffsets];
  330. unsigned int numberOfMessages = [messageOffsets count];
  331. NSString *endOfLine = [lastUploadedItem_ endOfLine];
  332. const char* utf8EndOfLine = [endOfLine UTF8String];
  333. BOOL didExhaustFolder = (lastUploadedIndex_ >= numberOfMessages);
  334. if (didExhaustFolder) {
  335. // move to next upload item (the next mbox)
  336. [self setLastUploadedItem:[lastUploadedItem_ nextOutlineItem]];
  337. lastUploadedIndex_ = -1;
  338. // immediately free the memory from the last mbox file
  339. [uploadingData_ release];
  340. uploadingData_ = nil;
  341. } else {
  342. OutlineViewItemMBox *thisUploadItem = lastUploadedItem_;
  343. int thisUploadIndex = lastUploadedIndex_;
  344. NSCellStateValue state = [thisUploadItem state];
  345. if (state != NSOffState) {
  346. NSString *mboxPath = [thisUploadItem path];
  347. // if we've not yet opened this mbox file, do it now
  348. if (uploadingData_ == nil) {
  349. // read in the file memory-mapped in case it's huge
  350. uploadingData_ = [[NSData alloc] initWithContentsOfMappedFile:mboxPath];
  351. if (uploadingData_ == nil) {
  352. [self failUploadWithMessageIndex:thisUploadIndex
  353. path:mboxPath
  354. range:NSMakeRange(0, 0)
  355. errorString:@"Could not open MBox file"];
  356. continue;
  357. }
  358. }
  359. unsigned int dataLen = [uploadingData_ length];
  360. const char *fileDataPtr = [uploadingData_ bytes];
  361. NSNumber *thisMessageOffsetNum = [messageOffsets objectAtIndex:thisUploadIndex];
  362. unsigned long long thisMessageOffset = [thisMessageOffsetNum unsignedLongLongValue];
  363. unsigned long long nextMessageOffset;
  364. BOOL isLastMessage = (thisUploadIndex + 1 == numberOfMessages);
  365. if (isLastMessage) {
  366. nextMessageOffset = dataLen;
  367. } else {
  368. NSNumber *nextMessageOffsetNum = [messageOffsets objectAtIndex:(1 + thisUploadIndex)];
  369. nextMessageOffset = [nextMessageOffsetNum unsignedLongLongValue];
  370. }
  371. // skip the first line in the message, containing the false From ???@???
  372. // and date, and get a "real" pointer to the start of the actual message
  373. // text
  374. unsigned long long thisMessageLength = nextMessageOffset - thisMessageOffset;
  375. const char *thisMessagePtr = fileDataPtr + thisMessageOffset;
  376. char *realMessagePtr = strnstr(thisMessagePtr, utf8EndOfLine,
  377. thisMessageLength);
  378. NSRange messageRange = NSMakeRange(thisMessageOffset, thisMessageLength);
  379. if (realMessagePtr == NULL) {
  380. // something is really wrong if we can't find the expected kind of
  381. // end-of-line string
  382. NSString *report = [self byteStringReportForAddress:(const unsigned char *)thisMessagePtr
  383. length:thisMessageLength];
  384. NSLog(@"could not find first return in message (%d-%d) from file: %@\n%@",
  385. (int)thisMessageOffset, (int)nextMessageOffset-1, mboxPath,
  386. report);
  387. NSString *template = @"Could not find opening line (bytes %d-%d)";
  388. NSString *errMsg = [NSString stringWithFormat:template,
  389. (int)thisMessageOffset, (int)nextMessageOffset-1];
  390. [self failUploadWithMessageIndex:thisUploadIndex
  391. path:mboxPath
  392. range:messageRange
  393. errorString:errMsg];
  394. continue;
  395. }
  396. realMessagePtr += [endOfLine length]; // skip return char
  397. unsigned long long skippedLineLength = realMessagePtr - thisMessagePtr;
  398. unsigned long long realMessageLength = thisMessageLength - skippedLineLength;
  399. // read the message into a string
  400. //
  401. // Try UTF-8 first, since that's well-defined, unlike other encodings
  402. NSStringEncoding encoding = NSUTF8StringEncoding;
  403. NSString *messageText = [[[NSString alloc] initWithBytesNoCopy:realMessagePtr
  404. length:realMessageLength
  405. encoding:encoding
  406. freeWhenDone:NO] autorelease];
  407. if (messageText == nil) {
  408. // try MacRoman encoding
  409. encoding = NSMacOSRomanStringEncoding;
  410. messageText = [[[NSString alloc] initWithBytesNoCopy:realMessagePtr
  411. length:realMessageLength
  412. encoding:encoding
  413. freeWhenDone:NO] autorelease];
  414. }
  415. if (messageText == nil) {
  416. // try WinLatin encoding
  417. encoding = NSWindowsCP1252StringEncoding;
  418. messageText = [[[NSString alloc] initWithBytesNoCopy:realMessagePtr
  419. length:realMessageLength
  420. encoding:encoding
  421. freeWhenDone:NO] autorelease];
  422. }
  423. if (messageText == nil) {
  424. NSLog(@"could not read message (%d-%d) from file: %@",
  425. (int)thisMessageOffset, (int)nextMessageOffset-1, mboxPath);
  426. NSString *template = @"Could not interpret message (bytes %d-%d)";
  427. NSString *errMsg = [NSString stringWithFormat:template,
  428. (int)thisMessageOffset, (int)nextMessageOffset-1];
  429. [self failUploadWithMessageIndex:thisUploadIndex
  430. path:mboxPath
  431. range:messageRange
  432. errorString:errMsg];
  433. continue;
  434. }
  435. // uploads limited to 31 megs
  436. if ([messageText length] < kMaxMesssageSize) {
  437. // fix up message headers, if necessary
  438. messageText = [EmUpUtilities messageTextWithAlteredHeadersForMessageText:messageText
  439. endOfLine:endOfLine];
  440. NSString *headers = [EmUpUtilities headersForMessageText:messageText
  441. endOfLine:endOfLine];
  442. if (headers == nil) {
  443. NSLog(@"could not find headers in message (bytes %d-%d) from file: %@",
  444. (int)thisMessageOffset, (int)nextMessageOffset-1, mboxPath);
  445. }
  446. // some old eudora mail lacks a Date header; for those, we'll make
  447. // one from the initial From line. The From line represents the
  448. // time the message was written, so we'll use the local time
  449. // zone
  450. NSString *dateStr = [EmUpUtilities stringForHeader:@"Date"
  451. fromHeaders:headers
  452. endOfLine:endOfLine];
  453. if ([dateStr length] == 0) {
  454. NSString *firstLine =
  455. [[[NSString alloc] initWithBytesNoCopy:(void *)thisMessagePtr
  456. length:skippedLineLength
  457. encoding:encoding
  458. freeWhenDone:NO] autorelease];
  459. NSString *newDateStr = [self dateStringForMessageFirstLine:firstLine];
  460. if ([newDateStr length] > 0) {
  461. // insert a date header at the beginning of the message
  462. messageText = [NSString stringWithFormat:@"Date: %@%@%@",
  463. newDateStr, endOfLine, messageText];
  464. }
  465. }
  466. if (![endOfLine isEqual:@"\n"]) {
  467. // The server's header parsing code doesn't respect \r\r as ending a
  468. // header block, so it tends to find "invalid headers" in the body
  469. // of messages using returns as line separators. So we'll change
  470. // returns to newlines before encoding the message.
  471. NSMutableString *mutable = [NSMutableString stringWithString:messageText];
  472. [mutable replaceOccurrencesOfString:@"\r\n"
  473. withString:@"\n"
  474. options:0
  475. range:NSMakeRange(0, [mutable length])];
  476. [mutable replaceOccurrencesOfString:@"\r"
  477. withString:@"\n"
  478. options:0
  479. range:NSMakeRange(0, [mutable length])];
  480. endOfLine = @"\n";
  481. messageText = mutable;
  482. }
  483. GDataEntryMailItem *newEntry = [GDataEntryMailItem mailItemWithRFC822String:messageText];
  484. [newEntry setProperty:[thisUploadItem name]
  485. forKey:kEmUpMailboxNameKey];
  486. [newEntry setProperty:mboxPath
  487. forKey:kEmUpMessagePathKey];
  488. [newEntry setProperty:[NSNumber numberWithInt:thisUploadIndex]
  489. forKey:kEmUpMessageIndexKey];
  490. [newEntry setProperty:[NSValue valueWithRange:messageRange]
  491. forKey:kEmUpMessageRangeKey];
  492. NSString *messageID = [EmUpUtilities stringForHeader:@"Message-ID"
  493. fromHeaders:headers
  494. endOfLine:endOfLine];
  495. if (messageID) {
  496. [newEntry setProperty:messageID
  497. forKey:kEmUpMessageIDKey];
  498. }
  499. // add message properties
  500. NSArray *props = [self mailItemPropertiesForHeaders:headers
  501. endOfLine:endOfLine];
  502. for (NSString *property in props) {
  503. GDataMailItemProperty *prop = [GDataMailItemProperty valueWithString:property];
  504. [newEntry addMailItemProperty:prop];
  505. }
  506. return newEntry;
  507. } else {
  508. NSString *template = @"Message too big (bytes %d-%d)";
  509. NSString *errMsg = [NSString stringWithFormat:template,
  510. (int)thisMessageOffset, (int)nextMessageOffset-1];
  511. [self failUploadWithMessageIndex:thisUploadIndex
  512. path:mboxPath
  513. range:messageRange
  514. errorString:errMsg];
  515. }
  516. }
  517. }
  518. }
  519. return nil;
  520. }
  521. - (NSString *)dateStringForMessageFirstLine:(NSString *)firstLine {
  522. if (firstLine == nil) return nil;
  523. // look for the characteristic Eudora new-message From line, like
  524. //
  525. // From ???@??? Thu Feb 27 20:43:33 2003
  526. //
  527. // which unfortunately isn't in the date format needed for mail headers
  528. NSScanner *scanner = [NSScanner scannerWithString:firstLine];
  529. NSString *dateStr = nil;
  530. if ([scanner scanString:@"From ???@???" intoString:nil]
  531. && [scanner scanUpToString:@"\r" intoString:&dateStr]) {
  532. NSCalendarDate *parsedDate = [NSCalendarDate dateWithString:dateStr
  533. calendarFormat:@"%a %b %d %H:%M:%S %Y"];
  534. if (parsedDate) {
  535. // regenerate the date in the proper format, like
  536. // 3 Apr 1995 16:38:24 -0700
  537. // per http://www.w3.org/Protocols/rfc822/#z28
  538. NSString *newDateStr = [parsedDate descriptionWithCalendarFormat:@"%d %b %Y %H:%M:%S %z"];
  539. return newDateStr;
  540. }
  541. }
  542. return nil;
  543. }
  544. - (NSArray *)mailItemPropertiesForHeaders:(NSString *)headers
  545. endOfLine:(NSString *)endOfLine {
  546. NSMutableArray *props = [NSMutableArray array];
  547. NSString *mozStatus = [EmUpUtilities stringForHeader:@"X-Mozilla-Status"
  548. fromHeaders:headers
  549. endOfLine:endOfLine];
  550. if (mozStatus) {
  551. // convert from hex
  552. NSScanner *scanner = [NSScanner scannerWithString:mozStatus];
  553. unsigned int hexInt = 0;
  554. if ([scanner scanHexInt:&hexInt]) {
  555. BOOL isRead = ((hexInt & 0x0001) != 0);
  556. if (!isRead) [props addObject:kGDataMailItemIsUnread];
  557. BOOL isFlagged = ((hexInt & 0x0004) != 0);
  558. if (isFlagged) [props addObject:kGDataMailItemIsStarred];
  559. BOOL isDeleted = ((hexInt & 0x0008) != 0);
  560. if (isDeleted) [props addObject:kGDataMailItemIsTrash];
  561. // is draft status available in Thunderbird headers?
  562. }
  563. } else {
  564. // eudora status
  565. NSString *status = [EmUpUtilities stringForHeader:@"Status"
  566. fromHeaders:headers
  567. endOfLine:endOfLine];
  568. if (status) {
  569. BOOL isUnread = ([status rangeOfString:@"U"].location != NSNotFound);
  570. if (isUnread) [props addObject:kGDataMailItemIsUnread];
  571. }
  572. // other mobx status per http://wiki.dovecot.org/MailboxFormat/mbox
  573. NSString *xstatus = [EmUpUtilities stringForHeader:@"X-Status"
  574. fromHeaders:headers
  575. endOfLine:endOfLine];
  576. if (xstatus) {
  577. BOOL isFlagged = ([xstatus rangeOfString:@"F"].location != NSNotFound);
  578. if (isFlagged) [props addObject:kGDataMailItemIsStarred];
  579. BOOL isDeleted = ([xstatus rangeOfString:@"D"].location != NSNotFound);
  580. if (isDeleted) [props addObject:kGDataMailItemIsTrash];
  581. BOOL isDraft = ([xstatus rangeOfString:@"T"].location != NSNotFound);
  582. if (isDraft) [props addObject:kGDataMailItemIsDraft];
  583. }
  584. }
  585. if ([props count] > 0) return props;
  586. return nil;
  587. }
  588. - (void)setLastUploadedItem:(OutlineViewItemMBox *)item {
  589. [lastUploadedItem_ autorelease];
  590. lastUploadedItem_ = [item retain];
  591. }
  592. @end
  593. // cribbed from GDataMIMEDocument
  594. const char* memsearch(const char* needle, unsigned long long needleLen,
  595. const char* haystack, unsigned long long haystackLen) {
  596. // This is a simple approach. We start off by assuming that both memchr() and
  597. // memcmp are implemented efficiently on the given platform. We search for an
  598. // instance of the first char of our needle in the haystack. If the remaining
  599. // size could fit our needle, then we memcmp to see if it occurs at this point
  600. // in the haystack. If not, we move on to search for the first char again,
  601. // starting from the next character in the haystack.
  602. const char* ptr = haystack;
  603. unsigned long long remain = haystackLen;
  604. while ((ptr = memchr(ptr, needle[0], remain)) != 0) {
  605. remain = haystackLen - (ptr - haystack);
  606. if (remain < needleLen) {
  607. return NULL;
  608. }
  609. if (memcmp(ptr, needle, needleLen) == 0) {
  610. return ptr;
  611. }
  612. ptr++;
  613. remain--;
  614. }
  615. return NULL;
  616. }