arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.mRaw
  1/*
  2 Licensed to the Apache Software Foundation (ASF) under one
  3 or more contributor license agreements.  See the NOTICE file
  4 distributed with this work for additional information
  5 regarding copyright ownership.  The ASF licenses this file
  6 to you under the Apache License, Version 2.0 (the
  7 "License"); you may not use this file except in compliance
  8 with the License.  You may obtain a copy of the License at
  9
 10 http://www.apache.org/licenses/LICENSE-2.0
 11
 12 Unless required by applicable law or agreed to in writing,
 13 software distributed under the License is distributed on an
 14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 KIND, either express or implied.  See the License for the
 16 specific language governing permissions and limitations
 17 under the License.
 18 */
 19
 20#import "CDVFile.h"
 21#import "CDVLocalFilesystem.h"
 22#import <Cordova/CDV.h>
 23#import <MobileCoreServices/MobileCoreServices.h>
 24#import <sys/xattr.h>
 25
 26@implementation CDVLocalFilesystem
 27@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
 28
 29- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
 30{
 31    if (self) {
 32        self.name = name;
 33        self.fsRoot = fsRoot;
 34    }
 35    return self;
 36}
 37
 38/*
 39 * IN
 40 *  NSString localURI
 41 * OUT
 42 *  CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
 43 *   URI represents a non-existent path, or is unrecognized or otherwise malformed.
 44 */
 45- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
 46{
 47    CDVPluginResult* result = nil;
 48    NSDictionary* entry = [self makeEntryForLocalURL:url];
 49    if (entry) {
 50        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
 51    } else {
 52        // return NOT_FOUND_ERR
 53        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
 54    }
 55    return result;
 56}
 57- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
 58    NSString *path = [self filesystemPathForURL:url];
 59    NSFileManager* fileMgr = [[NSFileManager alloc] init];
 60    BOOL isDir = NO;
 61    // see if exists and is file or dir
 62    BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
 63    if (bExists) {
 64        return [self makeEntryForPath:url.fullPath isDirectory:isDir];
 65    } else {
 66        return nil;
 67    }
 68}
 69- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
 70{
 71    NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
 72    NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
 73    if (isDir && ![fullPath hasSuffix:@"/"]) {
 74        fullPath = [fullPath stringByAppendingString:@"/"];
 75    }
 76    [dirEntry setObject:[NSNumber numberWithBool:!isDir]  forKey:@"isFile"];
 77    [dirEntry setObject:[NSNumber numberWithBool:isDir]  forKey:@"isDirectory"];
 78    [dirEntry setObject:fullPath forKey:@"fullPath"];
 79    [dirEntry setObject:lastPart forKey:@"name"];
 80    [dirEntry setObject:self.name forKey: @"filesystemName"];
 81
 82    NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
 83    if (self.urlTransformer) {
 84        nativeURL = self.urlTransformer(nativeURL);
 85    }
 86
 87    dirEntry[@"nativeURL"] = [nativeURL absoluteString];
 88
 89    return dirEntry;
 90}
 91
 92- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
 93{
 94    NSRange questionMark = [fullPath rangeOfString:@"?"];
 95    if (questionMark.location != NSNotFound) {
 96        return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
 97    }
 98    return fullPath;
 99}
