Cleanup and relese safety; if you release an ivar, you must set it to nil to avoid...
[adiumx.git] / Plugins / Bonjour / libezv / Classes / EKEzvOutgoingFileTransfer.m
blobbf37c89faf7c71fec0a99b56ede31d466814f280
1 //
2 //  EKEzvOutgoingFileTransfer.m
3 //  Adium
4 //
5 //  Created by Erich Kreutzer on 8/10/07.
6 //  Copyright 2007 __MyCompanyName__. All rights reserved.
7 //
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 {
32         UInt32 magicNumber;
33         UInt32 versionNumber;
34         char filler[16];
35         UInt16 numberEntries;
37 typedef struct AppleSingleHeader AppleSingleHeader;
39 struct AppleSingleEntry {
40         UInt32 entryID;
41         UInt32 offset;
42         UInt32 length;
44 typedef struct AppleSingleEntry AppleSingleEntry;
46 struct AppleSingleFinderInfo {
47         struct FileInfo finderInfo;
48         struct FXInfo extendedFinderInfo; 
50 typedef struct AppleSingleFinderInfo AppleSingleFinderInfo;
53 @implementation EKEzvOutgoingFileTransfer
54 - (id)init
56         if ((self = [super init])) {
57                 urlSizes = [[NSMutableDictionary alloc] initWithCapacity:10];
58                 validURLS = [[NSMutableArray alloc] initWithCapacity:10];
59                 urlData = [[NSMutableDictionary alloc] initWithCapacity:10];
60         }
61         return self;
63 - (void)dealloc
65         [urlSizes release];
66         [validURLS release];
67         [urlData release];
68         [randomString release];
69         [server release];
71         [super dealloc];
74 - (BOOL)isDirectory
76         return isDirectory;
79 - (NSString *)posixflags
81         return posixflags;
84 - (void)setContactUID:(NSString *)newUID
86         if (contactUID != newUID) {
87                 [contactUID release];
88                 contactUID = [newUID retain];
89         }
92 - (void)startSending
94         bool success = NO;
96         /* Get contact from UID */
97         [self setContact:[[self manager] contactForIdentifier:contactUID]];
99         success = [self processTransfer];
100         if (!success) {
101                 [[[[self manager] client] client] transferFailed:self];
102                 return;
103         }
105         success = [self getData];
106         if (!success) {
107                 [[[[self manager] client] client] transferFailed:self];
108                 return;
109         }
111         /* We need to start the server */
112         success = [self startHTTPServer];
113         if (!success) {
114                 [[[[self manager] client] client] transferFailed:self];
115                 return;
116         }
117         
118         /* Now we send the correct information to the contact */
119         [self sendTransferMessage];
120         
121         /* Keep ourself around until the transfer is complete or cancelled */
122         [self retain];
125 - (void)stopSending
127         [server stop];
128         
129         /* We called -[self retain] in startSending */
130         [self autorelease];
133 - (bool) processTransfer
135         /*Check to see if it is a directory, mimetype, etc... */
136         NSString *path = [self localFilename];
137         BOOL directory = NO;
138         BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory];
139         if (!exists) {
140                 [self cancelTransfer];
141                 return NO;
142         }
143         if (directory) {
144                 isDirectory = YES;
145         }
147         [self setMimeType:[self mimeTypeForPath:path]];
148         posixflags = [self posixFlagsForPath:path];
150         if (posixflags == nil) {
151                 return NO;
152         }
154         return YES;
157 - (bool)getData
159         /*Let's load the data from disk into the urlData dictionary */
160         if (!isDirectory) {
161                 /*Only one file so let's add the path */
163                 [urlData setObject:[self localFilename] forKey:[[self localFilename] lastPathComponent]];
165         } else {
166                 /* We will need to update the size of the transfer so let's set it to 0 so we can add to it */
167                 [self setSize:0u];
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]];
174                 NSString *file;
175                 NSString *basePath = [[self localFilename] stringByAppendingString:@"/"];
176                 while (file = [enumerator nextObject]) {
177                         NSString *fullPath = [basePath stringByAppendingString:file];
179                         BOOL exists = NO;
180                         BOOL directory = NO;
181                         exists = [fileManager fileExistsAtPath:fullPath isDirectory:&directory];
182                         if (!exists) {
183                                 [[[[self manager] client] client] reportError:@"File to transfer no longer exists." ofLevel:AWEzvError];
185                                 return nil;
186                         }
187                         if (!directory && ![file hasPrefix:@".DS_Store"]) {
188                                 NSString *subPath = [[[[self localFilename] lastPathComponent] stringByAppendingString:@"/"] stringByAppendingString: file];
190                                 /*Reset the size */
191                                 NSNumber *sizeNumber = [self sizeNumberForPath:fullPath];
192                                 if (sizeNumber) {
193                                         [urlSizes setObject:sizeNumber forKey:subPath];
194                                         [self setSize:[self size] + [sizeNumber unsignedLongLongValue]];
195                                 }
197                                 [urlData setObject:fullPath forKey:subPath];
198                         }
199                 }
200         }
201         return YES;
204 - (bool) startHTTPServer
206         server = [[HTTPServer alloc] init];
208         NSError *error;
209         BOOL success = [server start:&error];
211         if (!success)
212         {
213                 [[[[self manager] client] client] reportError:@"Could not start HTTP Server." ofLevel:AWEzvError];
214                 return NO;
215         } else {
216                 [server setTransfer: self];
217                 return YES;
218         }
221 - (void)sendTransferMessage
223         [[self contact] sendOutgoingFileTransfer: self];
226 #pragma mark Support Methods
228 - (NSData *)generateDirectoryXML
230         /*Example XML:
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>
235          *  </dir>
236          * </dir>
237          **/
238         NSString *newPath = [self localFilename];
239         /*Create the dir */
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]];
244         }
246         /*Add the name */
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];
252         NSXMLElement *child;
253         while (child = [enumerator nextObject]) {
254                 [root addChild:child];
255         }
257         NSString *xmlString = [root XMLString];
258         return [NSData dataWithBytes:[xmlString UTF8String] length:[xmlString length]];
261 - (NSArray *)generateXMLFromDirectory:(NSString *)basePath
263         /*Example XML:
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>
268          *  </dir>
269          * </dir>
270          **/
271         NSMutableArray *children = [NSMutableArray arrayWithCapacity:10];
272         NSFileManager *fileManager = [NSFileManager defaultManager];
273         NSEnumerator *enumerator = [[fileManager directoryContentsAtPath:basePath] objectEnumerator];
275         NSString *file;
276         while (file = [enumerator nextObject]) {
277                 NSString *newPath = [basePath stringByAppendingPathComponent:file];
278                 bool exists = NO;
279                 bool directory = NO;
280                 exists = [fileManager fileExistsAtPath:newPath isDirectory:&directory];
281                 if (!exists) {
282                         [[[[self manager] client] client] reportError:@"File to transfer no longer exists." ofLevel:AWEzvError];
283                         return nil;
284                 }
285                 if ([file hasPrefix:@".DS_Store"]) {
286                         continue;
287                 }
289                 if (directory) {
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]];
295                         }
297                         NSXMLElement *name = [[NSXMLElement alloc] initWithName:@"name" stringValue:file];
298                         [directory addChild:name];
300                         NSArray *dirChildren = [self generateXMLFromDirectory:newPath];
302                         NSEnumerator *dirEnumerator = [dirChildren objectEnumerator];
303                         NSXMLElement *child;
304                         while (child = [dirEnumerator nextObject]) {
305                                 [directory addChild:child];
306                         }
308                         [children addObject:directory];
309                 } else {
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]];
315                         }
317                         NSString *posixFlags = [self posixFlagsForPath:newPath];
318                         if (posixFlags != nil) {
319                                 [fileXML addAttribute:[NSXMLNode attributeWithName:@"posixflags" stringValue:posixFlags]];
320                         }
321                         NSString *sizeString = [self sizeForPath:newPath];
322                         if (size != nil) {
323                                 [fileXML addAttribute:[NSXMLNode attributeWithName:@"size" stringValue:sizeString]];
324                         }
326                         NSXMLElement *name = [[NSXMLElement alloc] initWithName:@"name" stringValue:file];
327                         [fileXML addChild:name];
329                         /*Now add this to the array */
330                         [children addObject:fileXML];
331                 }
332         }
333         return children;
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]];
347         if (isDirectory)
348                 URI = [URI stringByAppendingString:@"/"];
350         [validURLS addObject:URI];
352         component = [component stringByAppendingString:URI];
354         return component;
356 - (BOOL)isBaseURIForDirectoryTransfer:(NSString *)URI
358         BOOL isBase = NO;
359         if ([URI hasPrefix:randomString] && ([URI length] > [randomString length])) {
360                 NSString *path = [URI substringFromIndex:[randomString length]];
361                 if ([path isEqualToString:[[[self localFilename] lastPathComponent] stringByAppendingString:@"/"]]) {
362                         isBase = YES;
363                 }
364         }
365         return isBase;
367 - (BOOL)isValidURI:(NSString *)URI
369         bool isValid = NO;
370         isValid = [validURLS containsObject:URI];
371         if (!isValid) {
372                 [[[[self manager] client] client] reportError:@"Client requested an invalid file." ofLevel:AWEzvError];
373         }
374         return isValid;
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];
385         }
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;
405         /*Finder info */
406         /*Get the info from the finder */
407         FSRef ref;
408         FSCatalogInfo catalogInfo;
409         OSStatus err;
410         BOOL itemIsDirectory = NO;
411         err = FSPathMakeRef((const UInt8 *)[filePath fileSystemRepresentation], &ref, &itemIsDirectory);
412         if (err != noErr) {
413                 [[[[self manager] client] client] reportError:@"AppleSingle: Error creating FSRef" ofLevel:AWEzvError];
414                 return nil;
415         }
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);
422         if (err != noErr) {
423                 [[[[self manager] client] client] reportError:@"AppleSingle: Error creating FSRef" ofLevel:AWEzvError];
424                 return nil;
425         }
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);
443         /*Real Name*/
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];
456         } else {
457                 newSize = [self size];
458         }
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*/
471         return data;
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];
480         }
481         return data;
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]];
491         }
493         return posixFlags;
496 - (NSString *)mimeTypeForPath:(NSString *)filePath
498         NSString *mime = nil;
499         NSString *UTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
500                                                                            (CFStringRef)[filePath pathExtension],
501                                                                            NULL) autorelease];
502         mime = [(NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)UTI, kUTTagClassMIMEType) autorelease];
503         if (!mime || [mime length] == 0)
504         {
505                 mime = @"application/octet-stream";
506         }
507         return mime;
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]];
517         }
519         return fileSize;
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];
528         }
530         return fileSize;
533 - (void)cancelTransfer
535         [self stopSending];
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 */
550         [self stopSending];
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]];
563         }
566 - (BOOL)moreFilesToDownload
568         BOOL more = NO;
569         if (isDirectory && urlData)
570                 more = ([urlData count] > 0);
571         return more;
574 - (NSData *)directoryXMLData
576         return directoryXMLData;
578 @end