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