PageRenderTime 34ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/core/externals/update-engine/externals/google-toolbox-for-mac/AppKit/GTMGetURLHandler.m

http://macfuse.googlecode.com/
Objective C | 308 lines | 211 code | 19 blank | 78 comment | 29 complexity | 2b5f09eb320ffada04bc91ae45b66e30 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-2.0
  1. //
  2. // GTMGetURLHandler.m
  3. //
  4. // Copyright 2008 Google Inc.
  5. //
  6. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7. // use this file except in compliance with the License. You may obtain a copy
  8. // 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, software
  13. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. // License for the specific language governing permissions and limitations under
  16. // the License.
  17. //
  18. // Add this class to your app to have get URL handled almost automatically for
  19. // you. For each entry in your CFBundleURLTypes dictionaries, add a new
  20. // key/object pair of GTMBundleURLClass/the name of the class you want
  21. // to have handle the scheme(s).
  22. // Then have that class respond to the class method:
  23. // + (BOOL)gtm_openURL:(NSURL*)url
  24. // and voila, it will just work.
  25. // Note that in Debug mode we will do extensive testing to make sure that this
  26. // is all hooked up correctly, and will spew out to the console if we
  27. // find anything amiss.
  28. //
  29. // Example plist entry
  30. // ...
  31. //
  32. // <key>CFBundleURLTypes</key>
  33. // <array>
  34. // <dict>
  35. // <key>CFBundleURLName</key>
  36. // <string>Google Suggestion URL</string>
  37. // <key>GTMBundleURLClass</key>
  38. // <string>GoogleSuggestURLHandler</string>
  39. // <key>CFBundleURLSchemes</key>
  40. // <array>
  41. // <string>googlesuggest</string>
  42. // <string>googlesuggestextreme</string>
  43. // </array>
  44. // </dict>
  45. // </array>
  46. //
  47. //
  48. // Example implementation
  49. // @interface GoogleSuggestURLHandler
  50. // @end
  51. // @implementation GoogleSuggestURLHandler
  52. // + (BOOL)gtm_openURL:(NSURL*)url {
  53. // NSLog(@"%@", url);
  54. // }
  55. // @end
  56. #import <AppKit/AppKit.h>
  57. #import "GTMNSAppleEventDescriptor+Foundation.h"
  58. #import "GTMMethodCheck.h"
  59. static NSString *const kGTMBundleURLClassKey = @"GTMBundleURLClass";
  60. // A variety of constants Apple really should have defined somewhere to
  61. // allow the compiler to find your typos.
  62. static NSString *const kGTMCFBundleURLSchemesKey = @"CFBundleURLSchemes";
  63. static NSString *const kGTMCFBundleURLNameKey = @"CFBundleURLName";
  64. static NSString *const kGTMCFBundleTypeRoleKey = @"CFBundleTypeRole";
  65. static NSString *const kGTMCFBundleURLTypesKey = @"CFBundleURLTypes";
  66. static NSString *const kGTMCFBundleViewerRole = @"Viewer";
  67. static NSString *const kGTMCFBundleEditorRole = @"Editor";
  68. // Set this macro elsewhere is you want to force the
  69. // bundle checks on/off. They are nice for debugging
  70. // problems, but shouldn't be required in a release version
  71. // unless you are paranoid about your users messing with your
  72. // Info.plist
  73. #ifndef GTM_CHECK_BUNDLE_URL_CLASSES
  74. #define GTM_CHECK_BUNDLE_URL_CLASSES DEBUG
  75. #endif // GTM_CHECK_BUNDLE_URL_CLASSES
  76. @protocol GTMGetURLHandlerProtocol
  77. + (BOOL)gtm_openURL:(NSURL*)url;
  78. @end
  79. @interface GTMGetURLHandler : NSObject {
  80. NSArray *urlTypes_;
  81. }
  82. - (id)initWithTypes:(NSArray*)urlTypes;
  83. - (void)getUrl:(NSAppleEventDescriptor *)event
  84. withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
  85. - (void)addError:(OSStatus)error
  86. withDescription:(NSString*)string
  87. toDescriptor:(NSAppleEventDescriptor *)desc;
  88. + (id)handlerForBundle:(NSBundle *)bundle;
  89. + (void)getUrl:(NSAppleEventDescriptor *)event
  90. withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
  91. @end
  92. @implementation GTMGetURLHandler
  93. GTM_METHOD_CHECK(NSNumber, gtm_appleEventDescriptor);
  94. GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
  95. + (void)load {
  96. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  97. NSAppleEventManager *man = [NSAppleEventManager sharedAppleEventManager];
  98. [man setEventHandler:self
  99. andSelector:@selector(getUrl:withReplyEvent:)
  100. forEventClass:kInternetEventClass
  101. andEventID:kAEGetURL];
  102. [pool drain];
  103. }
  104. + (void)getUrl:(NSAppleEventDescriptor *)event
  105. withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
  106. static GTMGetURLHandler *sHandler = nil;
  107. if (!sHandler) {
  108. NSBundle *bundle = [NSBundle mainBundle];
  109. sHandler = [GTMGetURLHandler handlerForBundle:bundle];
  110. if (sHandler) {
  111. [sHandler retain];
  112. }
  113. }
  114. [sHandler getUrl:event withReplyEvent:replyEvent];
  115. }
  116. + (id)handlerForBundle:(NSBundle *)bundle {
  117. GTMGetURLHandler *handler = nil;
  118. NSArray *urlTypes
  119. = [bundle objectForInfoDictionaryKey:kGTMCFBundleURLTypesKey];
  120. if (urlTypes) {
  121. handler = [[[GTMGetURLHandler alloc] initWithTypes:urlTypes] autorelease];
  122. } else {
  123. // COV_NF_START
  124. // Hard to test it if we don't have it.
  125. _GTMDevLog(@"If you don't have CFBundleURLTypes in your plist, you may want"
  126. @" to remove GTMGetURLHandler.m from your project");
  127. // COV_NF_END
  128. }
  129. return handler;
  130. }
  131. - (id)initWithTypes:(NSArray*)urlTypes {
  132. if ((self = [super init])) {
  133. urlTypes_ = [urlTypes retain];
  134. #if GTM_CHECK_BUNDLE_URL_CLASSES
  135. // Some debug handling to check to make sure we can handle the
  136. // classes properly. We check here instead of at init in case some of the
  137. // handlers are being handled by plugins or other imported code that are
  138. // loaded after we have been initialized.
  139. NSDictionary *urlType;
  140. GTM_FOREACH_OBJECT(urlType, urlTypes_) {
  141. NSString *className = [urlType objectForKey:kGTMBundleURLClassKey];
  142. if ([className length]) {
  143. Class cls = NSClassFromString(className);
  144. if (cls) {
  145. if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
  146. _GTMDevLog(@"Class %@ for URL handler %@ "
  147. @"(URL schemes: %@) doesn't respond to openURL:",
  148. className,
  149. [urlType objectForKey:kGTMCFBundleURLNameKey],
  150. [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
  151. }
  152. } else {
  153. _GTMDevLog(@"Unable to get class %@ for URL handler %@ "
  154. @"(URL schemes: %@)",
  155. className,
  156. [urlType objectForKey:kGTMCFBundleURLNameKey],
  157. [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
  158. }
  159. } else {
  160. NSString *role = [urlType objectForKey:kGTMCFBundleTypeRoleKey];
  161. if ([role caseInsensitiveCompare:kGTMCFBundleViewerRole] == NSOrderedSame ||
  162. [role caseInsensitiveCompare:kGTMCFBundleEditorRole] == NSOrderedSame) {
  163. _GTMDevLog(@"Missing %@ for URL handler %@ "
  164. @"(URL schemes: %@)",
  165. kGTMBundleURLClassKey,
  166. [urlType objectForKey:kGTMCFBundleURLNameKey],
  167. [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
  168. }
  169. }
  170. }
  171. #endif // GTM_CHECK_BUNDLE_URL_CLASSES
  172. }
  173. return self;
  174. }
  175. // COV_NF_START
  176. // Singleton is never dealloc'd
  177. - (void)dealloc {
  178. [urlTypes_ release];
  179. [super dealloc];
  180. }
  181. // COV_NF_END
  182. - (NSURL*)extractURLFromEvent:(NSAppleEventDescriptor*)event
  183. withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
  184. NSAppleEventDescriptor *desc
  185. = [event paramDescriptorForKeyword:keyDirectObject];
  186. NSString *urlstring = [desc stringValue];
  187. NSURL *url = [NSURL URLWithString:urlstring];
  188. if (!url) {
  189. // COV_NF_START
  190. // Can't convince the OS to give me a bad URL
  191. [self addError:errAECoercionFail
  192. withDescription:@"Unable to extract url from key direct object."
  193. toDescriptor:replyEvent];
  194. // COV_NF_END
  195. }
  196. return url;
  197. }
  198. - (Class)getClassForScheme:(NSString *)scheme
  199. withReplyEvent:(NSAppleEventDescriptor*)replyEvent {
  200. NSDictionary *urlType;
  201. Class cls = nil;
  202. NSString *typeScheme = nil;
  203. GTM_FOREACH_OBJECT(urlType, urlTypes_) {
  204. NSArray *schemes = [urlType objectForKey:kGTMCFBundleURLSchemesKey];
  205. NSString *aScheme;
  206. GTM_FOREACH_OBJECT(aScheme, schemes) {
  207. if ([aScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
  208. typeScheme = aScheme;
  209. break;
  210. }
  211. }
  212. if (typeScheme) {
  213. break;
  214. }
  215. }
  216. if (typeScheme) {
  217. NSString *class = [urlType objectForKey:kGTMBundleURLClassKey];
  218. if (class) {
  219. cls = NSClassFromString(class);
  220. }
  221. if (!cls) {
  222. NSString *errorString
  223. = [NSString stringWithFormat:@"Unable to instantiate class for "
  224. @"%@:%@ for scheme:%@.",
  225. kGTMBundleURLClassKey, class, typeScheme];
  226. [self addError:errAECorruptData
  227. withDescription:errorString
  228. toDescriptor:replyEvent];
  229. } else {
  230. if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
  231. NSString *errorString
  232. = [NSString stringWithFormat:@"Class %@:%@ for scheme:%@ does not"
  233. @"respond to gtm_openURL:",
  234. kGTMBundleURLClassKey, class, typeScheme];
  235. [self addError:errAECorruptData
  236. withDescription:errorString
  237. toDescriptor:replyEvent];
  238. cls = Nil;
  239. }
  240. }
  241. } else {
  242. // COV_NF_START
  243. // Don't know how to force an URL that we don't respond to upon ourselves.
  244. NSString *errorString
  245. = [NSString stringWithFormat:@"Unable to find handler for scheme %@.",
  246. scheme];
  247. [self addError:errAECorruptData
  248. withDescription:errorString
  249. toDescriptor:replyEvent];
  250. // COV_NF_END
  251. }
  252. return cls;
  253. }
  254. - (void)getUrl:(NSAppleEventDescriptor *)event
  255. withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
  256. NSURL *url = [self extractURLFromEvent:event withReplyEvent:replyEvent];
  257. if (!url) {
  258. return;
  259. }
  260. NSString *scheme = [url scheme];
  261. Class cls = [self getClassForScheme:scheme withReplyEvent:replyEvent];
  262. if (!cls) {
  263. return;
  264. }
  265. BOOL wasGood = [cls gtm_openURL:url];
  266. if (!wasGood) {
  267. NSString *errorString
  268. = [NSString stringWithFormat:@"[%@ gtm_openURL:] failed to handle %@",
  269. NSStringFromClass(cls), url];
  270. [self addError:errAEEventNotHandled
  271. withDescription:errorString
  272. toDescriptor:replyEvent];
  273. }
  274. }
  275. - (void)addError:(OSStatus)error
  276. withDescription:(NSString*)string
  277. toDescriptor:(NSAppleEventDescriptor *)desc {
  278. NSAppleEventDescriptor *errorDesc = nil;
  279. if (error != noErr) {
  280. NSNumber *errNum = [NSNumber numberWithLong:error];
  281. errorDesc = [errNum gtm_appleEventDescriptor];
  282. [desc setParamDescriptor:errorDesc forKeyword:keyErrorNumber];
  283. }
  284. if (string) {
  285. errorDesc = [string gtm_appleEventDescriptor];
  286. [desc setParamDescriptor:errorDesc forKeyword:keyErrorString];
  287. }
  288. }
  289. @end