Skip to content

Instantly share code, notes, and snippets.

@ohswift
Created November 11, 2021 10:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ohswift/fb8a0ffd68335b95a5b59bc96e1856a1 to your computer and use it in GitHub Desktop.
Save ohswift/fb8a0ffd68335b95a5b59bc96e1856a1 to your computer and use it in GitHub Desktop.
iOS 14剪切板的ProbableWebURL逻辑
//
// PBProbableWebDataDetective.m
// MyPasteboard
//
// Created by xxx on 2021/7/21.
//
#import "PBProbableWebDataDetective.h"
#import <CFNetwork/CFNetwork.h>
//#import <UIKit/UIKit.h>
static NSString *const _UIPasteboardDetectionPatternProbableWebURL = @"com.apple.uikit.pasteboard-detection-pattern.probable-web-url";
static NSString *const _UIPasteboardDetectionPatternProbableWebSearch = @"com.apple.uikit.pasteboard-detection-pattern.probable-web-search";
static NSString *const _UIPasteboardDetectionPatternNumber = @"com.apple.uikit.pasteboard-detection-pattern.number";
int sub_100011D1C(NSString *value);
extern "C" {
extern BOOL _CFHostIsDomainTopLevel(NSString *host); // 判断是否顶级域名,如.com、.ac.uk
}
@interface NSString (WebNSURLExtras)
-(BOOL)_webkit_looksLikeAbsoluteURL; // 可以参考下面的实现,demo直接用Foundation的私有接口
-(BOOL)_web_looksLikeIPAddress; // 先不翻译,demo直接用Foundation的私有接口
@end
@implementation NSString (WebNSURLExtras)
-(NSString *)_webkit_stringByTrimmingWhitespace2
{
NSMutableString *trimmed = [self mutableCopy];
CFStringTrimWhitespace((__bridge CFMutableStringRef)trimmed);
return trimmed;
}
-(NSRange)_webkit_rangeOfURLScheme2
{
NSRange colon = [self rangeOfString:@":"];
if (colon.location != NSNotFound && colon.location > 0) {
NSRange scheme = {0, colon.location};
static NSCharacterSet *InverseSchemeCharacterSet = nil;
if (!InverseSchemeCharacterSet) {
NSString *acceptableCharacters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+.-";
InverseSchemeCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:acceptableCharacters] invertedSet];
}
NSRange illegals = [self rangeOfCharacterFromSet:InverseSchemeCharacterSet options:0 range:scheme];
if (illegals.location == NSNotFound)
return scheme;
}
return NSMakeRange(NSNotFound, 0);
}
-(BOOL)_webkit_looksLikeAbsoluteURL2
{
// Trim whitespace because _web_URLWithString allows whitespace.
return [[self _webkit_stringByTrimmingWhitespace2] _webkit_rangeOfURLScheme2].location != NSNotFound;
}
- (BOOL)_web_looksLikeIPAddress2
{
return YES;
}
@end
@interface NSString (safari)
- (NSString *)safari_stringByRemovingUnnecessaryCharactersFromUserTypedURLString;
- (NSString *)safari_possibleTopLevelDomainCorrectionForUserTypedString;
- (NSString *)safari_highLevelDomainFromHost;
- (NSString *)safari_topLevelDomainUsingCFFromComponents:(NSArray *)comp;
- (void)safari_reverseEnumerateComponents:(NSArray *)array usingBlock:(void (^)(NSString *,int,int*))block;
@end
@implementation NSString(safari)
- (NSString *)safari_stringByRemovingUnnecessaryCharactersFromUserTypedURLString
{
NSMutableString *v6 = [[self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
[v6 replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0, [v6 length])];
[v6 replaceOccurrencesOfString:@"\r" withString:@"" options:0 range:NSMakeRange(0, [v6 length])];
if ([v6 length] && [v6 characterAtIndex:0] == '<')
{
[v6 deleteCharactersInRange:NSMakeRange(0, 1)];
NSUInteger v7 = [@"URL:" length];
if ([v6 length] >= v7 && ![v6 compare:@"URL:" options:1 range:NSMakeRange(0, v7)]) {
[v6 deleteCharactersInRange:NSMakeRange(0, v7)];
}
NSUInteger v9 = [v6 length];
if (v9) {
if ([v6 characterAtIndex:v9-1] == '>') {
[v6 deleteCharactersInRange:NSMakeRange(v9-1, 1)];
}
}
}
return v6;
}
- (NSString *)safari_possibleTopLevelDomainCorrectionForUserTypedString
{
static NSDictionary *qword_100022998 = nil;
if (!qword_100022998) {
qword_100022998 = [[NSDictionary alloc] initWithObjectsAndKeys:@".com",@".cmo",@".com",@"c.om",
@".com",@".xom",@".net",@".ent",
@".net",@".ent",@".net",@".nte",
@".org",@".ogr",@".org",@".rog",
@".info",@".ifno",@".info",@".ifnp",nil];
}
NSUInteger v23 = [self length];
BOOL v3 = [self hasSuffix:@"."];
v23 -= v3;
for (NSString *key in qword_100022998) {
NSRange v9 = [self rangeOfString:key options:13 range:NSMakeRange(0, v23)];
if (v9.location != NSNotFound) {
return [self stringByReplacingCharactersInRange:v9 withString:key];
}
}
return nil;
}
- (NSString *)safari_highLevelDomainFromHost
{
if (![self length]) return nil;
if ([self _web_looksLikeIPAddress]) {
return self;
}
NSArray *array = [self componentsSeparatedByString:@"."];
return [self safari_topLevelDomainUsingCFFromComponents:array];
}
- (NSString *)safari_topLevelDomainUsingCFFromComponents:(NSArray *)comp
{
__block NSString *topLevelDomain = nil;
__block BOOL isTopLevelDomain = NO;
[self safari_reverseEnumerateComponents:comp usingBlock:^(NSString *host, int index, int *stop) {
BOOL isHost = _CFHostIsDomainTopLevel(host);
topLevelDomain = host;
if (isHost == YES) {
isTopLevelDomain = YES;
}
else {
if (isTopLevelDomain == YES) {
*stop = YES;
}
}
}];
if (isTopLevelDomain) {
if (self.length != topLevelDomain.length) {
return topLevelDomain;
}
return self;
}
return nil;
}
- (void)safari_reverseEnumerateComponents:(NSArray *)array usingBlock:(void (^)(NSString *,int,int*))block
{
NSEnumerator *enumerator = [array reverseObjectEnumerator];
NSString *object;
NSMutableString *mString;
int idx = 0;
int res = 0;
while (object = [enumerator nextObject]) {
if (mString == nil) {
mString = [NSMutableString stringWithCapacity:16];
}
else {
[mString insertString:@"." atIndex:0];
}
[mString insertString:object atIndex:0];
res = 0;
block(mString, idx, &res);
if (res) break;;
idx++;
}
return;
}
@end
// 返回":"的range
NSRange sub_100012CEE(NSString *value)
{
if ([value length]) {
return [value rangeOfString:@":"];
}
return NSMakeRange(NSNotFound, -1);
}
// 有":"返回":"前的字符串,没有返回空
NSString *sub_100012934(NSString *value)
{
NSRange range = sub_100012CEE(value);
if (range.location == NSNotFound) {
return nil;
}
return [value substringToIndex:range.location];
}
int sub_1000129A8(NSString *value, int* arg2)
{
int res = 0;
if (![value length]) return 0;
NSString *v4 = [value lowercaseString];
NSString *v6 = [v4 safari_possibleTopLevelDomainCorrectionForUserTypedString];
if ([v6 length]) {
v4 = v6;
}
NSString *v8 = [v4 safari_highLevelDomainFromHost];
if ([v8 length]) {
*arg2 = 2 *([v4 rangeOfString:@"@"].location == NSNotFound);
return 1;
}
NSUInteger length = [v4 length];
static NSSet *v19 = nil;
if (!v19) {
v19 = [[NSSet alloc] initWithObjects:@"private", @"box", @"local", nil];
}
NSUInteger index = [v4 rangeOfString:@"." options:NSBackwardsSearch].location;
if (!length || index == NSNotFound || (index+1) == length) {
return 0;
}
NSString *v17 = [v4 substringFromIndex:index+1];
if ([v19 containsObject:[v17 lowercaseString]]) {
*arg2 = 2 *([v4 rangeOfString:@"@"].location == NSNotFound);
return 1;
}
return res;
}
//
NSCharacterSet *sub_100012D62()
{
NSMutableCharacterSet *res = [[NSMutableCharacterSet alloc] init];
[res formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
[res formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
[res formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
return [res invertedSet];
}
// 判断核心函数,<3说明匹配成功
int sub_100011D1C(NSString *value)
{
// XXX: 有tls缓存的逻辑,这里简化掉了
int res = 3;
value = [value copy];
NSCharacterSet *whiteCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *valueAfterTtrim = [value stringByTrimmingCharactersInSet:whiteCharacterSet];
NSString *valueAfterSafari = nil;
if (![valueAfterTtrim length]) return 4;
valueAfterSafari = [valueAfterTtrim safari_stringByRemovingUnnecessaryCharactersFromUserTypedURLString];
if (![valueAfterSafari length]) return 0;
NSRange rangeWC = [valueAfterSafari rangeOfCharacterFromSet:whiteCharacterSet];
NSRange rangeSlash = [valueAfterSafari rangeOfString:@"/"];
if (rangeWC.location != NSNotFound ) { // 有空格的情况
NSString *subString2WC = [valueAfterSafari substringToIndex:rangeWC.location]; // 空格前字符
NSString *subString2Colon = sub_100012934(subString2WC); // 冒号前字符
BOOL v24 = NO;
if (!subString2Colon.length) {
v24 = YES;
}
else {
static NSSet *qword_1000229C0 = nil;
if (!qword_1000229C0) {
qword_1000229C0 = [[NSSet alloc] initWithObjects:@"data",@"file",@"ftp",@"ftps",@"http",@"https",@"javascript",nil];
}
v24 = [qword_1000229C0 containsObject:subString2Colon];
}
if (!v24) return 0;
NSUInteger v25 = [subString2Colon length];
if (!v25 || v25 != subString2WC.length-1) {
res = 1;
if ([subString2WC _webkit_looksLikeAbsoluteURL]) {
return res;
}
if ([subString2WC rangeOfString:@"?"].location == NSNotFound) { // 有空格,同时带问号的话,会走到下面 LABEL_36 共同的逻辑
return 0;
}
}
else {
return 0;
}
}
// 找不到 '/'
if (rangeSlash.location == NSNotFound) {
NSString *v15 = valueAfterSafari;
NSRange v16 = [v15 rangeOfString:@":" options:NSBackwardsSearch];
if (v16.location == NSNotFound) {
goto LABEL_36;
}
NSUInteger v17 = [v15 length] - 1;
if (v16.location != v17) {
NSUInteger v18 = v16.location + 1;
while (v18 < v17) {
unichar v19 = [v15 characterAtIndex:v18];
if (v19 <= 0xFFu) {
++v18;
if ( (_DefaultRuneLocale.__runetype[v19] & 0x400) != 0 )
continue;
}
valueAfterSafari = v15;
goto LABEL_36;
};
valueAfterSafari = [v15 substringToIndex:v16.location];
}
}
LABEL_36:
NSUInteger length = [valueAfterSafari length];
BOOL go_LABEL_47 = NO;
if (length && ([valueAfterSafari characterAtIndex:length-1] == ':')) {
NSRange rangeOfColon = sub_100012CEE(valueAfterSafari);
if (rangeOfColon.location == (length-1)) { // 最后一个字符是:
go_LABEL_47 = YES;
}
}
if (!go_LABEL_47 && [valueAfterSafari _webkit_looksLikeAbsoluteURL]) {
NSString *valueBeforeColon = sub_100012934(valueAfterSafari);
if (rangeSlash.location == NSNotFound) { // XXX: 找不到'/'
res = 2;
if (![valueAfterSafari isEqualToString:@"about:blank"]) {
if ([valueBeforeColon length]) {
static NSSet *qword_1000229E0 = nil;
if (!qword_1000229E0) {
qword_1000229E0 = [[NSSet alloc] initWithObjects:@"data", @"facetime", @"gamecenter", @"irc", "javascript", "mailto", "man", "message", "radar", "spotify", "tel", nil];
}
unsigned char v42 = [qword_1000229E0 containsObject:valueBeforeColon]; // XXX: 没转小写
res = 2*(v42^1u) + 1;
}
else {
res = 3;
}
}
}
else {
static NSSet *qword_1000229D0 = nil;
if (!qword_1000229D0) {
qword_1000229D0 = [[NSSet alloc] initWithObjects:@"site", @"link", @"related", @"cache", nil];
}
if (valueBeforeColon.length) {
unsigned char v36 = [qword_1000229D0 containsObject:[valueBeforeColon lowercaseString]];
res = v36 ^ 1u;
}
else {
res = 1;
}
}
return res; // ok
}
//LABEL_47:
NSRange v37 = [valueAfterSafari rangeOfString:@"."];
if (rangeSlash.location == NSNotFound) {
int v55;
if ( v37.location && v37.location != NSNotFound && sub_1000129A8(valueAfterSafari, &v55)) {
res = v55;
}
else {
res = 0x3 +(([valueAfterSafari caseInsensitiveCompare:@"localhost"]==0)?-1:0);
}
return res;
}
res = 3;
static NSCharacterSet *qword_1000229F0 = nil;
if (!qword_1000229F0) {
qword_1000229F0 = sub_100012D62();
}
if ([valueAfterSafari rangeOfCharacterFromSet:qword_1000229F0].location == NSNotFound) {
return res;
}
int v55 = 0;
NSString *v40 = nil;
if (v37.location && v37.location != NSNotFound && (v40 = [valueAfterSafari substringToIndex:v37.location]) && sub_1000129A8(v40, &v55)) {
res = v55;
}
else {
res = 2;
rangeSlash = [valueAfterSafari rangeOfString:@"/" options:NSBackwardsSearch];
if (rangeSlash.location != [valueAfterSafari length]-1) {
NSRange v44 = [valueAfterSafari rangeOfString:@"#" options:NSBackwardsSearch];
if (v44.location==NSNotFound || v44.location < rangeSlash.location) {
NSRange v45 = [valueAfterSafari rangeOfString:@"?" options:NSBackwardsSearch];
if (v45.location==NSNotFound || v45.location <= rangeSlash.location) {
res = ((unsigned char)[valueAfterSafari hasPrefix:@"localhost/"]) ^ 3LL;
}
}
}
}
return res;
}
@implementation PBProbableWebDataDetective
- (id)detectedPatternValuesInValue:(NSString *)value
{
// @"com.apple.uikit.pasteboard-detection-pattern.probable-web-url"
if (![value isKindOfClass:NSString.class]) return nil;
NSDictionary *res = nil;
NSCharacterSet *whitesCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSArray *components = [value componentsSeparatedByCharactersInSet:whitesCharacterSet];
for (NSString *comp in components) {
if (sub_100011D1C(comp) < 3) {
res = @{
_UIPasteboardDetectionPatternProbableWebURL:comp
};
return res;
}
}
int v16 = sub_100011D1C(value);
if (v16 && v16 < 3) {
res = @{
_UIPasteboardDetectionPatternProbableWebURL:value
};
return res;
}
if (v16==0 || v16==3) {
res = @{
_UIPasteboardDetectionPatternProbableWebSearch:value
};
return res;
}
return nil;
}
@end
@implementation PBNumberDataDetective
- (id)detectedPatternValuesInValue:(NSString *)value
{
if (![value isKindOfClass:NSString.class]) return @{};
NSLocale *v7 = [NSLocale currentLocale];
NSString *v9 = [v7 groupingSeparator];
NSString *v11 = value;
if (v9) {
v11 = [value stringByReplacingOccurrencesOfString:v9 withString:@""];
}
if (![v11 length]) return @{};
NSScanner *v13 = [NSScanner scannerWithString:v11];
[v13 setLocale:v7];
double res;
if ([v13 scanDouble:&res]) {
return @{_UIPasteboardDetectionPatternNumber: @(res)};
}
return @{};
}
@end
void TestSafari()
{
NSLog(@"safari: %@", [@"ddd.333.xxx.com" safari_highLevelDomainFromHost]);
NSLog(@"safari: %@", [@"ddd.333.xxx.444" safari_highLevelDomainFromHost]);
}
void TestWebDetective()
{
NSLog(@"--------------------------- web ----------------------");
PBProbableWebDataDetective *detective = [PBProbableWebDataDetective new];
NSArray *okCases = @[
@"8.:/ Юt7hud",
@"8-:/Юt7hud",
@"8-:/Юt7hud",
@"3[得意]`:/ ⻓按复制此条消息,打开XXX,口令xxx",
@"http://kkk/?"
];
NSArray *badCases = @[
@"8~:/ Юt7hudZhJeLDWj8ΔΔ",
@"3 8`:/ ⻓按复制此条消息,打开XXX,口令xxx",
];
for (NSString *value in okCases) {
NSDictionary *result = [detective detectedPatternValuesInValue:value];
NSCAssert([result objectForKey:_UIPasteboardDetectionPatternProbableWebURL], @"");
NSLog(@"ok cases:%@", result);
}
for (NSString *value in badCases) {
NSDictionary *result = [detective detectedPatternValuesInValue:value];
NSCAssert([result objectForKey:_UIPasteboardDetectionPatternProbableWebURL]==nil, @"");
NSLog(@"bad cases:%@", result);
}
}
void TestNumberDetective()
{
NSLog(@"--------------------------- number ----------------------");
PBNumberDataDetective *detective = [PBNumberDataDetective new];
NSArray *okCases = @[
@"8~:/ Юt7hud",
@"1 8`:/ ⻓按复制此条消息,打开XXX,口令xxx",
];
for (NSString *value in okCases) {
NSDictionary *result = [detective detectedPatternValuesInValue:value];
NSCAssert([result objectForKey:_UIPasteboardDetectionPatternNumber], @"");
NSLog(@"ok cases:%@", result);
}}
void TestDetective()
{
TestWebDetective();
TestNumberDetective();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment