// // SBRScriptPolicy.m // App // // Created by James Magahern on 10/15/21. // #import "SBRScriptPolicy.h" // For icon drawing #import static CGFloat RoundToScale(CGFloat v, CGFloat s) { return round(v * s) / s; } @implementation SBRScriptPolicy + (NSSet *)_commonCDNOrigins { static dispatch_once_t onceToken; static NSSet *whitelist = nil; dispatch_once(&onceToken, ^{ whitelist = [NSSet setWithArray:@[ // JSDelivr: Used by bootstrap @"cdn.jsdelivr.net", // SourceForge @"fsdn.net", // Open Source CDNs @"cdnjs.com", @"osscdn.com", // JQuery @"code.jquery.com", // Bootstrap @"bootstrapcdn.com", ]]; }); return whitelist; } + (NSString *)titleForPolicyType:(SBRScriptOriginPolicyType)policyType { switch (policyType) { case SBRScriptOriginPolicyTypeAlpha: return @"Alpha"; case SBRScriptOriginPolicyTypeBravo: return @"Bravo"; case SBRScriptOriginPolicyTypeCharlie: return @"Charlie"; case SBRScriptOriginPolicyTypeDelta: return @"Delta"; case SBRScriptOriginPolicyTypeEcho: return @"Echo"; } } + (NSString *)localizedDescriptionForPolicyType:(SBRScriptOriginPolicyType)policyType { switch (policyType) { case SBRScriptOriginPolicyTypeAlpha: return @"All scripts blocked."; case SBRScriptOriginPolicyTypeBravo: return @"Scripts on page are allowed."; case SBRScriptOriginPolicyTypeCharlie: return @"Allow scripts from the same security origin."; case SBRScriptOriginPolicyTypeDelta: return @"Allow scripts from common and host CDNs."; case SBRScriptOriginPolicyTypeEcho: return @"All scripts are allowed."; } } + (UIImage *)iconRepresentationForPolicyType:(SBRScriptOriginPolicyType)policyType withConfiguration:(nonnull SBRScriptPolicyIconConfiguration *)configuration { const CGSize size = [configuration size]; UIFont *font = [UIFont boldSystemFontOfSize:size.height - 2]; NSDictionary *attrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : [configuration foregroundColor] ?: [UIColor whiteColor], }; const CGFloat scale = UIScreen.mainScreen.scale; const CGRect rect = (CGRect) { .origin = CGPointZero, .size = size }; UIGraphicsBeginImageContextWithOptions(size, NO, scale); [([configuration backgroundColor] ?: [UIColor blackColor]) setFill]; UIBezierPath *backgroundPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:4.0]; [backgroundPath fill]; NSString *character = @""; switch (policyType) { case SBRScriptOriginPolicyTypeAlpha: character = @"𝝰"; break; case SBRScriptOriginPolicyTypeBravo: character = @"𝝱"; break; case SBRScriptOriginPolicyTypeCharlie: character = @"𝝲"; break; case SBRScriptOriginPolicyTypeDelta: character = @"𝝳"; break; case SBRScriptOriginPolicyTypeEcho: character = @"𝝴"; break; } CGSize charSize = [character sizeWithAttributes:attrs]; charSize.width = RoundToScale(charSize.width, scale); charSize.height = RoundToScale(charSize.height, scale); const CGRect charRect = (CGRect) { .origin = (CGPoint) { .x = (size.width - charSize.width) / 2, .y = -(charSize.height - size.height) / 2, }, .size = charSize }; [character drawInRect:charRect withAttributes:attrs]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } + (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithSecurityOrigin:(NSString *)origin policyType:(SBRScriptOriginPolicyType)policyType { if (self = [super init]) { _origin = [origin copy]; _policyType = policyType; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { NSString *origin = [coder decodeObjectForKey:@"origin"]; NSNumber *policyTypeNumber = [coder decodeObjectForKey:@"policyType"]; return [self initWithSecurityOrigin:origin policyType:[policyTypeNumber integerValue]]; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:_origin forKey:@"origin"]; [coder encodeObject:[NSNumber numberWithInteger:_policyType] forKey:@"policyType"]; } - (BOOL)allowsEmbeddedJavaScript { return _policyType > SBRScriptOriginPolicyTypeAlpha; } - (BOOL)allowsExternalJavaScriptResourceOrigin:(NSString *)resourceOrigin { switch (_policyType) { case SBRScriptOriginPolicyTypeAlpha: // No scripts allowed whatsoever. return NO; case SBRScriptOriginPolicyTypeBravo: // Only allows on-page scripts, no external scripts whatsoever. return NO; case SBRScriptOriginPolicyTypeCharlie: // Only allow scripts from the exact same origin as the host. return [_origin isEqualToString:resourceOrigin]; case SBRScriptOriginPolicyTypeDelta: { // Allow scripts from common CDNs, or CDNs that *appear* to belong to the host. if ([[[self class] _commonCDNOrigins] containsObject:resourceOrigin]) { return YES; } BOOL looksLikeCDN = NO; NSArray *hostOriginComponents = [_origin componentsSeparatedByString:@"."]; if ([hostOriginComponents count] > 1) { // Assume our "family" name is just before the TLD. NSString *hostFamilyName = [hostOriginComponents objectAtIndex:(hostOriginComponents.count - 2)]; looksLikeCDN = [resourceOrigin containsString:hostFamilyName]; if (!looksLikeCDN) { NSArray *resourceOriginComponents = [resourceOrigin componentsSeparatedByString:@"."]; if ([resourceOriginComponents count] > 1) { NSString *resourceFamilyName = [resourceOriginComponents objectAtIndex:(resourceOriginComponents.count - 2)]; // Check and see if the subdomain is "cdn", but also make sure we're not accidentally allowing an ad network CDN. looksLikeCDN = [[resourceOriginComponents firstObject] isEqualToString:@"cdn"]; BOOL looksLikeAdNetwork = ( [resourceFamilyName hasSuffix:@"ad"] || [resourceFamilyName hasSuffix:@"ads"] || [resourceFamilyName containsString:@"analy"] // "analy-tics" ); looksLikeCDN = looksLikeCDN && !looksLikeAdNetwork; NSLog(@"SBRProcessPlugin: [%@] CDN:%@ AD:%@", resourceOriginComponents, looksLikeCDN ? @"YES" : @"NO", looksLikeAdNetwork ? @"YES" : @"NO"); } } } return looksLikeCDN; } case SBRScriptOriginPolicyTypeEcho: // Anything goes. return YES; } } @end @implementation SBRScriptPolicyIconConfiguration - (instancetype)initWithSize:(CGSize)size foregroundColor:(nullable UIColor *)foregroundColor backgroundColor:(nullable UIColor *)backgroundColor { if (self = [super init]) { _size = size; _foregroundColor = foregroundColor; _backgroundColor = backgroundColor; } return self; } @end