100
101- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
102{
103    NSString *path = nil;
104    NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
105    path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
106    if ([path length] > 1 && [path hasSuffix:@"/"]) {
107      path = [path substringToIndex:([path length]-1)];
108    }
109    return path;
110}
111/*
112 * IN
113 *  NSString localURI
114 * OUT
115 *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
116 *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
117 *  or if the URL is malformed.
118 * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
119 */
120- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
121{
122    return [self filesystemPathForFullPath:url.fullPath];
123}
124
125- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
126{
127    if (fullPath) {
128        NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
129        if ([fullPath hasPrefix:@"/"]) {
130            return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
131        }
132        return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
133    }
134    return nil;
135}
136
137- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
138{
139    return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
140
141}
142
143- (NSString *)normalizePath:(NSString *)rawPath
144{
145    // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
146    BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
147    if (isAbsolutePath) {
148        rawPath = [rawPath substringFromIndex:1];
149    }
150    NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
151    for (int index = 0; index < [components count]; ++index) {
152        if ([[components objectAtIndex:index] isEqualToString:@".."]) {
153            [components removeObjectAtIndex:index];
154            if (index > 0) {
155                [components removeObjectAtIndex:index-1];
156                --index;
157            }
158        }
159    }
160
161    if (isAbsolutePath) {
162        return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
163    } else {
164        return [components componentsJoinedByString:@"/"];
165    }
166
167
168}
169
170- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
171{
172    BOOL bNumber = NO;
173    NSObject* value = dict[key];
174    if (value) {
175        bNumber = [value isKindOfClass:[NSNumber class]];
176    }
177    return bNumber;
178}
179
180- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
181{
182    CDVPluginResult* result = nil;
183    BOOL bDirRequest = NO;
184    BOOL create = NO;
185    BOOL exclusive = NO;
186    int errorCode = 0;  // !!! risky - no error code currently defined for 0
187
188    if ([self valueForKeyIsNumber:options key:@"create"]) {
189        create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
190    }
191    if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
192        exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
193    }
194    if ([self valueForKeyIsNumber:options key:@"getDir"]) {
195        // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
196        bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
197    }
198    // see if the requested path has invalid characters - should we be checking for  more than just ":"?
199    if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
200        errorCode = ENCODING_ERR;
201    } else {
202        // Build new fullPath for the requested resource.
203        // We concatenate the two paths together, and then scan the resulting string to remove
204        // parent ("..") references. Any parent references at the beginning of the string are
205        // silently removed.
206        NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
207        combinedPath = [self normalizePath:combinedPath];
208        CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
209
210        NSFileManager* fileMgr = [[NSFileManager alloc] init];
211        BOOL bIsDir;
212        BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
213        if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
214            // path exists and is not of requested type  - return TYPE_MISMATCH_ERR
215            errorCode = TYPE_MISMATCH_ERR;
216        } else if (!bExists && (create == NO)) {
217            // path does not exist and create is false - return NOT_FOUND_ERR
218            errorCode = NOT_FOUND_ERR;
219        } else if (bExists && (create == YES) && (exclusive == YES)) {
220            // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
221            errorCode = PATH_EXISTS_ERR;
222        } else {
223            // if bExists and create == YES - just return data
224            // if bExists and create == NO  - just return data
225            // if !bExists and create == YES - create and return data
226            BOOL bSuccess = YES;
227            NSError __autoreleasing* pError = nil;
228            if (!bExists && (create == YES)) {
229                if (bDirRequest) {
230                    // create the dir
231                    bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
232                } else {
233                    // create the empty file
234                    bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
235                }
236            }
237            if (!bSuccess) {
238                errorCode = ABORT_ERR;
239                if (pError) {
240                    NSLog(@"error creating directory: %@", [pError localizedDescription]);
241                }
242            } else {
243                // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
244                // file existed or was created
245                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
246            }
247        } // are all possible conditions met?
248    }
249
250    if (errorCode > 0) {
251        // create error callback
252        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
253    }
254    return result;
255
256}
257
258- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
259{
260    CDVPluginResult* result = nil;
261    CDVFilesystemURL *newURI = nil;
262    if ([localURI.fullPath isEqualToString:@""]) {
263        // return self
264        newURI = localURI;
265    } else {
266        newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
267    }
268    NSFileManager* fileMgr = [[NSFileManager alloc] init];
269    BOOL bIsDir;
270    BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
271    if (bExists) {
272        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
273    } else {
274        // invalid path or file does not exist
275        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
276    }
277    return result;
278}
279
280- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
281{
282    BOOL ok = NO;
283
284    NSString* filePath = [self filesystemPathForURL:localURI];
285    // we only care about this iCloud key for now.
286    // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
287    NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
288    id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
289
290    if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
291        if (IsAtLeastiOSVersion(@"5.1")) {
292            NSURL* url = [NSURL fileURLWithPath:filePath];
293            NSError* __autoreleasing error = nil;
294
295            ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
296        } else { // below 5.1 (deprecated - only really supported in 5.01)
297            u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
298            if (value == 0) { // remove the attribute (allow backup, the default)
299                ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
300            } else { // set the attribute (skip backup)
301                ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
302            }
303        }
304    }
305
306    if (ok) {
307        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
308    } else {
309        return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
310    }
311}
312
313/* remove the file or directory (recursively)
314 * IN:
315 * NSString* fullPath - the full path to the file or directory to be removed
316 * NSString* callbackId
317 * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
318 */
319
320- (CDVPluginResult*)doRemove:(NSString*)fullPath
321{
322    CDVPluginResult* result = nil;
323    BOOL bSuccess = NO;
324    NSError* __autoreleasing pError = nil;
325    NSFileManager* fileMgr = [[NSFileManager alloc] init];
326
327    @try {
328        bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
329        if (bSuccess) {
330            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
331        } else {
332            // see if we can give a useful error
333            CDVFileError errorCode = ABORT_ERR;
334            NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
335            if ([pError code] == NSFileNoSuchFileError) {
336                errorCode = NOT_FOUND_ERR;
337            } else if ([pError code] == NSFileWriteNoPermissionError) {
338                errorCode = NO_MODIFICATION_ALLOWED_ERR;
339            }
340
341            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
342        }
343    } @catch(NSException* e) {  // NSInvalidArgumentException if path is . or ..
344        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
345    }
346
347    return result;
348}
349
350- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
351{
352    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
353
354    NSFileManager* fileMgr = [[NSFileManager alloc] init];
355    BOOL bIsDir = NO;
356    BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
357    if (!bExists) {
358        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
359    }
360    if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
361        // dir is not empty
362        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
363    }
364    return [self doRemove:fileSystemPath];
365}
366
367- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
368{
369    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
370    return [self doRemove:fileSystemPath];
371}
372
373/*
374 * IN
375 *  NSString localURI
376 * OUT
377 *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
378 *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
379 *  or if the URL is malformed.
380 * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
381 */
382- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
383{
384    if ([fsPath hasPrefix:self.fsRoot]) {
385        return [fsPath substringFromIndex:[self.fsRoot length]];
386    }
387    return nil;
388}
389
390
391- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
392{
393    NSFileManager* fileMgr = [[NSFileManager alloc] init];
394    NSError* __autoreleasing error = nil;
395    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
396
397    NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
398
399    if (contents) {
400        NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
401        if ([contents count] > 0) {
402            // create an Entry (as JSON) for each file/dir
403            for (NSString* name in contents) {
404                // see if is dir or file
405                NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
406                BOOL bIsDir = NO;
407                [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
408                NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
409                [entries addObject:entryDict];
410            }
411        }
412        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
413    } else {
414        // assume not found but could check error for more specific error conditions
415        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
416    }
417}
418
419- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
420{
421    unsigned long long newPos = 0UL;
422
423    NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
424
425    if (file) {
426        [file truncateFileAtOffset:(unsigned long long)pos];
427        newPos = [file offsetInFile];
428        [file synchronizeFile];
429        [file closeFile];
430    }
431    return newPos;
432}
433
434- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
435{
436    unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
437    return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
438}
439
440- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
441{
442    NSString *filePath = [self filesystemPathForURL:localURL];
443
444    CDVPluginResult* result = nil;
445    CDVFileError errCode = INVALID_MODIFICATION_ERR;
446    int bytesWritten = 0;
447
448    if (filePath) {
449        NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
450        if (fileStream) {
451            NSUInteger len = [encData length];
452            if (len == 0) {
453                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
454            } else {
455                [fileStream open];
456
457                bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
458
459                [fileStream close];
460                if (bytesWritten > 0) {
461                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
462                    // } else {
463                    // can probably get more detailed error info via [fileStream streamError]
464                    // errCode already set to INVALID_MODIFICATION_ERR;
465                    // bytesWritten = 0; // may be set to -1 on error
466                }
467            }
468        } // else fileStream not created return INVALID_MODIFICATION_ERR
469    } else {
470        // invalid filePath
471        errCode = NOT_FOUND_ERR;
472    }
473    if (!result) {
474        // was an error
475        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
476    }
477    return result;
478}
479
480/**
481 * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
482 * or attempted to copy a directory into a directory that it contains directly or indirectly.
483 *
484 * IN:
485 *  NSString* srcDir
486 *  NSString* destinationDir
487 * OUT:
488 *  YES copy/ move is allows
489 *  NO move is onto itself
490 */
491- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
492{
493    // This weird test is to determine if we are copying or moving a directory into itself.
494    // Copy /Documents/myDir to /Documents/myDir-backup is okay but
495    // Copy /Documents/myDir to /Documents/myDir/backup not okay
496    BOOL copyOK = YES;
497    NSRange range = [dest rangeOfString:src];
498
499    if (range.location != NSNotFound) {
500        NSRange testRange = {range.length - 1, ([dest length] - range.length)};
501        NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
502        if (resultRange.location != NSNotFound) {
503            copyOK = NO;
504        }
505    }
506    return copyOK;
507}
508
509- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
510{
511    NSFileManager *fileMgr = [[NSFileManager alloc] init];
512    NSString *destRootPath = [self filesystemPathForURL:destURL];
513    BOOL bDestIsDir = NO;
514    BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
515
516    NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
517    NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
518
519    BOOL bNewIsDir = NO;
520    BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
521
522    CDVPluginResult *result = nil;
523    int errCode = 0;
524
525    if (!bDestExists) {
526        // the destination root does not exist
527        errCode = NOT_FOUND_ERR;
528    }
529
530    else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
531        /* Same FS, we can shortcut with NSFileManager operations */
532        NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
533
534        BOOL bSrcIsDir = NO;
535        BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
536
537        if (!bSrcExists) {
538            // the source does not exist
539            errCode = NOT_FOUND_ERR;
540        } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
541            // source and destination can not be the same
542            errCode = INVALID_MODIFICATION_ERR;
543        } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
544            // can't copy/move dir to file
545            errCode = INVALID_MODIFICATION_ERR;
546        } else { // no errors yet
547            NSError* __autoreleasing error = nil;
548            BOOL bSuccess = NO;
549            if (bCopy) {
550                if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
551                    // can't copy dir into self
552                    errCode = INVALID_MODIFICATION_ERR;
553                } else if (bNewExists) {
554                    // the full destination should NOT already exist if a copy
555                    errCode = PATH_EXISTS_ERR;
556                } else {
557                    bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
558                }
559            } else { // move
560                // iOS requires that destination must not exist before calling moveTo
561                // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
562                //
563                if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
564                    // can't move a file to directory
565                    errCode = INVALID_MODIFICATION_ERR;
566                } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
567                    // can't move a dir into itself
568                    errCode = INVALID_MODIFICATION_ERR;
569                } else if (bNewExists) {
570                    if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
571                        // can't move dir to a dir that is not empty
572                        errCode = INVALID_MODIFICATION_ERR;
573                        newFileSystemPath = nil;  // so we won't try to move
574                    } else {
575                        // remove destination so can perform the moveItemAtPath
576                        bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
577                        if (!bSuccess) {
578                            errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
579                            newFileSystemPath = nil;
580                        }
581                    }
582                } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
583                    // can't move a directory inside itself or to any child at any depth;
584                    errCode = INVALID_MODIFICATION_ERR;
585                    newFileSystemPath = nil;
586                }
587
588                if (newFileSystemPath != nil) {
589                    bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
590                }
591            }
592            if (bSuccess) {
593                // should verify it is there and of the correct type???
594                NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
595                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
596            } else {
597                if (error) {
598                    if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
599                        errCode = NOT_READABLE_ERR;
600                    } else if ([error code] == NSFileWriteOutOfSpaceError) {
601                        errCode = QUOTA_EXCEEDED_ERR;
602                    } else if ([error code] == NSFileWriteNoPermissionError) {
603                        errCode = NO_MODIFICATION_ALLOWED_ERR;
604                    }
605                }
606            }
607        }
608    } else {
609        // Need to copy the hard way
610        [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
611            CDVPluginResult* result = nil;
612            if (data != nil) {
613                BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
614                if (bSuccess) {
615                    // should verify it is there and of the correct type???
616                    NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
617                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
618                } else {
619                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
620                }
621            } else {
622                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
623            }
624            callback(result);
625        }];
626        return; // Async IO; return without callback.
627    }
628    if (result == nil) {
629        if (!errCode) {
630            errCode = INVALID_MODIFICATION_ERR; // Catch-all default
631        }
632        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
633    }
634    callback(result);
635}
636
637/* helper function to get the mimeType from the file extension
638 * IN:
639 *	NSString* fullPath - filename (may include path)
640 * OUT:
641 *	NSString* the mime type as type/subtype.  nil if not able to determine
642 */
643+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
644{
645    NSString* mimeType = nil;
646
647    if (fullPath) {
648        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
649        if (typeId) {
650            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
651            if (!mimeType) {
652                // special case for m4a
653                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
654                    mimeType = @"audio/mp4";
655                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
656                    mimeType = @"audio/wav";
657                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
658                    mimeType = @"text/css";
659                }
660            }
661            CFRelease(typeId);
662        }
663    }
664    return mimeType;
665}
666
667- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
668{
669    NSString *path = [self filesystemPathForURL:localURL];
670
671    NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
672    if (mimeType == nil) {
673        mimeType = @"*/*";
674    }
675    NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
676    if (start > 0) {
677        [file seekToFileOffset:start];
678    }
679
680    NSData* readData;
681    if (end < 0) {
682        readData = [file readDataToEndOfFile];
683    } else {
684        readData = [file readDataOfLength:(end - start)];
685    }
686    [file closeFile];
687
688    callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
689}
690
691- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
692{
693    NSString *path = [self filesystemPathForURL:localURL];
694    CDVPluginResult *result;
695    NSFileManager* fileMgr = [[NSFileManager alloc] init];
696
697    NSError* __autoreleasing error = nil;
698    NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
699
700    if (fileAttrs) {
701
702        // create dictionary of file info
703        NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
704
705        [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
706        [fileInfo setObject:@"" forKey:@"type"];  // can't easily get the mimetype unless create URL, send request and read response so skipping
707        [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
708
709        // Ensure that directories (and other non-regular files) report size of 0
710        unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
711        [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
712
713        NSDate* modDate = [fileAttrs fileModificationDate];
714        if (modDate) {
715            [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
716        }
717
718        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
719
720    } else {
721        // didn't get fileAttribs
722        CDVFileError errorCode = ABORT_ERR;
723        NSLog(@"error getting metadata: %@", [error localizedDescription]);
724        if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
725            errorCode = NOT_FOUND_ERR;
726        }
727        // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
728        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
729    }
730
731    callback(result);
732}
733
734@end