iOS web browser with a focus on security and privacy
1//
2// 1Password Extension
3//
4// Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov.
5// Copyright (c) 2014 AgileBits. All rights reserved.
6//
7
8#import "OnePasswordExtension.h"
9
10// Version
11#define VERSION_NUMBER @(184)
12static NSString *const AppExtensionVersionNumberKey = @"version_number";
13
14// Available App Extension Actions
15static NSString *const kUTTypeAppExtensionFindLoginAction = @"org.appextension.find-login-action";
16static NSString *const kUTTypeAppExtensionSaveLoginAction = @"org.appextension.save-login-action";
17static NSString *const kUTTypeAppExtensionChangePasswordAction = @"org.appextension.change-password-action";
18static NSString *const kUTTypeAppExtensionFillWebViewAction = @"org.appextension.fill-webview-action";
19static NSString *const kUTTypeAppExtensionFillBrowserAction = @"org.appextension.fill-browser-action";
20
21// WebView Dictionary keys
22static NSString *const AppExtensionWebViewPageFillScript = @"fillScript";
23static NSString *const AppExtensionWebViewPageDetails = @"pageDetails";
24
25@implementation OnePasswordExtension
26
27#pragma mark - Public Methods
28
29+ (OnePasswordExtension *)sharedExtension {
30 static dispatch_once_t onceToken;
31 static OnePasswordExtension *__sharedExtension;
32
33 dispatch_once(&onceToken, ^{
34 __sharedExtension = [OnePasswordExtension new];
35 });
36
37 return __sharedExtension;
38}
39
40- (BOOL)isAppExtensionAvailable {
41 if ([self isSystemAppExtensionAPIAvailable]) {
42 return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"org-appextension-feature-password-management://"]];
43 }
44
45 return NO;
46}
47
48#pragma mark - Native app Login
49
50- (void)findLoginForURLString:(nonnull NSString *)URLString forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion {
51 NSAssert(URLString != nil, @"URLString must not be nil");
52 NSAssert(viewController != nil, @"viewController must not be nil");
53
54 if (NO == [self isSystemAppExtensionAPIAvailable]) {
55 NSLog(@"Failed to findLoginForURLString, system API is not available");
56 if (completion) {
57 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]);
58 }
59
60 return;
61 }
62
63#ifdef __IPHONE_8_0
64 NSDictionary *item = @{ AppExtensionVersionNumberKey: VERSION_NUMBER, AppExtensionURLStringKey: URLString };
65
66 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionFindLoginAction];
67 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
68 if (returnedItems.count == 0) {
69 NSError *error = nil;
70 if (activityError) {
71 NSLog(@"Failed to findLoginForURLString: %@", activityError);
72 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError];
73 }
74 else {
75 error = [OnePasswordExtension extensionCancelledByUserError];
76 }
77
78 if (completion) {
79 completion(nil, error);
80 }
81
82 return;
83 }
84
85 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) {
86 if (completion) {
87 completion(itemDictionary, error);
88 }
89 }];
90 };
91
92 [viewController presentViewController:activityViewController animated:YES completion:nil];
93#endif
94}
95
96#pragma mark - New User Registration
97
98- (void)storeLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion {
99 NSAssert(URLString != nil, @"URLString must not be nil");
100 NSAssert(viewController != nil, @"viewController must not be nil");
101
102 if (NO == [self isSystemAppExtensionAPIAvailable]) {
103 NSLog(@"Failed to storeLoginForURLString, system API is not available");
104 if (completion) {
105 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]);
106 }
107
108 return;
109 }
110
111
112#ifdef __IPHONE_8_0
113 NSMutableDictionary *newLoginAttributesDict = [NSMutableDictionary new];
114 newLoginAttributesDict[AppExtensionVersionNumberKey] = VERSION_NUMBER;
115 newLoginAttributesDict[AppExtensionURLStringKey] = URLString;
116 [newLoginAttributesDict addEntriesFromDictionary:loginDetailsDictionary];
117 if (passwordGenerationOptions.count > 0) {
118 newLoginAttributesDict[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions;
119 }
120
121 UIActivityViewController *activityViewController = [self activityViewControllerForItem:newLoginAttributesDict viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionSaveLoginAction];
122 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
123 if (returnedItems.count == 0) {
124 NSError *error = nil;
125 if (activityError) {
126 NSLog(@"Failed to storeLoginForURLString: %@", activityError);
127 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError];
128 }
129 else {
130 error = [OnePasswordExtension extensionCancelledByUserError];
131 }
132
133 if (completion) {
134 completion(nil, error);
135 }
136
137 return;
138 }
139
140 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) {
141 if (completion) {
142 completion(itemDictionary, error);
143 }
144 }];
145 };
146
147 [viewController presentViewController:activityViewController animated:YES completion:nil];
148#endif
149}
150
151#pragma mark - Change Password
152
153- (void)changePasswordForLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion {
154 NSAssert(URLString != nil, @"URLString must not be nil");
155 NSAssert(viewController != nil, @"viewController must not be nil");
156
157 if (NO == [self isSystemAppExtensionAPIAvailable]) {
158 NSLog(@"Failed to changePasswordForLoginWithUsername, system API is not available");
159 if (completion) {
160 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]);
161 }
162
163 return;
164 }
165
166#ifdef __IPHONE_8_0
167 NSMutableDictionary *item = [NSMutableDictionary new];
168 item[AppExtensionVersionNumberKey] = VERSION_NUMBER;
169 item[AppExtensionURLStringKey] = URLString;
170 [item addEntriesFromDictionary:loginDetailsDictionary];
171 if (passwordGenerationOptions.count > 0) {
172 item[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions;
173 }
174
175 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionChangePasswordAction];
176
177 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
178 if (returnedItems.count == 0) {
179 NSError *error = nil;
180 if (activityError) {
181 NSLog(@"Failed to changePasswordForLoginWithUsername: %@", activityError);
182 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError];
183 }
184 else {
185 error = [OnePasswordExtension extensionCancelledByUserError];
186 }
187
188 if (completion) {
189 completion(nil, error);
190 }
191
192 return;
193 }
194
195 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) {
196 if (completion) {
197 completion(itemDictionary, error);
198 }
199 }];
200 };
201
202 [viewController presentViewController:activityViewController animated:YES completion:nil];
203#endif
204}
205
206#pragma mark - Web View filling Support
207
208- (void)fillItemIntoWebView:(nonnull id)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
209 NSAssert(webView != nil, @"webView must not be nil");
210 NSAssert(viewController != nil, @"viewController must not be nil");
211 NSAssert([webView isKindOfClass:[UIWebView class]] || [webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView or UIWebView.");
212
213#ifdef __IPHONE_8_0
214 if ([webView isKindOfClass:[UIWebView class]]) {
215 [self fillItemIntoUIWebView:webView webViewController:viewController sender:(id)sender showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) {
216 if (completion) {
217 completion(success, error);
218 }
219 }];
220 }
221 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW
222 else if ([webView isKindOfClass:[WKWebView class]]) {
223 [self fillItemIntoWKWebView:webView forViewController:viewController sender:(id)sender showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) {
224 if (completion) {
225 completion(success, error);
226 }
227 }];
228 }
229 #endif
230#endif
231}
232
233#pragma mark - Support for custom UIActivityViewControllers
234
235- (BOOL)isOnePasswordExtensionActivityType:(nullable NSString *)activityType {
236 return [@"com.agilebits.onepassword-ios.extension" isEqualToString:activityType] || [@"com.agilebits.beta.onepassword-ios.extension" isEqualToString:activityType];
237}
238
239- (void)createExtensionItemForWebView:(nonnull id)webView completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion {
240 NSAssert(webView != nil, @"webView must not be nil");
241 NSAssert([webView isKindOfClass:[UIWebView class]] || [webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView or UIWebView.");
242
243#ifdef __IPHONE_8_0
244 if ([webView isKindOfClass:[UIWebView class]]) {
245 UIWebView *uiWebView = (UIWebView *)webView;
246 NSString *collectedPageDetails = [uiWebView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript];
247
248 [self createExtensionItemForURLString:uiWebView.request.URL.absoluteString webPageDetails:collectedPageDetails completion:completion];
249 }
250 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW
251 else if ([webView isKindOfClass:[WKWebView class]]) {
252 WKWebView *wkWebView = (WKWebView *)webView;
253 [wkWebView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *evaluateError) {
254 if (result == nil) {
255 NSLog(@"1Password Extension failed to collect web page fields: %@", evaluateError);
256 NSError *failedToCollectFieldsError = [OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:evaluateError];
257 if (completion) {
258 if ([NSThread isMainThread]) {
259 completion(nil, failedToCollectFieldsError);
260 }
261 else {
262 dispatch_async(dispatch_get_main_queue(), ^{
263 completion(nil, failedToCollectFieldsError);
264 });
265 }
266 }
267
268 return;
269 }
270
271 [self createExtensionItemForURLString:wkWebView.URL.absoluteString webPageDetails:result completion:completion];
272 }];
273 }
274 #endif
275#endif
276}
277
278- (void)fillReturnedItems:(nullable NSArray *)returnedItems intoWebView:(nonnull id)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
279 NSAssert(webView != nil, @"webView must not be nil");
280
281 if (returnedItems.count == 0) {
282 NSError *error = [OnePasswordExtension extensionCancelledByUserError];
283 if (completion) {
284 completion(NO, error);
285 }
286
287 return;
288 }
289
290 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) {
291 if (itemDictionary.count == 0) {
292 if (completion) {
293 completion(NO, error);
294 }
295
296 return;
297 }
298
299 NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript];
300 [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) {
301 if (completion) {
302 completion(success, executeFillScriptError);
303 }
304 }];
305 }];
306}
307
308#pragma mark - Private methods
309
310- (BOOL)isSystemAppExtensionAPIAvailable {
311#ifdef __IPHONE_8_0
312 return [NSExtensionItem class] != nil;
313#else
314 return NO;
315#endif
316}
317
318- (void)findLoginIn1PasswordWithURLString:(nonnull NSString *)URLString collectedPageDetails:(nullable NSString *)collectedPageDetails forWebViewController:(nonnull UIViewController *)forViewController sender:(nullable id)sender withWebView:(nonnull id)webView showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
319 if ([URLString length] == 0) {
320 NSError *URLStringError = [OnePasswordExtension failedToObtainURLStringFromWebViewError];
321 NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", URLStringError);
322 if (completion) {
323 completion(NO, URLStringError);
324 }
325 return;
326 }
327
328 NSError *jsonError = nil;
329 NSData *data = [collectedPageDetails dataUsingEncoding:NSUTF8StringEncoding];
330 NSDictionary *collectedPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
331
332 if (collectedPageDetailsDictionary.count == 0) {
333 NSLog(@"Failed to parse JSON collected page details: %@", jsonError);
334 if (completion) {
335 completion(NO, jsonError);
336 }
337 return;
338 }
339
340 NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : collectedPageDetailsDictionary };
341
342 NSString *typeIdentifier = yesOrNo ? kUTTypeAppExtensionFillWebViewAction : kUTTypeAppExtensionFillBrowserAction;
343 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:forViewController sender:sender typeIdentifier:typeIdentifier];
344 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
345 if (returnedItems.count == 0) {
346 NSError *error = nil;
347 if (activityError) {
348 NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", activityError);
349 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError];
350 }
351 else {
352 error = [OnePasswordExtension extensionCancelledByUserError];
353 }
354
355 if (completion) {
356 completion(NO, error);
357 }
358
359 return;
360 }
361
362 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *processExtensionItemError) {
363 if (itemDictionary.count == 0) {
364 if (completion) {
365 completion(NO, processExtensionItemError);
366 }
367
368 return;
369 }
370
371 NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript];
372 [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) {
373 if (completion) {
374 completion(success, executeFillScriptError);
375 }
376 }];
377 }];
378 };
379
380 [forViewController presentViewController:activityViewController animated:YES completion:nil];
381}
382
383#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW
384- (void)fillItemIntoWKWebView:(nonnull WKWebView *)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
385 [webView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *error) {
386 if (result == nil) {
387 NSLog(@"1Password Extension failed to collect web page fields: %@", error);
388 if (completion) {
389 completion(NO,[OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:error]);
390 }
391
392 return;
393 }
394
395 [self findLoginIn1PasswordWithURLString:webView.URL.absoluteString collectedPageDetails:result forWebViewController:viewController sender:sender withWebView:webView showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *findLoginError) {
396 if (completion) {
397 completion(success, findLoginError);
398 }
399 }];
400 }];
401}
402#endif
403
404- (void)fillItemIntoUIWebView:(nonnull UIWebView *)webView webViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
405 NSString *collectedPageDetails = [webView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript];
406 [self findLoginIn1PasswordWithURLString:webView.request.URL.absoluteString collectedPageDetails:collectedPageDetails forWebViewController:viewController sender:sender withWebView:webView showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) {
407 if (completion) {
408 completion(success, error);
409 }
410 }];
411}
412
413- (void)executeFillScript:(NSString * __nullable)fillScript inWebView:(nonnull id)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
414
415 if (fillScript == nil) {
416 NSLog(@"Failed to executeFillScript, fillScript is missing");
417 if (completion) {
418 completion(NO, [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script is missing", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:nil]);
419 }
420
421 return;
422 }
423
424 NSMutableString *scriptSource = [OPWebViewFillScript mutableCopy];
425 [scriptSource appendFormat:@"(document, %@, undefined);", fillScript];
426
427#ifdef __IPHONE_8_0
428 if ([webView isKindOfClass:[UIWebView class]]) {
429 NSString *result = [((UIWebView *)webView) stringByEvaluatingJavaScriptFromString:scriptSource];
430 BOOL success = (result != nil);
431 NSError *error = nil;
432
433 if (!success) {
434 NSLog(@"Cannot executeFillScript, stringByEvaluatingJavaScriptFromString failed");
435 error = [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script could not be evaluated", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:nil];
436 }
437
438 if (completion) {
439 completion(success, error);
440 }
441 }
442
443 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW
444 else if ([webView isKindOfClass:[WKWebView class]]) {
445 [((WKWebView *)webView) evaluateJavaScript:scriptSource completionHandler:^(NSString *result, NSError *evaluationError) {
446 BOOL success = (result != nil);
447 NSError *error = nil;
448
449 if (!success) {
450 NSLog(@"Cannot executeFillScript, evaluateJavaScript failed: %@", evaluationError);
451 error = [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script could not be evaluated", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:error];
452 }
453
454 if (completion) {
455 completion(success, error);
456 }
457 }];
458 }
459 #endif
460#endif
461}
462
463#ifdef __IPHONE_8_0
464- (void)processExtensionItem:(nullable NSExtensionItem *)extensionItem completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion {
465 if (extensionItem.attachments.count == 0) {
466 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item had no attachments." };
467 NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo];
468 if (completion) {
469 completion(nil, error);
470 }
471 return;
472 }
473
474 NSItemProvider *itemProvider = extensionItem.attachments.firstObject;
475 if (NO == [itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePropertyList]) {
476 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item attachment does not conform to kUTTypePropertyList type identifier" };
477 NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo];
478 if (completion) {
479 completion(nil, error);
480 }
481 return;
482 }
483
484
485 [itemProvider loadItemForTypeIdentifier:(__bridge NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *itemDictionary, NSError *itemProviderError) {
486 NSError *error = nil;
487 if (itemDictionary.count == 0) {
488 NSLog(@"Failed to loadItemForTypeIdentifier: %@", itemProviderError);
489 error = [OnePasswordExtension failedToLoadItemProviderDataErrorWithUnderlyingError:itemProviderError];
490 }
491
492 if (completion) {
493 if ([NSThread isMainThread]) {
494 completion(itemDictionary, error);
495 }
496 else {
497 dispatch_async(dispatch_get_main_queue(), ^{
498 completion(itemDictionary, error);
499 });
500 }
501 }
502 }];
503}
504
505- (UIActivityViewController *)activityViewControllerForItem:(nonnull NSDictionary *)item viewController:(nonnull UIViewController*)viewController sender:(nullable id)sender typeIdentifier:(nonnull NSString *)typeIdentifier {
506#ifdef __IPHONE_8_0
507 NSAssert(NO == (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && sender == nil), @"sender must not be nil on iPad.");
508
509 NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:typeIdentifier];
510
511 NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];
512 extensionItem.attachments = @[ itemProvider ];
513
514 UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[ extensionItem ] applicationActivities:nil];
515
516 if ([sender isKindOfClass:[UIBarButtonItem class]]) {
517 controller.popoverPresentationController.barButtonItem = sender;
518 }
519 else if ([sender isKindOfClass:[UIView class]]) {
520 controller.popoverPresentationController.sourceView = [sender superview];
521 controller.popoverPresentationController.sourceRect = [sender frame];
522 }
523 else {
524 NSLog(@"sender can be nil on iPhone");
525 }
526
527 return controller;
528#else
529 return nil;
530#endif
531}
532
533#endif
534
535- (void)createExtensionItemForURLString:(nonnull NSString *)URLString webPageDetails:(nullable NSString *)webPageDetails completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion {
536 NSError *jsonError = nil;
537 NSData *data = [webPageDetails dataUsingEncoding:NSUTF8StringEncoding];
538 NSDictionary *webPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
539
540 if (webPageDetailsDictionary.count == 0) {
541 NSLog(@"Failed to parse JSON collected page details: %@", jsonError);
542 if (completion) {
543 completion(nil, jsonError);
544 }
545 return;
546 }
547
548 NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : webPageDetailsDictionary };
549
550 NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:kUTTypeAppExtensionFillBrowserAction];
551
552 NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];
553 extensionItem.attachments = @[ itemProvider ];
554
555 if (completion) {
556 if ([NSThread isMainThread]) {
557 completion(extensionItem, nil);
558 }
559 else {
560 dispatch_async(dispatch_get_main_queue(), ^{
561 completion(extensionItem, nil);
562 });
563 }
564 }
565}
566
567#pragma mark - Errors
568
569+ (NSError *)systemAppExtensionAPINotAvailableError {
570 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"App Extension API is not available in this version of iOS", @"OnePasswordExtension", @"1Password Extension Error Message") };
571 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeAPINotAvailable userInfo:userInfo];
572}
573
574
575+ (NSError *)extensionCancelledByUserError {
576 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"1Password Extension was cancelled by the user", @"OnePasswordExtension", @"1Password Extension Error Message") };
577 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCancelledByUser userInfo:userInfo];
578}
579
580+ (NSError *)failedToContactExtensionErrorWithActivityError:(nullable NSError *)activityError {
581 NSMutableDictionary *userInfo = [NSMutableDictionary new];
582 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to contact the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message");
583 if (activityError) {
584 userInfo[NSUnderlyingErrorKey] = activityError;
585 }
586
587 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToContactExtension userInfo:userInfo];
588}
589
590+ (NSError *)failedToCollectFieldsErrorWithUnderlyingError:(nullable NSError *)underlyingError {
591 NSMutableDictionary *userInfo = [NSMutableDictionary new];
592 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to execute script that collects web page information", @"OnePasswordExtension", @"1Password Extension Error Message");
593 if (underlyingError) {
594 userInfo[NSUnderlyingErrorKey] = underlyingError;
595 }
596
597 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCollectFieldsScriptFailed userInfo:userInfo];
598}
599
600+ (NSError *)failedToFillFieldsErrorWithLocalizedErrorMessage:(nullable NSString *)errorMessage underlyingError:(nullable NSError *)underlyingError {
601 NSMutableDictionary *userInfo = [NSMutableDictionary new];
602 if (errorMessage) {
603 userInfo[NSLocalizedDescriptionKey] = errorMessage;
604 }
605 if (underlyingError) {
606 userInfo[NSUnderlyingErrorKey] = underlyingError;
607 }
608
609 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFillFieldsScriptFailed userInfo:userInfo];
610}
611
612+ (NSError *)failedToLoadItemProviderDataErrorWithUnderlyingError:(nullable NSError *)underlyingError {
613 NSMutableDictionary *userInfo = [NSMutableDictionary new];
614 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to parse information returned by 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message");
615 if (underlyingError) {
616 userInfo[NSUnderlyingErrorKey] = underlyingError;
617 }
618
619 return [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToLoadItemProviderData userInfo:userInfo];
620}
621
622+ (NSError *)failedToObtainURLStringFromWebViewError {
623 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"Failed to obtain URL String from web view. The web view must be loaded completely when calling the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message") };
624 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToObtainURLStringFromWebView userInfo:userInfo];
625}
626
627#pragma mark - WebView field collection and filling scripts
628
629static NSString *const OPWebViewCollectFieldsScript = @";(function(document, undefined) {\
630var isFirefox = false, isChrome = false, isSafari = true;\
631\
632 document.elementsByOPID={};document.addEventListener('input',function(b){!1!==b.a&&'input'===b.target.tagName.toLowerCase()&&(b.target.dataset['com.agilebits.onepassword.userEdited']='yes')},!0);\
633function q(b,d){function f(a,e){var c=a[e];if('string'==typeof c)return c;c=a.getAttribute(e);return'string'==typeof c?c:null}function h(a,e){if(-1===['text','password'].indexOf(e.type.toLowerCase())||!(m.test(a.value)||m.test(a.htmlID)||m.test(a.htmlName)||m.test(a.placeholder)||m.test(a['label-tag'])||m.test(a['label-data'])||m.test(a['label-aria'])))return!1;if(!a.visible)return!0;if('password'==e.type.toLowerCase())return!1;var c=e.type;v(e,!0);return c!==e.type}function n(a){switch(p(a.type)){case 'checkbox':return a.checked?\
634'✓':'';case 'hidden':a=a.value;if(!a||'number'!=typeof a.length)return'';254<a.length&&(a=a.substr(0,254)+'...SNIPPED');return a;default:return a.value}}function l(a){return a.options?(a=Array.prototype.slice.call(a.options).map(function(a){var c=a.text,c=c?p(c).replace(/\\s/mg,'').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\/?]/mg,''):null;return[c?c:null,a.value]}),{options:a}):null}function r(a){var e;for(a=a.parentElement||a.parentNode;a&&'td'!=p(a.tagName);)a=a.parentElement||a.parentNode;if(!a||\
635void 0===a)return null;e=a.parentElement||a.parentNode;if('tr'!=e.tagName.toLowerCase())return null;e=e.previousElementSibling;if(!e||'tr'!=(e.tagName+'').toLowerCase()||e.cells&&a.cellIndex>=e.cells.length)return null;a=e.cells[a.cellIndex];a=a.textContent||a.innerText;return a=x(a)}function s(a){var e,c=[];if(a.labels&&a.labels.length&&0<a.labels.length)c=Array.prototype.slice.call(a.labels);else{a.id&&(c=c.concat(Array.prototype.slice.call(w(b,'label[for='+JSON.stringify(a.id)+']'))));if(a.name){e=\
636w(b,'label[for='+JSON.stringify(a.name)+']');for(var f=0;f<e.length;f++)-1===c.indexOf(e[f])&&c.push(e[f])}for(e=a;e&&e!=b;e=e.parentNode)'label'===p(e.tagName)&&-1===c.indexOf(e)&&c.push(e)}0===c.length&&(e=a.parentNode,'dd'===e.tagName.toLowerCase()&&null!==e.previousElementSibling&&'dt'===e.previousElementSibling.tagName.toLowerCase()&&c.push(e.previousElementSibling));return 0<c.length?c.map(function(a){return(a.textContent||a.innerText).replace(/^\\s+/,'').replace(/\\s+$/,'').replace('\\n','').replace(/\\s{2,}/,\
637' ')}).join(''):null}function g(a,e,c,b){void 0!==b&&b===c||null===c||void 0===c||(a[e]=c)}function p(a){return'string'===typeof a?a.toLowerCase():(''+a).toLowerCase()}function w(a,b){var c=[];try{c=a.querySelectorAll(b)}catch(f){}return c}var t=b.defaultView?b.defaultView:window,m=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)','i'),u=Array.prototype.slice.call(w(b,'form')).map(function(a,b){var c={},d='__form__'+\
638b;a.opid=d;c.opid=d;g(c,'htmlName',f(a,'name'));g(c,'htmlID',f(a,'id'));d=f(a,'action');d=new URL(d,window.location.href);g(c,'htmlAction',d?d.href:null);g(c,'htmlMethod',f(a,'method'));return c}),E=Array.prototype.slice.call(y(b)).map(function(a,e){var c={},d='__'+e,k=-1==a.maxLength?999:a.maxLength;if(!k||'number'===typeof k&&isNaN(k))k=999;b.elementsByOPID[d]=a;a.opid=d;c.opid=d;c.elementNumber=e;g(c,'maxLength',Math.min(k,999),999);c.visible=z(a);c.viewable=A(a);g(c,'htmlID',f(a,'id'));g(c,'htmlName',\
639f(a,'name'));g(c,'htmlClass',f(a,'class'));g(c,'tabindex',f(a,'tabindex'));g(c,'title',f(a,'title'));g(c,'userEdited',!!a.dataset['com.agilebits.onepassword.userEdited']);if('hidden'!=p(a.type)){g(c,'label-tag',s(a));g(c,'label-data',f(a,'data-label'));g(c,'label-aria',f(a,'aria-label'));g(c,'label-top',r(a));d=[];for(k=a;k&&k.nextSibling;){k=k.nextSibling;if(B(k))break;C(d,k)}g(c,'label-right',d.join(''));d=[];D(a,d);d=d.reverse().join('');g(c,'label-left',d);g(c,'placeholder',f(a,'placeholder'))}g(c,\
640'rel',f(a,'rel'));g(c,'type',p(f(a,'type')));g(c,'value',n(a));g(c,'checked',a.checked,!1);g(c,'autoCompleteType',a.getAttribute('x-autocompletetype')||a.getAttribute('autocompletetype')||a.getAttribute('autocomplete'),'off');g(c,'disabled',a.disabled);g(c,'readonly',a.b||a.readOnly);g(c,'selectInfo',l(a));g(c,'aria-hidden','true'==a.getAttribute('aria-hidden'),!1);g(c,'aria-disabled','true'==a.getAttribute('aria-disabled'),!1);g(c,'aria-haspopup','true'==a.getAttribute('aria-haspopup'),!1);g(c,'data-unmasked',\
641a.dataset.unmasked);g(c,'data-stripe',f(a,'data-stripe'));g(c,'onepasswordFieldType',a.dataset.onepasswordFieldType||a.type);g(c,'onepasswordDesignation',a.dataset.onepasswordDesignation);g(c,'onepasswordSignInUrl',a.dataset.onepasswordSignInUrl);g(c,'onepasswordSectionTitle',a.dataset.onepasswordSectionTitle);g(c,'onepasswordSectionFieldKind',a.dataset.onepasswordSectionFieldKind);g(c,'onepasswordSectionFieldTitle',a.dataset.onepasswordSectionFieldTitle);g(c,'onepasswordSectionFieldValue',a.dataset.onepasswordSectionFieldValue);\
642a.form&&(c.form=f(a.form,'opid'));g(c,'fakeTested',h(c,a),!1);return c});E.filter(function(a){return a.fakeTested}).forEach(function(a){var e=b.elementsByOPID[a.opid];e.getBoundingClientRect();var c=e.value;!e||e&&'function'!==typeof e.click||e.click();v(e,!1);e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));e.value!==c&&(e.value=c);e.click&&e.click();a.postFakeTestVisible=z(e);a.postFakeTestViewable=A(e);a.postFakeTestType=e.type;a=e.value;var c=e.ownerDocument.createEvent('HTMLEvents'),\
643d=e.ownerDocument.createEvent('HTMLEvents');e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));d.initEvent('input',!0,!0);e.dispatchEvent(d);c.initEvent('change',!0,!0);e.dispatchEvent(c);e.blur();e.value!==a&&(e.value=a)});t={documentUUID:d,title:b.title,url:t.location.href,documentUrl:b.location.href,tabUrl:t.location.href,forms:function(a){var b={};a.forEach(function(a){b[a.opid]=a});return b}(u),fields:E,collectedTimestamp:(new Date).getTime()};(u=document.querySelector('[data-onepassword-title]'))&&\
644u.dataset[DISPLAY_TITLE_ATTRIBUE]&&(t.displayTitle=u.dataset.onepasswordTitle);return t};document.elementForOPID=G;function F(b,d){var f;isFirefox?(f=document.createEvent('KeyboardEvent'),f.initKeyEvent(d,!0,!1,null,!1,!1,!1,!1,0,0)):(f=b.ownerDocument.createEvent('Events'),f.initEvent(d,!0,!1),f.charCode=0,f.keyCode=0,f.which=0,f.srcElement=b,f.target=b);return f}window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','登录','लॉग इन करें','change password'];\
645window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');\
646window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];function x(b){var d=null;b&&(d=b.replace(/^\\s+|\\s+$|\\r?\\n.*$/mg,''),d=0<d.length?d:null);return d}function C(b,d){var f;f='';3===d.nodeType?f=d.nodeValue:1===d.nodeType&&(f=d.textContent||d.innerText);(f=x(f))&&b.push(f)}\
647function B(b){var d;b&&void 0!==b?(d='select option input form textarea button table iframe body head script'.split(' '),b?(b=b?(b.tagName||'').toLowerCase():'',d=d.constructor==Array?0<=d.indexOf(b):b===d):d=!1):d=!0;return d}\
648function D(b,d,f){var h;for(f||(f=0);b&&b.previousSibling;){b=b.previousSibling;if(B(b))return;C(d,b)}if(b&&0===d.length){for(h=null;!h;){b=b.parentElement||b.parentNode;if(!b)return;for(h=b.previousSibling;h&&!B(h)&&h.lastChild;)h=h.lastChild}B(h)||(C(d,h),0===d.length&&D(h,d,f+1))}}\
649function z(b){var d=b;b=(b=b.ownerDocument)?b.defaultView:{};for(var f;d&&d!==document;){f=b.getComputedStyle?b.getComputedStyle(d,null):d.style;if(!f)return!0;if('none'===f.display||'hidden'==f.visibility)return!1;d=d.parentNode}return d===document}\
650function A(b){var d=b.ownerDocument.documentElement,f=b.getBoundingClientRect(),h=d.scrollWidth,n=d.scrollHeight,l=f.left-d.clientLeft,d=f.top-d.clientTop,r;if(!z(b)||!b.offsetParent||10>b.clientWidth||10>b.clientHeight)return!1;var s=b.getClientRects();if(0===s.length)return!1;for(var g=0;g<s.length;g++)if(r=s[g],r.left>h||0>r.right)return!1;if(0>l||l>h||0>d||d>n)return!1;for(f=b.ownerDocument.elementFromPoint(l+(f.right>window.innerWidth?(window.innerWidth-l)/2:f.width/2),d+(f.bottom>window.innerHeight?\
651(window.innerHeight-d)/2:f.height/2));f&&f!==b&&f!==document;){if(f.tagName&&'string'===typeof f.tagName&&'label'===f.tagName.toLowerCase()&&b.labels&&0<b.labels.length)return 0<=Array.prototype.slice.call(b.labels).indexOf(f);f=f.parentNode}return f===b}\
652function G(b){var d;if(void 0===b||null===b)return null;try{var f=Array.prototype.slice.call(y(document)),h=f.filter(function(d){return d.opid==b});if(0<h.length)d=h[0],1<h.length&&console.warn('More than one element found with opid '+b);else{var n=parseInt(b.split('__')[1],10);isNaN(n)||(d=f[n])}}catch(l){console.error('An unexpected error occurred: '+l)}finally{return d}};function y(b){var d=[];try{d=b.querySelectorAll('input, select, button')}catch(f){}return d}function v(b,d){if(d){var f=b.value;b.focus();b.value!==f&&(b.value=f)}else b.focus()};\
653 \
654 return JSON.stringify(q(document, 'oneshotUUID'));\
655})(document);\
656\
657";
658
659static NSString *const OPWebViewFillScript = @";(function(document, fillScript, undefined) {\
660var isFirefox = false, isChrome = false, isSafari = true;\
661\
662 var g=!0,k=!0;\
663function n(a){var b=null;return a?0===a.indexOf('https://')&&'http:'===document.location.protocol&&(b=document.querySelectorAll('input[type=password]'),0<b.length&&(confirmResult=confirm('1Password warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'),0==confirmResult))?!0:!1:!1}\
664function m(a){var b,c=[],d=a.properties,e=1,h,f=[];d&&d.delay_between_operations&&(e=d.delay_between_operations);if(!n(a.savedURL)){h=function(a,b){var d=a[0];if(void 0===d)b();else{if('delay'===d.operation||'delay'===d[0])e=d.parameters?d.parameters[0]:d[1];else{if(d=p(d))for(var l=0;l<d.length;l++)-1===f.indexOf(d[l])&&f.push(d[l]);c=c.concat(f.map(function(a){return a&&a.hasOwnProperty('opid')?a.opid:null}))}setTimeout(function(){h(a.slice(1),b)},e)}};if(b=a.options)b.hasOwnProperty('animate')&&\
665(k=b.animate),b.hasOwnProperty('markFilling')&&(g=b.markFilling);a.itemType&&'fillPassword'===a.itemType&&(g=!1);a.hasOwnProperty('script')&&(b=a.script,h(b,function(){a.hasOwnProperty('autosubmit')&&'function'==typeof autosubmit&&(a.itemType&&'fillLogin'!==a.itemType||(0<f.length?setTimeout(function(){autosubmit(a.autosubmit,d.allow_clicky_autosubmit,f)},AUTOSUBMIT_DELAY):DEBUG_AUTOSUBMIT&&console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ',f)));'object'==typeof protectedGlobalPage&&\
666protectedGlobalPage.b('fillItemResults',{documentUUID:documentUUID,fillContextIdentifier:a.fillContextIdentifier,usedOpids:c},function(){fillingItemType=null})}))}}var x={fill_by_opid:q,fill_by_query:r,click_on_opid:s,click_on_query:t,touch_all_fields:u,simple_set_value_by_query:v,focus_by_opid:w,delay:null};\
667function p(a){var b;if(a.hasOwnProperty('operation')&&a.hasOwnProperty('parameters'))b=a.operation,a=a.parameters;else if('[object Array]'===Object.prototype.toString.call(a))b=a[0],a=a.splice(1);else return null;return x.hasOwnProperty(b)?x[b].apply(this,a):null}function q(a,b){var c;return(c=y(a))?(z(c,b),[c]):null}function r(a,b){var c;c=A(a);return Array.prototype.map.call(Array.prototype.slice.call(c),function(a){z(a,b);return a},this)}\
668function v(a,b){var c,d=[];c=A(a);Array.prototype.forEach.call(Array.prototype.slice.call(c),function(a){a.disabled||a.a||a.readOnly||void 0===a.value||(a.value=b,d.push(a))});return d}function w(a){if(a=y(a))'function'===typeof a.click&&a.click(),'function'===typeof a.focus&&B(a,!0);return null}function s(a){return(a=y(a))?C(a)?[a]:null:null}\
669function t(a){a=A(a);return Array.prototype.map.call(Array.prototype.slice.call(a),function(a){C(a);'function'===typeof a.click&&a.click();'function'===typeof a.focus&&B(a,!0);return[a]},this)}function u(){D()};var E={'true':!0,y:!0,1:!0,yes:!0,'✓':!0},F=200;function z(a,b){var c;if(a&&null!==b&&void 0!==b&&!(a.disabled||a.a||a.readOnly))switch(g&&a.form&&!a.form.opfilled&&(a.form.opfilled=!0),a.type?a.type.toLowerCase():null){case 'checkbox':c=b&&1<=b.length&&E.hasOwnProperty(b.toLowerCase())&&!0===E[b.toLowerCase()];a.checked===c||G(a,function(a){a.checked=c});break;case 'radio':!0===E[b.toLowerCase()]&&a.click();break;default:a.value==b||G(a,function(a){a.value=b})}}\
670function G(a,b){H(a);b(a);I(a);J(a)&&(a.className+=' com-agilebits-onepassword-extension-animated-fill',setTimeout(function(){a&&a.className&&(a.className=a.className.replace(/(\\s)?com-agilebits-onepassword-extension-animated-fill/,''))},F))};document.elementForOPID=y;function K(a,b){var c;isFirefox?(c=document.createEvent('KeyboardEvent'),c.initKeyEvent(b,!0,!1,null,!1,!1,!1,!1,0,0)):(c=a.ownerDocument.createEvent('Events'),c.initEvent(b,!0,!1),c.charCode=0,c.keyCode=0,c.which=0,c.srcElement=a,c.target=a);return c}function H(a){var b=a.value;C(a);B(a,!1);a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));a.value!==b&&(a.value=b)}\
671function I(a){var b=a.value,c=a.ownerDocument.createEvent('HTMLEvents'),d=a.ownerDocument.createEvent('HTMLEvents');a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));d.initEvent('input',!0,!0);a.dispatchEvent(d);c.initEvent('change',!0,!0);a.dispatchEvent(c);a.blur();a.value!==b&&(a.value=b)}function C(a){if(!a||a&&'function'!==typeof a.click)return!1;a.click();return!0}\
672function L(){var a=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)','i');return Array.prototype.slice.call(A(\"input[type='text']\")).filter(function(b){return b.value&&a.test(b.value)},this)}function D(){L().forEach(function(a){H(a);a.click&&a.click();I(a)})}\
673window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','登录','लॉग इन करें','change password'];window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');\
674window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];\
675function J(a){var b;if(b=k)a:{b=a;for(var c=a.ownerDocument,c=c?c.defaultView:{},d;b&&b!==document;){d=c.getComputedStyle?c.getComputedStyle(b,null):b.style;if(!d){b=!0;break a}if('none'===d.display||'hidden'==d.visibility){b=!1;break a}b=b.parentNode}b=b===document}return b?-1!=='email text password number tel url'.split(' ').indexOf(a.type||''):!1}\
676function y(a){var b;if(void 0===a||null===a)return null;try{var c=Array.prototype.slice.call(A('input, select, button')),d=c.filter(function(b){return b.opid==a});if(0<d.length)b=d[0],1<d.length&&console.warn('More than one element found with opid '+a);else{var e=parseInt(a.split('__')[1],10);isNaN(e)||(b=c[e])}}catch(h){console.error('An unexpected error occurred: '+h)}finally{return b}};function A(a){var b=document,c=[];try{c=b.querySelectorAll(a)}catch(d){}return c}function B(a,b){if(b){var c=a.value;a.focus();a.value!==c&&(a.value=c)}else a.focus()};\
677\
678 m(fillScript);\
679 return JSON.stringify({'success': true});\
680})\
681\
682";
683
684
685#pragma mark - Deprecated methods
686
687/*
688 Deprecated in version 1.5
689 Use fillItemIntoWebView:forViewController:sender:showOnlyLogins:completion: instead
690 */
691- (void)fillLoginIntoWebView:(nonnull id)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordSuccessCompletionBlock)completion {
692 [self fillItemIntoWebView:webView forViewController:viewController sender:sender showOnlyLogins:YES completion:completion];
693}
694
695@end