CDVURLProtocol.m
9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "CDVURLProtocol.h"
#import "CDVCommandQueue.h"
#import "CDVWhitelist.h"
#import "CDVViewController.h"
static CDVWhitelist* gWhitelist = nil;
// Contains a set of NSNumbers of addresses of controllers. It doesn't store
// the actual pointer to avoid retaining.
static NSMutableSet* gRegisteredControllers = nil;
NSString* const kCDVAssetsLibraryPrefixes = @"assets-library://";
// Returns the registered view controller that sent the given request.
// If the user-agent is not from a UIWebView, or if it's from an unregistered one,
// then nil is returned.
static CDVViewController *viewControllerForRequest(NSURLRequest* request)
{
// The exec bridge explicitly sets the VC address in a header.
// This works around the User-Agent not being set for file: URLs.
NSString* addrString = [request valueForHTTPHeaderField:@"vc"];
if (addrString == nil) {
NSString* userAgent = [request valueForHTTPHeaderField:@"User-Agent"];
if (userAgent == nil) {
return nil;
}
NSUInteger bracketLocation = [userAgent rangeOfString:@"(" options:NSBackwardsSearch].location;
if (bracketLocation == NSNotFound) {
return nil;
}
addrString = [userAgent substringFromIndex:bracketLocation + 1];
}
long long viewControllerAddress = [addrString longLongValue];
@synchronized(gRegisteredControllers) {
if (![gRegisteredControllers containsObject:[NSNumber numberWithLongLong:viewControllerAddress]]) {
return nil;
}
}
return (__bridge CDVViewController*)(void*)viewControllerAddress;
}
@implementation CDVURLProtocol
+ (void)registerPGHttpURLProtocol {}
+ (void)registerURLProtocol {}
// Called to register the URLProtocol, and to make it away of an instance of
// a ViewController.
+ (void)registerViewController:(CDVViewController*)viewController
{
if (gRegisteredControllers == nil) {
[NSURLProtocol registerClass:[CDVURLProtocol class]];
gRegisteredControllers = [[NSMutableSet alloc] initWithCapacity:8];
// The whitelist doesn't change, so grab the first one and store it.
gWhitelist = viewController.whitelist;
// Note that we grab the whitelist from the first viewcontroller for now - but this will change
// when we allow a registered viewcontroller to have its own whitelist (e.g InAppBrowser)
// Differentiating the requests will be through the 'vc' http header below as used for the js->objc bridge.
// The 'vc' value is generated by casting the viewcontroller object to a (long long) value (see CDVViewController::webViewDidFinishLoad)
if (gWhitelist == nil) {
// NSLog(@"WARNING: NO whitelist has been set in CDVURLProtocol.");
}
}
@synchronized(gRegisteredControllers) {
[gRegisteredControllers addObject:[NSNumber numberWithLongLong:(long long)viewController]];
}
}
+ (void)unregisterViewController:(CDVViewController*)viewController
{
@synchronized(gRegisteredControllers) {
[gRegisteredControllers removeObject:[NSNumber numberWithLongLong:(long long)viewController]];
}
}
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
NSURL* theUrl = [theRequest URL];
CDVViewController* viewController = viewControllerForRequest(theRequest);
if ([[theUrl absoluteString] hasPrefix:kCDVAssetsLibraryPrefixes]) {
return YES;
} else if (viewController != nil) {
if ([[theUrl path] isEqualToString:@"/!gap_exec"]) {
NSString* queuedCommandsJSON = [theRequest valueForHTTPHeaderField:@"cmds"];
NSString* requestId = [theRequest valueForHTTPHeaderField:@"rc"];
if (requestId == nil) {
// NSLog(@"!cordova request missing rc header");
return NO;
}
BOOL hasCmds = [queuedCommandsJSON length] > 0;
if (hasCmds) {
SEL sel = @selector(enqueueCommandBatch:);
[viewController.commandQueue performSelectorOnMainThread:sel withObject:queuedCommandsJSON waitUntilDone:NO];
[viewController.commandQueue performSelectorOnMainThread:@selector(executePending) withObject:nil waitUntilDone:NO];
} else {
SEL sel = @selector(processXhrExecBridgePoke:);
[viewController.commandQueue performSelectorOnMainThread:sel withObject:[NSNumber numberWithInteger:[requestId integerValue]] waitUntilDone:NO];
}
// Returning NO here would be 20% faster, but it spams WebInspector's console with failure messages.
// If JS->Native bridge speed is really important for an app, they should use the iframe bridge.
// Returning YES here causes the request to come through canInitWithRequest two more times.
// For this reason, we return NO when cmds exist.
return !hasCmds;
}
// we only care about http and https connections.
// CORS takes care of http: trying to access file: URLs.
if ([gWhitelist schemeIsAllowed:[theUrl scheme]]) {
// if it FAILS the whitelist, we return TRUE, so we can fail the connection later
return ![gWhitelist URLIsAllowed:theUrl];
}
}
return NO;
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
{
// NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
return request;
}
- (void)startLoading
{
// NSLog(@"%@ received %@ - start", self, NSStringFromSelector(_cmd));
NSURL* url = [[self request] URL];
if ([[url path] isEqualToString:@"/!gap_exec"]) {
[self sendResponseWithResponseCode:200 data:nil mimeType:nil];
return;
} else if ([[url absoluteString] hasPrefix:kCDVAssetsLibraryPrefixes]) {
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
if (asset) {
// We have the asset! Get the data and send it along.
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
Byte* buffer = (Byte*)malloc((unsigned long)[assetRepresentation size]);
NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:(NSUInteger)[assetRepresentation size] error:nil];
NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
[self sendResponseWithResponseCode:200 data:data mimeType:MIMEType];
} else {
// Retrieving the asset failed for some reason. Send an error.
[self sendResponseWithResponseCode:404 data:nil mimeType:nil];
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
// Retrieving the asset failed for some reason. Send an error.
[self sendResponseWithResponseCode:401 data:nil mimeType:nil];
};
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:url resultBlock:resultBlock failureBlock:failureBlock];
return;
}
NSString* body = [gWhitelist errorStringForURL:url];
[self sendResponseWithResponseCode:401 data:[body dataUsingEncoding:NSASCIIStringEncoding] mimeType:nil];
}
- (void)stopLoading
{
// do any cleanup here
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB
{
return NO;
}
- (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mimeType:(NSString*)mimeType
{
if (mimeType == nil) {
mimeType = @"text/plain";
}
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:[[self request] URL] statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:@{@"Content-Type" : mimeType}];
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
if (data != nil) {
[[self client] URLProtocol:self didLoadData:data];
}
[[self client] URLProtocolDidFinishLoading:self];
}
@end