diff --git a/Boxer/BXDriveBundleImport.m b/Boxer/BXDriveBundleImport.m index 6fea6180..f41c9653 100644 --- a/Boxer/BXDriveBundleImport.m +++ b/Boxer/BXDriveBundleImport.m @@ -126,15 +126,18 @@ - (void) main if (self.isCancelled) return; //Work out what to do with the related file paths we've parsed from the cue file - NSURL *baseURL = sourceURL.URLByDeletingLastPathComponent; NSMutableDictionary *revisedPaths = [NSMutableDictionary dictionaryWithCapacity: numRelatedPaths]; for (NSString *fromPath in relatedPaths) { - //Rewrite Windows-style paths - NSString *sanitisedFromPath = [fromPath stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]; + NSError *pathError = nil; + NSURL *fromURL = [ADBBinCueImage resourceURLForRawPath: fromPath inCueAtURL: sourceURL error: &pathError]; + if (!fromURL) + { + self.error = pathError; + return; + } - NSURL *fromURL = [baseURL URLByAppendingPathComponent: sanitisedFromPath]; NSString *fromName = fromURL.lastPathComponent; NSURL *toURL = [destinationURL URLByAppendingPathComponent: fromName]; diff --git a/Other Sources/ADBToolkit/ADBBinCueImage.h b/Other Sources/ADBToolkit/ADBBinCueImage.h index ce3fdee0..e7e6ecb2 100644 --- a/Other Sources/ADBToolkit/ADBBinCueImage.h +++ b/Other Sources/ADBToolkit/ADBBinCueImage.h @@ -38,8 +38,13 @@ NS_ASSUME_NONNULL_BEGIN /// Returns an array of the track files specified in the specified CUE, /// as absolute OS X filesystem URLs resolved relative to the CUE's location. +/// Returns @c nil and populates @c outError if any resource resolves outside the CUE's folder. + (nullable NSArray *) resourceURLsInCueAtURL: (NSURL *)cueURL error: (out NSError **)outError; +/// Returns a track file URL resolved relative to the specified CUE file. +/// Returns @c nil and populates @c outError if the resource resolves outside the CUE's folder. ++ (nullable NSURL *) resourceURLForRawPath: (NSString *)rawPath inCueAtURL: (NSURL *)cueURL error: (out NSError **)outError; + /// Returns the location of the binary image for the specified CUE file, /// as an absolute OS X filesystem URL resolved relative to the CUE's location. /// Returns @c nil if the binary image path could not be determined. diff --git a/Other Sources/ADBToolkit/ADBBinCueImage.m b/Other Sources/ADBToolkit/ADBBinCueImage.m index cfe84da0..c9144719 100644 --- a/Other Sources/ADBToolkit/ADBBinCueImage.m +++ b/Other Sources/ADBToolkit/ADBBinCueImage.m @@ -41,6 +41,26 @@ /// This is used as a sanity check by +isCueAtPath: to avoid scanning large files unnecessarily. #define ADBCueMaxFileSize 10240 +static BOOL ADBURLIsInDirectory(NSURL *URL, NSURL *directoryURL) +{ + NSString *path = URL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + NSString *directoryPath = directoryURL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + + return [path isEqualToString: directoryPath] || [path hasPrefix: [directoryPath stringByAppendingString: @"/"]]; +} + +static NSError *ADBCueResourcePathError(NSURL *cueURL, NSString *rawPath) +{ + NSString *description = [NSString stringWithFormat: @"The CUE file “%@” refers to a resource outside its folder.", cueURL.lastPathComponent]; + NSString *failureReason = [NSString stringWithFormat: @"The resource path “%@” does not resolve inside the CUE file's folder.", rawPath]; + + return [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadInvalidFileNameError + userInfo: @{ NSLocalizedDescriptionKey: description, + NSLocalizedFailureReasonErrorKey: failureReason, + NSURLErrorKey: cueURL }]; +} + @implementation ADBBinCueImage @@ -70,6 +90,28 @@ + (NSArray *) rawPathsInCueContents: (NSString *)cueContents return paths; } ++ (NSURL *) resourceURLForRawPath: (NSString *)rawPath inCueAtURL: (NSURL *)cueURL error: (out NSError **)outError +{ + NSURL *baseURL = cueURL.URLByDeletingLastPathComponent.URLByStandardizingPath; + + //Rewrite Windows-style paths + NSString *normalizedPath = [rawPath stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]; + + //Form an absolute path with all ../ components resolved. + NSURL *resourceURL = [baseURL URLByAppendingPathComponent: normalizedPath].URLByStandardizingPath; + + if (!ADBURLIsInDirectory(resourceURL, baseURL)) + { + if (outError) + { + *outError = ADBCueResourcePathError(cueURL, rawPath); + } + return nil; + } + + return resourceURL; +} + + (NSArray *) resourceURLsInCueAtURL: (NSURL *)cueURL error: (out NSError **)outError { NSString *cueContents = [[NSString alloc] initWithContentsOfURL: cueURL @@ -81,20 +123,28 @@ + (NSArray *) resourceURLsInCueAtURL: (NSURL *)cueURL error: (out NSError **)out NSArray *rawPaths = [self rawPathsInCueContents: cueContents]; - //The URL relative to which we will resolve the paths in the CUE - NSURL *baseURL = cueURL.URLByDeletingLastPathComponent; - NSMutableArray *resolvedURLs = [NSMutableArray arrayWithCapacity: rawPaths.count]; + NSError *resourceError = nil; for (NSString *rawPath in rawPaths) @autoreleasepool { - //Rewrite Windows-style paths - NSString *normalizedPath = [rawPath stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]; - - //Form an absolute path with all ../ components resolved. - NSURL *resourceURL = [baseURL URLByAppendingPathComponent: normalizedPath].URLByStandardizingPath; + NSURL *resourceURL = [self resourceURLForRawPath: rawPath inCueAtURL: cueURL error: &resourceError]; + if (!resourceURL) + { + break; + } [resolvedURLs addObject: resourceURL]; } + + if (resourceError) + { + if (outError) + { + *outError = resourceError; + } + return nil; + } + return resolvedURLs; }