Replace percent escapes in file URL before using as path

Summary:
This is to fix a bug that prevents bundling of projects that contain spaces (or other special characters) in their names.

#### Reproduction steps before the fix
 1. Create a project with a space in the name:
    ![screen shot 2015-04-16 at 17 23 46](https://cloud.githubusercontent.com/assets/1121616/7176887/63af36de-e45d-11e4-9aa9-40586560b716.png)

 2. Follow the steps in `OPTION 2` for running from a bundled file, i.e. create the `main.bundle` file, add it to the project if is not there already, and uncomment `jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];`
 3. Run the application. This is what happens:

![screen shot 2015-04-16 at 17 27 48](https://cloud.githubusercontent.com/assets/1121616/7176955/f139764a-e45d-11e4-8dc8-3c13aab70828.png)

To prove that it has to do with a space in the name, refactor the project name to not contain a space:

![screen shot 2015-04-16 at 17 28 27](https://cloud.githubusercontent.com/assets/1121616/7176966/056b6c9a
Closes https://github.com/facebook/react-native/pull/876
Github Author: Herman Schaaf <hermanschaaf@gmail.com>

Test Plan: Imported from GitHub, without a `Test Plan:` line.
This commit is contained in:
Herman Schaaf
2015-04-23 10:37:09 -07:00
parent 357a54500e
commit 4242bd9c83

View File

@@ -10,47 +10,15 @@
#import "RCTJavaScriptLoader.h"
#import "RCTBridge.h"
#import "RCTInvalidating.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTConvert.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
#define CACHE_DIR @"RCTJSBundleCache"
#pragma mark - Application Engine
/**
* TODO:
* - Add window resize rotation events matching the DOM API.
* - Device pixel ration hooks.
* - Source maps.
*/
@implementation RCTJavaScriptLoader
{
__weak RCTBridge *_bridge;
}
/**
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
* engine in its own dedicated thread.
*
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don't delay the dispatch (so we can begin working in JS much
* faster.) Event handling must still be sent via a GCD dispatch, of course.
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
* TODO: We can invent a `requestAnimationFrame` and
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation.
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
*
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
@@ -61,92 +29,87 @@
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
{
NSURL *originalURL = scriptURL;
if (!scriptURL.scheme || [scriptURL isFileURL]) {
scriptURL = [RCTConvert NSURL:scriptURL.path];
}
if (scriptURL == nil) {
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
NSLocalizedDescriptionKey: @"No script URL provided"
NSLocalizedDescriptionKey: originalURL ? [NSString stringWithFormat:@"Script URL '%@' could not be found.", originalURL] : @"No script URL provided."
}];
onComplete(error);
return;
}
if ([scriptURL isFileURL]) {
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
if (![localPath hasPrefix:bundlePath]) {
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
scriptURL = [NSURL fileURLWithPath:absolutePath];
}
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
onComplete(error);
return;
}
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
onComplete(error);
return;
}
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
for (NSDictionary *err in errorDetails[@"errors"]) {
[fakeStack addObject: @{
@"methodName": err[@"description"] ?: @"",
@"file": err[@"filename"] ?: @"",
@"lineNumber": err[@"lineNumber"] ?: @0
}];
}
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": fakeStack,
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
for (NSDictionary *err in errorDetails[@"errors"]) {
[fakeStack addObject: @{
@"methodName": err[@"description"] ?: @"",
@"file": err[@"filename"] ?: @"",
@"lineNumber": err[@"lineNumber"] ?: @0
}];
}
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": fakeStack,
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
onComplete(error);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
onComplete(error);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
}];
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
}];
[task resume];
}