2 // EKEzvOutgoingFileTransfer.m
5 // Created by Erich Kreutzer on 8/10/07.
6 // Copyright 2007 __MyCompanyName__. All rights reserved.
9 #import "EKEzvOutgoingFileTransfer.h"
12 #define APPLE_SINGLE_HEADER_LENGTH 26
13 #define APPLE_SINGLE_MAGIC_NUMBER 0x00051600
14 #define APPLE_SINGLE_VERSION_NUMBER 0x00020000
16 #define AS_ENTRY_DATA_FORK 1
17 #define AS_ENTRY_RESOURCE_FORK 2
18 #define AS_ENTRY_REAL_NAME 3
19 #define AS_ENTRY_COMMENT 4
20 #define AS_ENTRY_ICON_BW 5
21 #define AS_ENTRY_ICON_COLOR 6
22 #define AS_ENTRY_DATE_INFO 8
23 #define AS_ENTRY_FINDER_INFO 9
24 #define AS_ENTRY_MACINTOSH_FILE_INFO 10
25 #define AS_ENTRY_PRODOS_FILE_INFO 11
26 #define AS_ENTRY_MSDOS_FILE_INFO 12
27 #define AS_ENTRY_AFP_SHORT_NAME 13
28 #define AS_ENTRY_AFP_FILE_INFO 14
29 #define AS_ENTRY_AFP_DIRECTORY_ID 15
31 struct AppleSingleHeader {
37 typedef struct AppleSingleHeader AppleSingleHeader;
39 struct AppleSingleEntry {
44 typedef struct AppleSingleEntry AppleSingleEntry;
46 struct AppleSingleFinderInfo {
47 struct FileInfo finderInfo;
48 struct FXInfo extendedFinderInfo;
50 typedef struct AppleSingleFinderInfo AppleSingleFinderInfo;
53 @implementation EKEzvOutgoingFileTransfer
56 if ((self = [super init])) {
57 urlSizes = [[NSMutableDictionary alloc] initWithCapacity:10];
58 validURLS = [[NSMutableArray alloc] initWithCapacity:10];
59 urlData = [[NSMutableDictionary alloc] initWithCapacity:10];
68 [randomString release];
79 - (NSString *)posixflags
84 - (void)setContactUID:(NSString *)newUID
86 if (contactUID != newUID) {
88 contactUID = [newUID retain];
96 /* Get contact from UID */
97 [self setContact:[[self manager] contactForIdentifier:contactUID]];
99 success = [self processTransfer];
101 [[[[self manager] client] client] transferFailed:self];
105 success = [self getData];
107 [[[[self manager] client] client] transferFailed:self];
111 /* We need to start the server */
112 success = [self startHTTPServer];
114 [[[[self manager] client] client] transferFailed:self];
118 /* Now we send the correct information to the contact */
119 [self sendTransferMessage];
121 /* Keep ourself around until the transfer is complete or cancelled */
129 /* We called -[self retain] in startSending */
133 - (bool) processTransfer
135 /*Check to see if it is a directory, mimetype, etc... */
136 NSString *path = [self localFilename];
138 BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory];
140 [self cancelTransfer];
147 [self setMimeType:[self mimeTypeForPath:path]];
148 posixflags = [self posixFlagsForPath:path];
150 if (posixflags == nil) {
159 /*Let's load the data from disk into the urlData dictionary */
161 /*Only one file so let's add the path */
163 [urlData setObject:[self localFilename] forKey:[[self localFilename] lastPathComponent]];
166 /* We will need to update the size of the transfer so let's set it to 0 so we can add to it */
169 /*First we need to get the NSData for the xml to describe the directory contents*/
170 directoryXMLData = [[self generateDirectoryXML] retain];
171 /* Now we need to get the NSData for each item in the directory */
172 NSFileManager *fileManager = [NSFileManager defaultManager];
173 NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:[self localFilename]];
175 NSString *basePath = [[self localFilename] stringByAppendingString:@"/"];
176 while (file = [enumerator nextObject]) {
177 NSString *fullPath = [basePath stringByAppendingString:file];
181 exists = [fileManager fileExistsAtPath:fullPath isDirectory:&directory];
183 [[[[self manager] client] client] reportError:@"File to transfer no longer exists." ofLevel:AWEzvError];
187 if (!directory && ![file hasPrefix:@".DS_Store"]) {
188 NSString *subPath = [[[[self localFilename] lastPathComponent] stringByAppendingString:@"/"] stringByAppendingString: file];
191 NSNumber *sizeNumber = [self sizeNumberForPath:fullPath];
193 [urlSizes setObject:sizeNumber forKey:subPath];
194 [self setSize:[self size] + [sizeNumber unsignedLongLongValue]];
197 [urlData setObject:fullPath forKey:subPath];
204 - (bool) startHTTPServer
206 server = [[HTTPServer alloc] init];
209 BOOL success = [server start:&error];
213 [[[[self manager] client] client] reportError:@"Could not start HTTP Server." ofLevel:AWEzvError];
216 [server setTransfer: self];
221 - (void)sendTransferMessage
223 [[self contact] sendOutgoingFileTransfer: self];
226 #pragma mark Support Methods
228 - (NSData *)generateDirectoryXML
231 * <dir posixflags="01ED"> <name>untitled folder</name>
232 * <file mimetype="application/rtf" size="318"> <name>blah copy.rtf</name></file>
233 * <file mimetype="application/rtf" size="318"> <name>blah.rtf</name></file>
234 * <dir posixflags="01ED"> <name>folder</name>
238 NSString *newPath = [self localFilename];
240 NSXMLElement *root = [[NSXMLElement alloc] initWithName:@"dir"];
241 NSString *posixFlags = [self posixFlagsForPath: newPath];
242 if (posixFlags != nil) {
243 [root addAttribute:[NSXMLNode attributeWithName:@"posixflags" stringValue:posixFlags]];
247 NSXMLElement *name = [[NSXMLElement alloc] initWithName:@"name" stringValue:[newPath lastPathComponent]];
248 [root addChild:name];
249 NSArray *children = [self generateXMLFromDirectory:newPath];
251 NSEnumerator *enumerator = [children objectEnumerator];
253 while (child = [enumerator nextObject]) {
254 [root addChild:child];
257 NSString *xmlString = [root XMLString];
258 return [NSData dataWithBytes:[xmlString UTF8String] length:[xmlString length]];
261 - (NSArray *)generateXMLFromDirectory:(NSString *)basePath
264 * <dir posixflags="01ED"> <name>untitled folder</name>
265 * <file mimetype="application/rtf" size="318"> <name>blah copy.rtf</name></file>
266 * <file mimetype="application/rtf" size="318"> <name>blah.rtf</name></file>
267 * <dir posixflags="01ED"> <name>folder</name>
271 NSMutableArray *children = [NSMutableArray arrayWithCapacity:10];
272 NSFileManager *fileManager = [NSFileManager defaultManager];
273 NSEnumerator *enumerator = [[fileManager directoryContentsAtPath:basePath] objectEnumerator];
276 while (file = [enumerator nextObject]) {
277 NSString *newPath = [basePath stringByAppendingPathComponent:file];
280 exists = [fileManager fileExistsAtPath:newPath isDirectory:&directory];
282 [[[[self manager] client] client] reportError:@"File to transfer no longer exists." ofLevel:AWEzvError];
285 if ([file hasPrefix:@".DS_Store"]) {
290 // handle the creation of the directory xml
291 NSXMLElement *directory = [[NSXMLElement alloc] initWithName:@"dir"];
292 NSString *posixFlags = [self posixFlagsForPath: newPath];
293 if (posixFlags != nil) {
294 [directory addAttribute:[NSXMLNode attributeWithName:@"posixflags" stringValue:posixFlags]];
297 NSXMLElement *name = [[NSXMLElement alloc] initWithName:@"name" stringValue:file];
298 [directory addChild:name];
300 NSArray *dirChildren = [self generateXMLFromDirectory:newPath];
302 NSEnumerator *dirEnumerator = [dirChildren objectEnumerator];
304 while (child = [dirEnumerator nextObject]) {
305 [directory addChild:child];
308 [children addObject:directory];
310 // create the file xml
311 NSXMLElement *fileXML = [[NSXMLElement alloc] initWithName:@"file"];
312 NSString *mimeTypeString = [self mimeTypeForPath:newPath];
313 if (mimeType != nil) {
314 [fileXML addAttribute:[NSXMLNode attributeWithName:@"mimetype" stringValue:mimeTypeString]];
317 NSString *posixFlags = [self posixFlagsForPath:newPath];
318 if (posixFlags != nil) {
319 [fileXML addAttribute:[NSXMLNode attributeWithName:@"posixflags" stringValue:posixFlags]];
321 NSString *sizeString = [self sizeForPath:newPath];
323 [fileXML addAttribute:[NSXMLNode attributeWithName:@"size" stringValue:sizeString]];
326 NSXMLElement *name = [[NSXMLElement alloc] initWithName:@"name" stringValue:file];
327 [fileXML addChild:name];
329 /*Now add this to the array */
330 [children addObject:fileXML];
335 - (NSString *)baseURL
338 NSString *component = [NSString stringWithFormat:@"http://%@:%hu", [server localHost], [server port]];
340 NSString *URI = @"/";
342 URI = [URI stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]];
344 randomString = [[URI stringByAppendingString:@"/"] retain];
346 URI = [URI stringByAppendingPathComponent:[[[self localFilename] lastPathComponent] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
348 URI = [URI stringByAppendingString:@"/"];
350 [validURLS addObject:URI];
352 component = [component stringByAppendingString:URI];
356 - (BOOL)isBaseURIForDirectoryTransfer:(NSString *)URI
359 if ([URI hasPrefix:randomString] && ([URI length] > [randomString length])) {
360 NSString *path = [URI substringFromIndex:[randomString length]];
361 if ([path isEqualToString:[[[self localFilename] lastPathComponent] stringByAppendingString:@"/"]]) {
367 - (BOOL)isValidURI:(NSString *)URI
370 isValid = [validURLS containsObject:URI];
372 [[[[self manager] client] client] reportError:@"Client requested an invalid file." ofLevel:AWEzvError];
377 - (NSData *)appleSingleDataForURI:(NSString *)URI
379 NSString *filePath = nil;
380 NSNumber *singleSize = nil;
381 if ([URI hasPrefix:randomString] && ([URI length] > [randomString length])) {
382 NSString *path = [URI substringFromIndex:[randomString length]];
383 filePath = [[[self localFilename] stringByDeletingLastPathComponent] stringByAppendingPathComponent: path];
384 singleSize = [urlSizes valueForKey:path];
388 /*Include the three entries that iChat includes */
389 struct AppleSingleHeader info;
390 memset(&info, 0, sizeof(info));
391 info.magicNumber = htonl(APPLE_SINGLE_MAGIC_NUMBER);
392 info.versionNumber = htonl(APPLE_SINGLE_VERSION_NUMBER);
393 info.numberEntries = htons(3);
395 /* Setup the three AppleSingleEntrys */
396 struct AppleSingleEntry finderInfoEntry;
397 struct AppleSingleEntry realNameEntry;
398 struct AppleSingleEntry dataEntry;
400 memset(&finderInfoEntry, 0, sizeof(finderInfoEntry));
401 memset(&realNameEntry, 0, sizeof(realNameEntry));
402 memset(&dataEntry, 0, sizeof(dataEntry));
404 unsigned long long offset = APPLE_SINGLE_HEADER_LENGTH + sizeof(finderInfoEntry) * 3;
406 /*Get the info from the finder */
408 FSCatalogInfo catalogInfo;
410 BOOL itemIsDirectory = NO;
411 err = FSPathMakeRef((const UInt8 *)[filePath fileSystemRepresentation], &ref, &itemIsDirectory);
413 [[[[self manager] client] client] reportError:@"AppleSingle: Error creating FSRef" ofLevel:AWEzvError];
416 err = FSGetCatalogInfo (/*const FSRef * ref*/ &ref,
417 /*FSCatalogInfoBitmap whichInfo*/ (kFSCatInfoFinderInfo | kFSCatInfoFinderXInfo),
418 /*FSCatalogInfo * catalogInfo*/ &catalogInfo,
419 /*HFSUniStr255 * outName*/ NULL,
420 /*FSSpec * fsSpec*/ NULL,
421 /*FSRef * parentRef*/ NULL);
423 [[[[self manager] client] client] reportError:@"AppleSingle: Error creating FSRef" ofLevel:AWEzvError];
427 /*Use the info from finder to create the AppleSingleFinderInfo struct */
428 struct AppleSingleFinderInfo fileInfo;
429 memset(&fileInfo, 0, sizeof(fileInfo));
430 BlockMoveData(&(catalogInfo.finderInfo), &(fileInfo.finderInfo), sizeof((fileInfo.finderInfo)));
431 BlockMoveData(&(catalogInfo.extFinderInfo), &(fileInfo.extendedFinderInfo), sizeof((fileInfo.extendedFinderInfo)));
433 /*Now switch from host to network byte order */
434 fileInfo.finderInfo.finderFlags = htons(fileInfo.finderInfo.finderFlags);
436 /*Create the AppleSingleEntry for this data */
437 finderInfoEntry.entryID = htonl(AS_ENTRY_FINDER_INFO);
438 finderInfoEntry.length = htonl(sizeof(fileInfo));
439 /*Offset so that it is at the end of the *3* AppleSingleEntries*/
440 finderInfoEntry.offset = htonl(offset);
441 offset += sizeof(fileInfo);
444 const char *realName = [[URI lastPathComponent] UTF8String];
445 unsigned nameLength = [[URI lastPathComponent] length];
447 realNameEntry.entryID = htonl(AS_ENTRY_REAL_NAME);
448 realNameEntry.length = htonl(nameLength);
449 /*Offset so that it is at the end of the *3* AppleSingleEntries*/
450 realNameEntry.offset = htonl(offset);
452 offset += nameLength;
453 unsigned long long newSize;
454 if ([self isDirectory]) {
455 newSize = [singleSize unsignedLongLongValue];
457 newSize = [self size];
459 /*Data resource fork */
460 dataEntry.entryID = htonl(AS_ENTRY_DATA_FORK);
461 dataEntry.length = htonl(newSize);
462 dataEntry.offset = htonl(offset);
464 NSMutableData *data = [NSMutableData dataWithBytes:&info length: APPLE_SINGLE_HEADER_LENGTH];
465 [data appendBytes:&finderInfoEntry length:sizeof(finderInfoEntry)];
466 [data appendBytes:&realNameEntry length:sizeof(realNameEntry)];
467 [data appendBytes:&dataEntry length:sizeof(dataEntry)];
468 [data appendBytes:&fileInfo length:sizeof(fileInfo)];
469 [data appendBytes:realName length:nameLength];
470 /*Data will be appended in the HTTPServer*/
473 - (NSString *)fileDataForURI:(NSString *)URI
475 NSString *data = nil;
476 if ([URI hasPrefix:randomString] && ([URI length] > [randomString length])) {
477 NSString *path = [URI substringFromIndex:[randomString length]];
478 data = [(NSString *)[urlData valueForKey:path] retain];
479 [urlData removeObjectForKey:path];
484 - (NSString *)posixFlagsForPath:(NSString *)filePath
486 NSString *posixFlags = nil;
487 NSDictionary *attributes = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:NO];
488 if (attributes && [attributes objectForKey:NSFilePosixPermissions]) {
489 NSNumber *posixInfo = [attributes objectForKey:NSFilePosixPermissions];
490 posixFlags = [NSString stringWithFormat:@"%X", [posixInfo longValue]];
496 - (NSString *)mimeTypeForPath:(NSString *)filePath
498 NSString *mime = nil;
499 NSString *UTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
500 (CFStringRef)[filePath pathExtension],
502 mime = [(NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)UTI, kUTTagClassMIMEType) autorelease];
503 if (!mime || [mime length] == 0)
505 mime = @"application/octet-stream";
510 - (NSString *)sizeForPath:(NSString *)filePath
512 NSString *fileSize = nil;
513 NSDictionary *attributes = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:NO];
514 if (attributes && [attributes objectForKey:NSFileSize]) {
515 NSNumber *fileSizeNumber = [attributes objectForKey:NSFileSize];
516 fileSize = [NSString stringWithFormat:@"%qu", [fileSizeNumber unsignedLongLongValue]];
522 - (NSNumber *)sizeNumberForPath:(NSString *)filePath
524 NSNumber *fileSize = nil;
525 NSDictionary *attributes = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:NO];
526 if (attributes && [attributes objectForKey:NSFileSize]) {
527 fileSize = [attributes objectForKey:NSFileSize];
533 - (void)cancelTransfer
538 - (void)userFailedDownload
540 [[[[self manager] client] client] remoteCanceledFileTransfer:self];
542 - (void)userBeganDownload
544 [[[[self manager] client] client] remoteUserBeganDownload:self];
547 - (void)userFinishedDownload
549 /* Cleanup the data lying around */
552 [[[[self manager] client] client] remoteUserFinishedDownload:self];
555 - (void)didSendDataWithLength:(UInt32)length
557 bytesSent = bytesSent+length;
558 percentComplete = ((float)bytesSent/(float)[[self sizeNumber] floatValue]);
559 if (percentComplete < 1.0) {
560 [[[[self manager] client] client] updateProgressForFileTransfer:self
561 percent:[NSNumber numberWithFloat:percentComplete]
562 bytesSent:[NSNumber numberWithLongLong:bytesSent]];
566 - (BOOL)moreFilesToDownload
569 if (isDirectory && urlData)
570 more = ([urlData count] > 0);
574 - (NSData *)directoryXMLData
576 return directoryXMLData;