Files
秦宇航 f38c2b8fd2 Fix issues/65
Fix issue 65: scan QR from screen requires independent GPU.
https://github.com/qinyuhang/ShadowsocksX-NG-R/issues/65
2018-02-02 21:35:35 +08:00

287 lines
12 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// QRCodeUtils.m
// ShadowsocksX-NG
//
// Created by 邱宇舟 on 16/6/8.
// Copyright © 2016年 qiuyuzhou. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import "Utils.h"
void ScanQRCodeOnScreen() {
/* displays[] Quartz display ID's */
CGDirectDisplayID *displays = nil;
CGError err = CGDisplayNoErr;
CGDisplayCount dspCount = 0;
/* How many active displays do we have? */
err = CGGetActiveDisplayList(0, NULL, &dspCount);
/* If we are getting an error here then their won't be much to display. */
if(err != CGDisplayNoErr)
{
NSLog(@"Could not get active display count (%d)\n", err);
return;
}
/* Allocate enough memory to hold all the display IDs we have. */
displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID));
// Get the list of active displays
err = CGGetActiveDisplayList(dspCount,
displays,
&dspCount);
/* More error-checking here. */
if(err != CGDisplayNoErr)
{
NSLog(@"Could not get active display list (%d)\n", err);
return;
}
NSMutableArray* foundSSUrls = [NSMutableArray array];
CIDetector *detector = [CIDetector detectorOfType:@"CIDetectorTypeQRCode"
context:[CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer: @true}]
options:@{ CIDetectorAccuracy:CIDetectorAccuracyHigh }];
for (unsigned int displaysIndex = 0; displaysIndex < dspCount; displaysIndex++)
{
/* Make a snapshot image of the current display. */
CGImageRef image = CGDisplayCreateImage(displays[displaysIndex]);
NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image]];
for (CIQRCodeFeature *feature in features) {
// NSLog(@"%@", feature.messageString);
if ( [feature.messageString hasPrefix:@"ss://"] )
{
[foundSSUrls addObject:[NSURL URLWithString:feature.messageString]];
}else if ( [feature.messageString hasPrefix:@"ssr://"] ){
[foundSSUrls addObject:[NSURL URLWithString:feature.messageString]];
}
}
CGImageRelease(image);
}
free(displays);
// TODO if not find any urls push a notification tells there is no QR on the screen
[[NSNotificationCenter defaultCenter]
postNotificationName:@"NOTIFY_FOUND_SS_URL"
object:nil
userInfo: @{ @"urls": foundSSUrls,
@"source": @"qrcode"
}
];
}
NSString* decode64(NSString* str){
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
str = [str stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
if(str.length%4){
NSInteger length = (4-str.length%4) + str.length;
str = [str stringByPaddingToLength: length withString:@"=" startingAtIndex:0];
}
NSData* decodeData = [[NSData alloc] initWithBase64EncodedString:str options:0];
NSString* decodeStr = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];
return decodeStr;
}
NSString* encode64(NSString* str){
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSString *stringBase64 = [data base64EncodedStringWithOptions: NSDataBase64EncodingEndLineWithCarriageReturn];
stringBase64 = [stringBase64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
stringBase64 = [stringBase64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
stringBase64 = [stringBase64 stringByReplacingOccurrencesOfString:@"=" withString:@""];
return stringBase64;
}
NSDictionary<NSString*, id>* ParseAppURLSchemes(NSURL* url){
if(!url.host){
return nil;
}
NSString *urlString = [url absoluteString];
if ([urlString hasPrefix:@"ss://"]) {
return ParseSSURL(url);
}
if ([urlString hasPrefix:@"ssr://"]) {
return ParseSSRURL(url);
}
return nil;
}
// 解析SS URL,如果成功则返回一个与ServerProfile类兼容的dict
// 或SSR URL,ServerProfile类已经默认添加SSR参数,默认放空,如果URL为SSR://则改变解析方法
// ss:// + base64(method:password@domain:port)
static NSDictionary<NSString*, id>* ParseSSURL(NSURL* url) {
if (!url.host) {
return nil;
}
NSString *urlString = [url absoluteString];
int i = 0;
NSString *errorReason = nil;
if([urlString hasPrefix:@"ss://"]){
while(i < 2) {
if (i == 1) {
NSString* host = url.host;
if ([host length]%4!=0) {
int n = 4 - [host length]%4;
if (1==n) {
host = [host stringByAppendingString:@"="];
} else if (2==n) {
host = [host stringByAppendingString:@"=="];
}
}
NSData *data = [[NSData alloc] initWithBase64EncodedString:host options:0];
NSString *decodedString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
urlString = decodedString;
}
i++;
urlString = [urlString stringByReplacingOccurrencesOfString:@"ss://" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, urlString.length)];
NSRange firstColonRange = [urlString rangeOfString:@":"];
NSRange lastColonRange = [urlString rangeOfString:@":" options:NSBackwardsSearch];
NSRange lastAtRange = [urlString rangeOfString:@"@" options:NSBackwardsSearch];
if (firstColonRange.length == 0) {
errorReason = @"colon not found";
continue;
}
if (firstColonRange.location == lastColonRange.location) {
errorReason = @"only one colon";
continue;
}
if (lastAtRange.length == 0) {
errorReason = @"at not found";
continue;
}
if (!((firstColonRange.location < lastAtRange.location) && (lastAtRange.location < lastColonRange.location))) {
errorReason = @"wrong position";
continue;
}
NSString *method = [urlString substringWithRange:NSMakeRange(0, firstColonRange.location)];
NSString *password = [urlString substringWithRange:NSMakeRange(firstColonRange.location + 1, lastAtRange.location - firstColonRange.location - 1)];
NSString *IP = [urlString substringWithRange:NSMakeRange(lastAtRange.location + 1, lastColonRange.location - lastAtRange.location - 1)];
NSString *port = [urlString substringWithRange:NSMakeRange(lastColonRange.location + 1, urlString.length - lastColonRange.location - 1)];
return @{@"ServerHost": IP,
@"ServerPort": @([port integerValue]),
@"Method": method,
@"Password": password,
};
}
}
return nil;
}
static NSDictionary<NSString*, id>* ParseSSRURL(NSURL* url) {
NSString *urlString = [url absoluteString];
NSString *firstParam;
NSString *lastParam;
//if ([urlString hasPrefix:@"ssr://"]){
// ssr:// + base64(abc.xyz:12345:auth_sha1_v2:rc4-md5:tls1.2_ticket_auth:{base64(password)}/?obfsparam={base64(混淆参数(网址))}&protoparam={base64(混淆协议)}&remarks={base64(节点名称)}&group={base64(分组名)})
urlString = [urlString stringByReplacingOccurrencesOfString:@"ssr://" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, urlString.length)];
NSString *decodedString = decode64(urlString);
NSLog(@"%@", decodedString);
if ([decodedString isEqual: @""]) {
[[NSNotificationCenter defaultCenter]
postNotificationName:@"NOTIFY_INVALIDE_QR"
object:nil
userInfo: @{ @"urls": @"配置二维码无效!",
@"source": @"qrcode"
}
];
}else{
NSRange paramSplit = [decodedString rangeOfString:@"?"];
NSLog(@"%lu", (unsigned long)paramSplit.location);
if (paramSplit.length == 0){
firstParam = decodedString;
} else {
firstParam = [decodedString substringToIndex:paramSplit.location-1];
lastParam = [decodedString substringFromIndex:paramSplit.location];
}
NSDictionary *parserLastParamDict = parseSSRLastParam(lastParam);
//后面已经parser完成,接下来需要解析到profile里面
//abc.xyz:12345:auth_sha1_v2:rc4-md5:tls1.2_ticket_auth:{base64(password)}
NSRange range = [firstParam rangeOfString:@":"];
NSString *ip = [firstParam substringToIndex:range.location];//第一个参数是域名
firstParam = [firstParam substringFromIndex:range.location + range.length];
range = [firstParam rangeOfString:@":"];
NSString *port = [firstParam substringToIndex:range.location];//第二个参数是端口
firstParam = [firstParam substringFromIndex:range.location + range.length];
range = [firstParam rangeOfString:@":"];
NSString *ssrProtocol = [firstParam substringToIndex:range.location];//第三个参数是协议
firstParam = [firstParam substringFromIndex:range.location + range.length];
range = [firstParam rangeOfString:@":"];
NSString *encryption = [firstParam substringToIndex:range.location];//第四个参数是加密
firstParam = [firstParam substringFromIndex:range.location + range.length];
range = [firstParam rangeOfString:@":"];
NSString *ssrObfs = [firstParam substringToIndex:range.location];//第五个参数是混淆协议
firstParam = [firstParam substringFromIndex:range.location + range.length];
// range = [firstParam rangeOfString:@":"];
NSString *password = decode64(firstParam);// [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:firstParam options:0]encoding:NSUTF8StringEncoding];//第五个参数是base64密码
NSString *ssrObfsParam = @"";
NSString *remarks = @"";
NSString *ssrProtocolParam = @"";
NSString *ssrGroup = @"";
for (NSString *key in parserLastParamDict) {
// NSLog(@"key: %@ value: %@", key, parserLastParamDict[key]);
if ([key isEqual: @"obfsparam"]) {
ssrObfsParam = parserLastParamDict[key];
} else if ([key isEqual: @"remarks"]) {
remarks = parserLastParamDict[key];
} else if([key isEqual:@"protoparam"]){
ssrProtocolParam = parserLastParamDict[key];
} else if([key isEqual:@"group"]){
ssrGroup = parserLastParamDict[key];
}
}
return @{@"ServerHost":ip,
@"ServerPort": @([port integerValue]),
@"Method": encryption,
@"Password": password,
@"ssrObfs":ssrObfs,
@"ssrObfsParam":ssrObfsParam,
@"ssrProtocol":ssrProtocol,
@"ssrProtocolParam":ssrProtocolParam,
@"Remark":remarks,
@"ssrGroup":ssrGroup,
};
}
//}
return nil;
}
static NSDictionary<NSString*, id>* parseSSRLastParam(NSString* lastParam){
NSMutableDictionary *parserLastParamDict = [[NSMutableDictionary alloc]init];
if(lastParam.length == 0){
return nil;
}
lastParam = [lastParam substringFromIndex:1];
NSArray *lastParamArray = [lastParam componentsSeparatedByString:@"&"];
for (int i=0; i<lastParamArray.count; i++) {
NSString *toSplitString = lastParamArray[i];
NSRange lastParamSplit = [toSplitString rangeOfString:@"="];
if (lastParamSplit.location != NSNotFound) {
NSString *key = [toSplitString substringToIndex:lastParamSplit.location];
NSString *value = decode64([toSplitString substringFromIndex:lastParamSplit.location+1]);
[parserLastParamDict setValue: value forKey: key];
}
}
return parserLastParamDict;
}