2021-10-21 11:05:40 -07:00
|
|
|
|
//
|
|
|
|
|
|
// SBRScriptPolicy.m
|
|
|
|
|
|
// App
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by James Magahern on 10/15/21.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "SBRScriptPolicy.h"
|
|
|
|
|
|
|
|
|
|
|
|
// For icon drawing
|
|
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
|
static CGFloat RoundToScale(CGFloat v, CGFloat s) {
|
|
|
|
|
|
return round(v * s) / s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 11:05:40 -07:00
|
|
|
|
@implementation SBRScriptPolicy
|
|
|
|
|
|
|
2021-10-21 13:24:58 -07:00
|
|
|
|
+ (NSSet<NSString *> *)_commonCDNOrigins {
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
|
|
static NSSet<NSString *> *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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 11:05:40 -07:00
|
|
|
|
+ (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.";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
|
+ (UIImage *)iconRepresentationForPolicyType:(SBRScriptOriginPolicyType)policyType withConfiguration:(nonnull SBRScriptPolicyIconConfiguration *)configuration
|
2021-10-21 11:05:40 -07:00
|
|
|
|
{
|
2021-10-21 15:08:04 -07:00
|
|
|
|
const CGSize size = [configuration size];
|
2023-04-14 19:37:31 -07:00
|
|
|
|
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 11:05:40 -07:00
|
|
|
|
UIFont *font = [UIFont boldSystemFontOfSize:size.height - 2];
|
|
|
|
|
|
NSDictionary<NSAttributedStringKey, id> *attrs = @{
|
|
|
|
|
|
NSFontAttributeName : font,
|
2021-10-21 15:08:04 -07:00
|
|
|
|
NSForegroundColorAttributeName : [configuration foregroundColor] ?: [UIColor whiteColor],
|
2021-10-21 11:05:40 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
|
const CGFloat scale = UIScreen.mainScreen.scale;
|
2021-10-21 11:05:40 -07:00
|
|
|
|
const CGRect rect = (CGRect) { .origin = CGPointZero, .size = size };
|
2021-10-21 15:08:04 -07:00
|
|
|
|
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
|
2021-10-21 11:05:40 -07:00
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
|
[([configuration backgroundColor] ?: [UIColor blackColor]) setFill];
|
2021-10-21 11:05:40 -07:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
|
CGSize charSize = [character sizeWithAttributes:attrs];
|
|
|
|
|
|
charSize.width = RoundToScale(charSize.width, scale);
|
|
|
|
|
|
charSize.height = RoundToScale(charSize.height, scale);
|
|
|
|
|
|
|
2021-10-21 11:05:40 -07:00
|
|
|
|
const CGRect charRect = (CGRect) {
|
|
|
|
|
|
.origin = (CGPoint) {
|
|
|
|
|
|
.x = (size.width - charSize.width) / 2,
|
2021-10-21 15:08:04 -07:00
|
|
|
|
.y = -(charSize.height - size.height) / 2,
|
2021-10-21 11:05:40 -07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
.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"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 13:24:58 -07:00
|
|
|
|
- (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<NSString *> *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<NSString *> *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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-21 11:05:40 -07:00
|
|
|
|
@end
|
2021-10-21 15:08:04 -07:00
|
|
|
|
|
|
|
|
|
|
@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
|