阿里sdk

This commit is contained in:
Robin
2026-02-20 17:56:24 +08:00
parent 39524692e5
commit f3af234308
524 changed files with 58345 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
//
// HttpDnsLocker.h
// AlicloudHttpDNS
//
// Created by 王贇 on 2023/8/16.
// Copyright © 2023 alibaba-inc.com. All rights reserved.
//
#ifndef HttpDnsLocker_h
#define HttpDnsLocker_h
#import <Foundation/Foundation.h>
#import "HttpdnsRequest.h"
@interface HttpDnsLocker : NSObject
+ (instancetype)sharedInstance;
- (void)lock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType;
- (BOOL)tryLock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType;
- (void)unlock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType;
@end
#endif /* HttpDnsLocker_h */

View File

@@ -0,0 +1,91 @@
//
// HttpDnsLocker.m
// AlicloudHttpDNS
//
// Created by on 2023/8/16.
// Copyright © 2023 alibaba-inc.com. All rights reserved.
//
#import "HttpDnsLocker.h"
#import "HttpdnsService.h"
@implementation HttpDnsLocker {
NSMutableDictionary<NSString*, NSLock*> *_v4LockMap;
NSMutableDictionary<NSString*, NSLock*> *_v6LockMap;
NSMutableDictionary<NSString*, NSLock*> *_v4v6LockMap;
}
+ (instancetype)sharedInstance {
static HttpDnsLocker *locker = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
locker = [[HttpDnsLocker alloc] init];
});
return locker;
}
- (instancetype)init {
if (self = [super init]) {
_v4LockMap = [NSMutableDictionary dictionary];
_v6LockMap = [NSMutableDictionary dictionary];
_v4v6LockMap = [NSMutableDictionary dictionary];
}
return self;
}
- (void)lock:(NSString *)host queryType:(HttpdnsQueryIPType)queryIpType {
NSLock *condition = [self getLock:host queryType:queryIpType];
if (condition) {
[condition lock];
}
}
- (BOOL)tryLock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType {
NSLock *condition = [self getLock:host queryType:queryType];
if (condition) {
return [condition tryLock];
}
return NO;
}
- (void)unlock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType {
NSLock *condition = [self getLock:host queryType:queryType];
if (condition) {
[condition unlock];
}
}
- (NSLock *)getLock:(NSString *)host queryType:(HttpdnsQueryIPType)queryType {
if (queryType == HttpdnsQueryIPTypeIpv4) {
@synchronized (_v4LockMap) {
NSLock *lock = [_v4LockMap objectForKey:host];
if (!lock) {
lock = [[NSLock alloc] init];
[_v4LockMap setObject:lock forKey:host];
}
return lock;
}
} else if (queryType == HttpdnsQueryIPTypeIpv6) {
@synchronized (_v6LockMap) {
NSLock *condition = [_v6LockMap objectForKey:host];
if (!condition) {
condition = [[NSLock alloc] init];
[_v6LockMap setObject:condition forKey:host];
}
return condition;
}
} else {
@synchronized (_v4v6LockMap) {
NSLock *condition = [_v4v6LockMap objectForKey:host];
if (!condition) {
condition = [[NSLock alloc] init];
[_v4v6LockMap setObject:condition forKey:host];
}
return condition;
}
}
}
@end

View File

@@ -0,0 +1,38 @@
//
// HttpdnsHostObjectInMemoryCache.h
// AlicloudHttpDNS
//
// Created by xuyecan on 2024/9/28.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HttpdnsHostObject.h"
NS_ASSUME_NONNULL_BEGIN
// 这个字典在HTTPDNS中只用于存储HttpdnsHostObject对象这个对象是整个框架的核心对象用于缓存和处理域名解析结果
// 通常从缓存中获得这个对象之后,会根据不同场景改变一些字段的值,而且很可能发生在不同线程中
// 而不同线程从缓存中直接读取共享对象的话很有可能发生线程竞争的情况多线程访问某个对象的同一个字段在swift环境有较高概率发生crash
// 因此除了确保字典操作的线程安全拿出对象的时候也直接copy一个复制对象返回(HttpdnsHostObject对象实现了NSCopying协议)
@interface HttpdnsHostObjectInMemoryCache : NSObject
- (void)setHostObject:(HttpdnsHostObject *)object forCacheKey:(NSString *)key;
- (HttpdnsHostObject *)getHostObjectByCacheKey:(NSString *)key;
- (HttpdnsHostObject *)getHostObjectByCacheKey:(NSString *)key createIfNotExists:(HttpdnsHostObject *(^)(void))objectProducer;
- (void)updateQualityForCacheKey:(NSString *)key forIp:(NSString *)ip withConnectedRT:(NSInteger)connectedRT;
- (void)removeHostObjectByCacheKey:(NSString *)key;
- (void)removeAllHostObjects;
- (NSInteger)count;
- (NSArray *)allCacheKeys;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,94 @@
//
// HttpdnsHostObjectInMemoryCache.m
// AlicloudHttpDNS
//
// Created by xuyecan on 2024/9/28.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
//
#import "HttpdnsHostObjectInMemoryCache.h"
@interface HttpdnsHostObjectInMemoryCache ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, HttpdnsHostObject *> *cacheDict;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation HttpdnsHostObjectInMemoryCache
- (instancetype)init {
self = [super init];
if (self) {
_cacheDict = [NSMutableDictionary dictionary];
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)setHostObject:(HttpdnsHostObject *)object forCacheKey:(NSString *)key {
[_lock lock];
_cacheDict[key] = object;
[_lock unlock];
}
- (HttpdnsHostObject *)getHostObjectByCacheKey:(NSString *)key {
[_lock lock];
@try {
HttpdnsHostObject *object = _cacheDict[key];
return [object copy];
} @finally {
[_lock unlock];
}
}
- (HttpdnsHostObject *)getHostObjectByCacheKey:(NSString *)key createIfNotExists:(HttpdnsHostObject *(^)(void))objectProducer {
[_lock lock];
HttpdnsHostObject *object = _cacheDict[key];
@try {
if (!object) {
object = objectProducer();
_cacheDict[key] = object;
}
return [object copy];
} @finally {
[_lock unlock];
}
}
- (void)updateQualityForCacheKey:(NSString *)key forIp:(NSString *)ip withConnectedRT:(NSInteger)connectedRT {
[_lock lock];
HttpdnsHostObject *object = _cacheDict[key];
if (object) {
[object updateConnectedRT:connectedRT forIP:ip];
}
[_lock unlock];
}
- (void)removeHostObjectByCacheKey:(NSString *)key {
[_lock lock];
[_cacheDict removeObjectForKey:key];
[_lock unlock];
}
- (void)removeAllHostObjects {
[_lock lock];
[_cacheDict removeAllObjects];
[_lock unlock];
}
- (NSInteger)count {
[_lock lock];
NSInteger count = _cacheDict.count;
[_lock unlock];
return count;
}
- (NSArray *)allCacheKeys {
[_lock lock];
NSArray *keys = [_cacheDict allKeys];
[_lock unlock];
return keys;
}
@end

View File

@@ -0,0 +1,96 @@
//
// HttpdnsIPQualityDetector.h
// AlicloudHttpDNS
//
// Created by xuyecan on 2025/3/13.
// Copyright © 2025 alibaba-inc.com. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* IP质量检测回调
* @param cacheKey 缓存键
* @param ip IP地址
* @param costTime 连接耗时(毫秒),-1表示连接失败
*/
typedef void(^HttpdnsIPQualityCallback)(NSString *cacheKey, NSString *ip, NSInteger costTime);
@interface HttpdnsIPQualityDetector : NSObject
/**
* 单例方法
*/
+ (instancetype)sharedInstance;
/**
* 获取当前等待队列中的任务数量
*/
- (NSUInteger)pendingTasksCount;
/**
* 调度一个IP连接质量检测任务不会阻塞当前线程
* @param cacheKey 缓存键,通常是域名
* @param ip 要检测的IP地址
* @param port 连接端口如果为nil则默认使用80
* @param callback 检测完成后的回调
*/
- (void)scheduleIPQualityDetection:(NSString *)cacheKey
ip:(NSString *)ip
port:(nullable NSNumber *)port
callback:(HttpdnsIPQualityCallback)callback;
#pragma mark - Methods exposed for testing
/**
* 执行IP连接质量检测
* @param cacheKey 缓存键,通常是域名
* @param ip 要检测的IP地址
* @param port 连接端口如果为nil则默认使用80
* @param callback 检测完成后的回调
* @note 此方法主要用于测试
*/
- (void)executeDetection:(NSString *)cacheKey
ip:(NSString *)ip
port:(nullable NSNumber *)port
callback:(HttpdnsIPQualityCallback)callback;
/**
* 建立TCP连接并测量连接时间
* @param ip 要连接的IP地址
* @param port 连接端口
* @return 连接耗时(毫秒),-1表示连接失败
* @note 此方法主要用于测试
*/
- (NSInteger)tcpConnectToIP:(NSString *)ip port:(int)port;
/**
* 添加待处理任务
* @param cacheKey 缓存键,通常是域名
* @param ip 要检测的IP地址
* @param port 连接端口
* @param callback 检测完成后的回调
* @note 此方法主要用于测试
*/
- (void)addPendingTask:(NSString *)cacheKey
ip:(NSString *)ip
port:(nullable NSNumber *)port
callback:(HttpdnsIPQualityCallback)callback;
/**
* 处理待处理任务队列
* @note 此方法主要用于测试
*/
- (void)processPendingTasksIfNeeded;
/**
* 处理所有待处理任务
* @note 此方法主要用于测试
*/
- (void)processPendingTasks;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,291 @@
//
// HttpdnsIPQualityDetector.m
// AlicloudHttpDNS
//
// Created by xuyecan on 2025/3/13.
// Copyright © 2025 alibaba-inc.com. All rights reserved.
//
#import "HttpdnsIPQualityDetector.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netdb.h>
#import <unistd.h>
#import <sys/time.h>
#import <fcntl.h>
#import <errno.h>
#import "HttpdnsLog_Internal.h"
#import "HttpdnsUtil.h"
//
@interface HttpdnsDetectionTask : NSObject
@property (nonatomic, copy) NSString *cacheKey;
@property (nonatomic, copy) NSString *ip;
@property (nonatomic, strong) NSNumber *port;
@property (nonatomic, copy) HttpdnsIPQualityCallback callback;
@end
@implementation HttpdnsDetectionTask
@end
@interface HttpdnsIPQualityDetector ()
@property (nonatomic, strong) dispatch_queue_t detectQueue;
@property (nonatomic, strong) dispatch_semaphore_t concurrencySemaphore;
@property (nonatomic, strong) NSMutableArray<HttpdnsDetectionTask *> *pendingTasks;
@property (nonatomic, strong) NSLock *pendingTasksLock;
@property (nonatomic, assign) BOOL isProcessingPendingTasks;
/**
* 10
*/
@property (nonatomic, assign) NSUInteger maxConcurrentDetections;
@end
@implementation HttpdnsIPQualityDetector
+ (instancetype)sharedInstance {
static HttpdnsIPQualityDetector *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[HttpdnsIPQualityDetector alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_detectQueue = dispatch_queue_create("com.aliyun.httpdns.ipqualitydetector", DISPATCH_QUEUE_CONCURRENT);
_maxConcurrentDetections = 10;
_concurrencySemaphore = dispatch_semaphore_create(_maxConcurrentDetections);
_pendingTasks = [NSMutableArray array];
_pendingTasksLock = [[NSLock alloc] init];
_isProcessingPendingTasks = NO;
}
return self;
}
- (void)scheduleIPQualityDetection:(NSString *)cacheKey
ip:(NSString *)ip
port:(NSNumber *)port
callback:(HttpdnsIPQualityCallback)callback {
if (!cacheKey || !ip || !callback) {
HttpdnsLogDebug("IPQualityDetector invalid parameters for detection: cacheKey=%@, ip=%@", cacheKey, ip);
return;
}
//
if (dispatch_semaphore_wait(_concurrencySemaphore, DISPATCH_TIME_NOW) != 0) {
//
[self addPendingTask:cacheKey ip:ip port:port callback:callback];
return;
}
//
[self executeDetection:cacheKey ip:ip port:port callback:callback];
}
- (void)addPendingTask:(NSString *)cacheKey ip:(NSString *)ip port:(NSNumber *)port callback:(HttpdnsIPQualityCallback)callback {
// ARC
HttpdnsDetectionTask *task = [[HttpdnsDetectionTask alloc] init];
task.cacheKey = cacheKey;
task.ip = ip;
task.port = port;
task.callback = callback;
//
[_pendingTasksLock lock];
[_pendingTasks addObject:task];
[_pendingTasksLock unlock];
//
[self processPendingTasksIfNeeded];
}
- (NSUInteger)pendingTasksCount {
[_pendingTasksLock lock];
NSUInteger count = _pendingTasks.count;
[_pendingTasksLock unlock];
return count;
}
- (void)processPendingTasksIfNeeded {
[_pendingTasksLock lock];
BOOL shouldProcess = !_isProcessingPendingTasks && _pendingTasks.count > 0;
if (shouldProcess) {
_isProcessingPendingTasks = YES;
}
[_pendingTasksLock unlock];
if (shouldProcess) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self processPendingTasks];
});
}
}
- (void)processPendingTasks {
while (1) {
//
if (dispatch_semaphore_wait(_concurrencySemaphore, DISPATCH_TIME_NOW) != 0) {
//
[NSThread sleepForTimeInterval:0.1];
continue;
}
//
HttpdnsDetectionTask *task = nil;
[_pendingTasksLock lock];
if (_pendingTasks.count > 0) {
task = _pendingTasks.firstObject;
[_pendingTasks removeObjectAtIndex:0];
} else {
//
_isProcessingPendingTasks = NO;
[_pendingTasksLock unlock];
//
dispatch_semaphore_signal(_concurrencySemaphore);
break;
}
[_pendingTasksLock unlock];
//
[self executeDetection:task.cacheKey ip:task.ip port:task.port callback:task.callback];
}
}
- (void)executeDetection:(NSString *)cacheKey ip:(NSString *)ip port:(NSNumber *)port callback:(HttpdnsIPQualityCallback)callback {
//
HttpdnsIPQualityCallback strongCallback = [callback copy];
// 使线
dispatch_async(self.detectQueue, ^{
NSInteger costTime = [self tcpConnectToIP:ip port:port ? [port intValue] : 80];
// 线
dispatch_async(dispatch_get_global_queue(0, 0), ^{
strongCallback(cacheKey, ip, costTime);
//
dispatch_semaphore_signal(self->_concurrencySemaphore);
//
[self processPendingTasksIfNeeded];
});
});
}
- (NSInteger)tcpConnectToIP:(NSString *)ip port:(int)port {
if (!ip || port <= 0) {
return -1;
}
int socketFd;
struct sockaddr_in serverAddr;
struct sockaddr_in6 serverAddr6;
void *serverAddrPtr;
socklen_t serverAddrLen;
BOOL isIPv6 = [HttpdnsUtil isIPv6Address:ip];
BOOL isIpv4 = [HttpdnsUtil isIPv4Address:ip];
// socket
if (isIPv6) {
socketFd = socket(AF_INET6, SOCK_STREAM, 0);
if (socketFd < 0) {
HttpdnsLogDebug("IPQualityDetector failed to create IPv6 socket: %s", strerror(errno));
return -1;
}
memset(&serverAddr6, 0, sizeof(serverAddr6));
serverAddr6.sin6_family = AF_INET6;
serverAddr6.sin6_port = htons(port);
inet_pton(AF_INET6, [ip UTF8String], &serverAddr6.sin6_addr);
serverAddrPtr = &serverAddr6;
serverAddrLen = sizeof(serverAddr6);
} else if (isIpv4) {
socketFd = socket(AF_INET, SOCK_STREAM, 0);
if (socketFd < 0) {
HttpdnsLogDebug("IPQualityDetector failed to create IPv4 socket: %s", strerror(errno));
return -1;
}
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
inet_pton(AF_INET, [ip UTF8String], &serverAddr.sin_addr);
serverAddrPtr = &serverAddr;
serverAddrLen = sizeof(serverAddr);
} else {
return -1;
}
//
int flags = fcntl(socketFd, F_GETFL, 0);
fcntl(socketFd, F_SETFL, flags | O_NONBLOCK);
//
struct timeval startTime, endTime;
gettimeofday(&startTime, NULL);
//
int connectResult = connect(socketFd, serverAddrPtr, serverAddrLen);
if (connectResult < 0) {
if (errno == EINPROGRESS) {
// 使select
fd_set fdSet;
struct timeval timeout;
FD_ZERO(&fdSet);
FD_SET(socketFd, &fdSet);
// 2
// 2IP
timeout.tv_sec = 2;
timeout.tv_usec = 0;
int selectResult = select(socketFd + 1, NULL, &fdSet, NULL, &timeout);
if (selectResult <= 0) {
//
HttpdnsLogDebug("IPQualityDetector connection to %@ timed out or error: %s", ip, strerror(errno));
close(socketFd);
return -1;
} else {
//
int error;
socklen_t errorLen = sizeof(error);
if (getsockopt(socketFd, SOL_SOCKET, SO_ERROR, &error, &errorLen) < 0 || error != 0) {
HttpdnsLogDebug("IPQualityDetector connection to %@ failed after select: %s", ip, strerror(error));
close(socketFd);
return -1;
}
}
} else {
//
HttpdnsLogDebug("IPQualityDetector connection to %@ failed: %s", ip, strerror(errno));
close(socketFd);
return -1;
}
}
//
gettimeofday(&endTime, NULL);
// socket
close(socketFd);
//
long seconds = endTime.tv_sec - startTime.tv_sec;
long microseconds = endTime.tv_usec - startTime.tv_usec;
NSInteger costTime = (seconds * 1000) + (microseconds / 1000);
return costTime;
}
@end

View File

@@ -0,0 +1,106 @@
/*
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
//! Project version number for MacOSReachability.
FOUNDATION_EXPORT double ReachabilityVersionNumber;
//! Project version string for MacOSReachability.
FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[];
/**
* Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X.
*
* @see http://nshipster.com/ns_enum-ns_options/
**/
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
extern NSString *const kHttpdnsReachabilityChangedNotification;
typedef NS_ENUM(NSInteger, HttpdnsNetworkStatus) {
// Apple NetworkStatus Compatible Names.
HttpdnsNotReachable = 0,
HttpdnsReachableViaWiFi = 1,
HttpdnsReachableVia2G = 2,
HttpdnsReachableVia3G = 3,
HttpdnsReachableVia4G = 4,
HttpdnsReachableVia5G = 5
};
@class HttpdnsReachability;
typedef void (^NetworkReachable)(HttpdnsReachability * reachability);
typedef void (^NetworkUnreachable)(HttpdnsReachability * reachability);
typedef void (^NetworkReachability)(HttpdnsReachability * reachability, SCNetworkConnectionFlags flags);
@interface HttpdnsReachability : NSObject
@property (nonatomic, copy) NetworkReachable reachableBlock;
@property (nonatomic, copy) NetworkUnreachable unreachableBlock;
@property (nonatomic, copy) NetworkReachability reachabilityBlock;
@property (nonatomic, assign) BOOL reachableOnWWAN;
+(instancetype)sharedInstance;
+(instancetype)reachabilityWithHostname:(NSString*)hostname;
// This is identical to the function above, but is here to maintain
//compatibility with Apples original code. (see .m)
+(instancetype)reachabilityWithHostName:(NSString*)hostname;
+(instancetype)reachabilityForInternetConnection;
+(instancetype)reachabilityWithAddress:(void *)hostAddress;
+(instancetype)reachabilityForLocalWiFi;
+(instancetype)reachabilityWithURL:(NSURL*)url;
-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
-(BOOL)startNotifier;
-(void)stopNotifier;
-(BOOL)isReachable;
-(BOOL)isReachableViaWWAN;
-(BOOL)isReachableViaWiFi;
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired; // Identical DDG variant.
-(BOOL)connectionRequired; // Apple's routine.
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand;
// Is user intervention required?
-(BOOL)isInterventionRequired;
-(HttpdnsNetworkStatus)currentReachabilityStatus;
-(SCNetworkReachabilityFlags)reachabilityFlags;
-(NSString*)currentReachabilityString;
-(NSString*)currentReachabilityFlags;
@end

View File

@@ -0,0 +1,559 @@
/*
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#import "HttpdnsReachability.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
NSString *const kHttpdnsReachabilityChangedNotification = @"kHttpdnsReachabilityChangedNotification";
static CTTelephonyNetworkInfo *networkInfo;
@interface HttpdnsReachability ()
@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
@property (nonatomic, strong) id reachabilityObject;
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
@end
static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags)
{
return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
#if TARGET_OS_IPHONE
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
#else
'X',
#endif
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
}
// Start listening for reachability notifications on the current run loop
static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target)
HttpdnsReachability *reachability = ((__bridge HttpdnsReachability*)info);
// We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
// but what the heck eh?
@autoreleasepool
{
[reachability reachabilityChanged:flags];
}
}
@implementation HttpdnsReachability
#pragma mark - Class Constructor Methods
+(instancetype)sharedInstance
{
static HttpdnsReachability *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
instance = [HttpdnsReachability reachabilityWithHostname:@"www.taobao.com"];
});
return instance;
}
+(instancetype)reachabilityWithHostName:(NSString*)hostname
{
return [HttpdnsReachability reachabilityWithHostname:hostname];
}
+(instancetype)reachabilityWithHostname:(NSString*)hostname
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityWithAddress:(void *)hostAddress
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityForInternetConnection
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
+(instancetype)reachabilityForLocalWiFi
{
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
return [self reachabilityWithAddress:&localWifiAddress];
}
+(instancetype)reachabilityWithURL:(NSURL*)url
{
id reachability;
NSString *host = url.host;
BOOL isIpAddress = [self isIpAddress:host];
if (isIpAddress)
{
NSNumber *port = url.port ?: [url.scheme isEqualToString:@"https"] ? @(443) : @(80);
struct sockaddr_in address;
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
address.sin_port = htons([port intValue]);
address.sin_addr.s_addr = inet_addr([host UTF8String]);
reachability = [self reachabilityWithAddress:&address];
}
else
{
reachability = [self reachabilityWithHostname:host];
}
return reachability;
}
+(BOOL)isIpAddress:(NSString*)host
{
struct in_addr pin;
return 1 == inet_aton([host UTF8String], &pin);
}
// Initialization methods
-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
-(void)dealloc
{
[self stopNotifier];
if(self.reachabilityRef)
{
CFRelease(self.reachabilityRef);
self.reachabilityRef = nil;
}
self.reachableBlock = nil;
self.unreachableBlock = nil;
self.reachabilityBlock = nil;
self.reachabilitySerialQueue = nil;
}
#pragma mark - Notifier Methods
// Notifier
// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
-(BOOL)startNotifier
{
// allow start notifier to be called multiple times
if(self.reachabilityObject && (self.reachabilityObject == self))
{
return YES;
}
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
context.info = (__bridge void *)self;
if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
{
// Set it as our reachability queue, which will retain the queue
if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
{
// this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
// woah
self.reachabilityObject = self;
return YES;
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif
// UH OH - FAILURE - stop any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
}
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
}
// if we get here we fail at the internet
self.reachabilityObject = nil;
return NO;
}
-(void)stopNotifier
{
// First stop, any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
// Unregister target from the GCD serial dispatch queue.
SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
self.reachabilityObject = nil;
}
#pragma mark - reachability tests
// This is for the case where you flick the airplane mode;
// you end up getting something like this:
//Reachability: WR ct-----
//Reachability: -- -------
//Reachability: WR ct-----
//Reachability: -- -------
// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
BOOL connectionUP = YES;
if(!(flags & kSCNetworkReachabilityFlagsReachable))
connectionUP = NO;
if( (flags & testcase) == testcase )
connectionUP = NO;
#if TARGET_OS_IPHONE
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
// We're on 3G.
if(!self.reachableOnWWAN)
{
// We don't want to connect when on 3G.
connectionUP = NO;
}
}
#endif
return connectionUP;
}
-(BOOL)isReachable
{
SCNetworkReachabilityFlags flags;
if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
return NO;
return [self isReachableWithFlags:flags];
}
-(BOOL)isReachableViaWWAN
{
#if TARGET_OS_IPHONE
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're REACHABLE
if(flags & kSCNetworkReachabilityFlagsReachable)
{
// Now, check we're on WWAN
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
return YES;
}
}
}
#endif
return NO;
}
-(BOOL)isReachableViaWiFi
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're reachable
if((flags & kSCNetworkReachabilityFlagsReachable))
{
#if TARGET_OS_IPHONE
// Check we're NOT on WWAN
if((flags & kSCNetworkReachabilityFlagsIsWWAN))
{
return NO;
}
#endif
return YES;
}
}
return NO;
}
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired
{
return [self connectionRequired];
}
-(BOOL)connectionRequired
{
SCNetworkReachabilityFlags flags;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
}
return NO;
}
// Is user intervention required?
-(BOOL)isInterventionRequired
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired));
}
return NO;
}
#pragma mark - reachability status stuff
-(HttpdnsNetworkStatus)currentReachabilityStatus
{
if([self isReachable])
{
if([self isReachableViaWiFi])
{
return HttpdnsReachableViaWiFi;
}
#if TARGET_OS_IPHONE
NSString *nettype = networkInfo.currentRadioAccessTechnology;
if (nettype)
{
if ([CTRadioAccessTechnologyGPRS isEqualToString:nettype] ||
[CTRadioAccessTechnologyEdge isEqualToString:nettype] ||
[CTRadioAccessTechnologyCDMA1x isEqualToString:nettype])
{
return HttpdnsReachableVia2G;
}
else if ([CTRadioAccessTechnologyLTE isEqualToString:nettype])
{
return HttpdnsReachableVia4G;
}
else if ([CTRadioAccessTechnologyWCDMA isEqualToString:nettype] ||
[CTRadioAccessTechnologyHSDPA isEqualToString:nettype] ||
[CTRadioAccessTechnologyHSUPA isEqualToString:nettype] ||
[CTRadioAccessTechnologyCDMAEVDORev0 isEqualToString:nettype] ||
[CTRadioAccessTechnologyCDMAEVDORevA isEqualToString:nettype] ||
[CTRadioAccessTechnologyCDMAEVDORevB isEqualToString:nettype] ||
[CTRadioAccessTechnologyeHRPD isEqualToString:nettype])
{
return HttpdnsReachableVia3G;
}
else
{
return HttpdnsReachableVia5G;
}
}
// 使广4G
return HttpdnsReachableVia4G;
#endif
}
return HttpdnsNotReachable;
}
-(SCNetworkReachabilityFlags)reachabilityFlags
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return flags;
}
return 0;
}
-(NSString*)currentReachabilityString
{
HttpdnsNetworkStatus temp = [self currentReachabilityStatus];
if(temp == HttpdnsReachableVia2G)
{
return NSLocalizedString(@"2G", @"");
}
if(temp == HttpdnsReachableVia3G)
{
return NSLocalizedString(@"3G", @"");
}
if(temp == HttpdnsReachableVia4G)
{
return NSLocalizedString(@"4G", @"");
}
if(temp == HttpdnsReachableVia5G)
{
return NSLocalizedString(@"5G", @"");
}
if (temp == HttpdnsReachableViaWiFi)
{
// wifi
return NSLocalizedString(@"wifi", @"");
}
return NSLocalizedString(@"unknown", @"");
}
-(NSString*)currentReachabilityFlags
{
return reachabilityFlags([self reachabilityFlags]);
}
#pragma mark - Callback function calls this method
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
if([self isReachableWithFlags:flags])
{
if(self.reachableBlock)
{
self.reachableBlock(self);
}
}
else
{
if(self.unreachableBlock)
{
self.unreachableBlock(self);
}
}
if(self.reachabilityBlock)
{
self.reachabilityBlock(self, flags);
}
// this makes sure the change notification happens on the MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kHttpdnsReachabilityChangedNotification
object:self];
});
}
#pragma mark - Debug Description
- (NSString *) description
{
NSString *description = [NSString stringWithFormat:@"<%@: %p (%@)>",
NSStringFromClass([self class]), self, [self currentReachabilityFlags]];
return description;
}
@end

View File

@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import <Foundation/Foundation.h>
@class HttpDnsService;
@class HttpdnsHostObject;
@interface HttpdnsUtil : NSObject
+ (BOOL)isIPv4Address:(NSString *)addr;
+ (BOOL)isIPv6Address:(NSString *)addr;
+ (BOOL)isAnIP:(NSString *)candidate;
+ (BOOL)isAHost:(NSString *)host;
+ (void)warnMainThreadIfNecessary;
+ (NSDictionary *)getValidDictionaryFromJson:(id)jsonValue;
+ (BOOL)isEmptyArray:(NSArray *)inputArr;
+ (BOOL)isNotEmptyArray:(NSArray *)inputArr;
+ (BOOL)isEmptyString:(NSString *)inputStr;
+ (BOOL)isNotEmptyString:(NSString *)inputStr;
+ (BOOL)isValidJSON:(id)JSON;
+ (BOOL)isEmptyDictionary:(NSDictionary *)inputDict;
+ (BOOL)isNotEmptyDictionary:(NSDictionary *)inputDict;
+ (NSArray *)joinArrays:(NSArray *)array1 withArray:(NSArray *)array2;
+ (NSString *)getMD5StringFrom:(NSString *)originString;
+ (NSString *)URLEncodedString:(NSString *)str;
+ (NSString *)generateSessionID;
+ (NSString *)generateUserAgent;
+ (NSData *)encryptDataAESCBC:(NSData *)plaintext
withKey:(NSData *)key
error:(NSError **)error;
+ (NSData *)decryptDataAESCBC:(NSData *)ciphertext
withKey:(NSData *)key
error:(NSError **)error;
+ (NSString *)hexStringFromData:(NSData *)data;
+ (NSData *)dataFromHexString:(NSString *)hexString;
+ (NSString *)hmacSha256:(NSString *)data key:(NSString *)key;
+ (void)processCustomTTL:(HttpdnsHostObject *)hostObject forHost:(NSString *)host;
+ (void)processCustomTTL:(HttpdnsHostObject *)hostObject forHost:(NSString *)host service:(HttpDnsService *)service;
@end

View File

@@ -0,0 +1,471 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import <UIKit/UIKit.h>
#import "HttpdnsUtil.h"
#import "HttpdnsLog_Internal.h"
#import "CommonCrypto/CommonCrypto.h"
#import "arpa/inet.h"
#import "HttpdnsPublicConstant.h"
#import "httpdnsReachability.h"
#import "HttpdnsInternalConstant.h"
#import "HttpdnsHostObject.h"
#import "HttpdnsService.h"
@implementation HttpdnsUtil
+ (BOOL)isIPv4Address:(NSString *)addr {
if (!addr) {
return NO;
}
struct in_addr dst;
return inet_pton(AF_INET, [addr UTF8String], &(dst.s_addr)) == 1;
}
+ (BOOL)isIPv6Address:(NSString *)addr {
if (!addr) {
return NO;
}
struct in6_addr dst6;
return inet_pton(AF_INET6, [addr UTF8String], &dst6) == 1;
}
+ (BOOL)isAnIP:(NSString *)candidate {
if ([self isIPv4Address:candidate]) {
return YES;
}
if ([self isIPv6Address:candidate]) {
return YES;
}
return NO;
}
+ (BOOL)isAHost:(NSString *)host {
static dispatch_once_t once_token;
static NSRegularExpression *hostExpression = nil;
dispatch_once(&once_token, ^{
hostExpression = [[NSRegularExpression alloc] initWithPattern:@"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" options:NSRegularExpressionCaseInsensitive error:nil];
});
if (!host.length) {
return NO;
}
NSTextCheckingResult *checkResult = [hostExpression firstMatchInString:host options:0 range:NSMakeRange(0, [host length])];
if (checkResult.range.length == [host length]) {
return YES;
} else {
return NO;
}
}
+ (void)warnMainThreadIfNecessary {
if ([NSThread isMainThread]) {
HttpdnsLogDebug("Warning: A long-running Paas operation is being executed on the main thread.");
}
}
+ (NSDictionary *)getValidDictionaryFromJson:(id)jsonValue {
NSDictionary *dictionaryValueFromJson = nil;
if ([jsonValue isKindOfClass:[NSDictionary class]]) {
if ([(NSDictionary *)jsonValue allKeys].count > 0) {
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:jsonValue];
@try {
[self removeNSNullValueFromDictionary:mutableDict];
} @catch (NSException *exception) {}
dictionaryValueFromJson = [jsonValue copy];
}
}
return dictionaryValueFromJson;
}
+ (void)removeNSNullValueFromArray:(NSMutableArray *)array {
NSMutableArray *objToRemove = nil;
NSMutableIndexSet *indexToReplace = [[NSMutableIndexSet alloc] init];
NSMutableArray *objForReplace = [[NSMutableArray alloc] init];
for (int i = 0; i < array.count; ++i) {
id value = [array objectAtIndex:i];
if ([value isKindOfClass:[NSNull class]]) {
if (!objToRemove) {
objToRemove = [[NSMutableArray alloc] init];
}
[objToRemove addObject:value];
} else if ([value isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:value];
[self removeNSNullValueFromDictionary:mutableDict];
[indexToReplace addIndex:i];
[objForReplace addObject:mutableDict];
} else if ([value isKindOfClass:[NSArray class]]) {
NSMutableArray *v = [value mutableCopy];
[self removeNSNullValueFromArray:v];
[indexToReplace addIndex:i];
[objForReplace addObject:v];
}
}
[array replaceObjectsAtIndexes:indexToReplace withObjects:objForReplace];
if (objToRemove) {
[array removeObjectsInArray:objToRemove];
}
}
+ (void)removeNSNullValueFromDictionary:(NSMutableDictionary *)dict {
for (id key in [dict allKeys]) {
id value = [dict objectForKey:key];
if ([value isKindOfClass:[NSNull class]]) {
[dict removeObjectForKey:key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:value];
[self removeNSNullValueFromDictionary:mutableDict];
[dict setObject:mutableDict forKey:key];
} else if ([value isKindOfClass:[NSArray class]]) {
NSMutableArray *v = [value mutableCopy];
[self removeNSNullValueFromArray:v];
[dict setObject:v forKey:key];
}
}
}
+ (BOOL)isEmptyArray:(NSArray *)inputArr {
if (!inputArr) {
return YES;
}
return [inputArr count] == 0;
}
+ (BOOL)isNotEmptyArray:(NSArray *)inputArr {
return ![self isEmptyArray:inputArr];
}
+ (BOOL)isEmptyString:(NSString *)inputStr {
if (!inputStr) {
return YES;
}
return [inputStr length] == 0;
}
+ (BOOL)isNotEmptyString:(NSString *)inputStr {
return ![self isEmptyString:inputStr];
}
+ (BOOL)isValidJSON:(id)JSON {
BOOL isValid;
@try {
isValid = ([JSON isKindOfClass:[NSDictionary class]] || [JSON isKindOfClass:[NSArray class]]);
} @catch (NSException *ignore) {
}
return isValid;
}
+ (BOOL)isEmptyDictionary:(NSDictionary *)inputDict {
if (!inputDict) {
return YES;
}
return [inputDict count] == 0;
}
+ (BOOL)isNotEmptyDictionary:(NSDictionary *)inputDict {
return ![self isEmptyDictionary:inputDict];
}
+ (NSArray *)joinArrays:(NSArray *)array1 withArray:(NSArray *)array2 {
NSMutableArray *resultArray = [array1 mutableCopy];
[resultArray addObjectsFromArray:array2];
return [resultArray copy];
}
+ (id)convertJsonDataToObject:(NSData *)jsonData {
if (jsonData) {
NSError *error;
id jsonObj = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
if (!error) {
return jsonObj;
}
}
return nil;
}
+ (NSString *)getMD5StringFrom:(NSString *)originString {
const char * pointer = [originString UTF8String];
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(pointer, (CC_LONG)strlen(pointer), md5Buffer);
NSMutableString *string = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[string appendFormat:@"%02x",md5Buffer[i]];
return [string copy];
}
+ (NSString *)URLEncodedString:(NSString *)str {
if (str) {
return [str stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet characterSetWithCharactersInString:@"!*'();:@&=+$,/?%#[]\""].invertedSet];
}
return nil;
}
/**
sessionId
App
sessionId12base62
*/
+ (NSString *)generateSessionID {
static NSString *sessionId = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *alphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
NSUInteger length = alphabet.length;
if (![HttpdnsUtil isNotEmptyString:sessionId]) {
NSMutableString *mSessionId = [NSMutableString string];
for (int i = 0; i < 12; i++) {
[mSessionId appendFormat:@"%@", [alphabet substringWithRange:NSMakeRange(arc4random() % length, 1)]];
}
sessionId = [mSessionId copy];
}
});
return sessionId;
}
+ (NSString *)generateUserAgent {
UIDevice *device = [UIDevice currentDevice];
NSString *systemName = [device systemName];
NSString *systemVersion = [device systemVersion];
NSString *model = [device model];
NSString *userAgent = [NSString stringWithFormat:@"HttpdnsSDK/%@ (%@; iOS %@; %@)", HTTPDNS_IOS_SDK_VERSION, model, systemVersion, systemName];
return userAgent;
}
+ (NSData *)encryptDataAESCBC:(NSData *)plaintext
withKey:(NSData *)key
error:(NSError **)error {
//
if (plaintext == nil || [plaintext length] == 0 || key == nil || [key length] != kCCKeySizeAES128) {
if (error) {
*error = [NSError errorWithDomain:ALICLOUD_HTTPDNS_ERROR_DOMAIN
code:ALICLOUD_HTTPDNS_ENCRYPT_INVALID_PARAMS_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Invalid input parameters"}];
}
return nil;
}
// CBC128bit(16)IV
NSMutableData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
int result = SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, iv.mutableBytes);
if (result != 0) {
if (error) {
*error = [NSError errorWithDomain:ALICLOUD_HTTPDNS_ERROR_DOMAIN
code:ALICLOUD_HTTPDNS_ENCRYPT_RANDOM_IV_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Failed to generate random IV"}];
}
return nil;
}
// ()
size_t bufferSize = [plaintext length] + kCCBlockSizeAES128;
size_t encryptedSize = 0;
//
NSMutableData *cipherData = [NSMutableData dataWithLength:bufferSize];
//
// AESPKCS5PaddingPKCS7Padding
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES,
kCCOptionPKCS7Padding,
[key bytes],
[key length],
[iv bytes],
[plaintext bytes],
[plaintext length],
[cipherData mutableBytes],
[cipherData length],
&encryptedSize);
if (cryptStatus != kCCSuccess) {
if (error) {
*error = [NSError errorWithDomain:ALICLOUD_HTTPDNS_ERROR_DOMAIN
code:ALICLOUD_HTTPDNS_ENCRYPT_FAILED_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Encryption failed with status: %d", cryptStatus]}];
}
return nil;
}
//
[cipherData setLength:encryptedSize];
// IV
NSMutableData *resultData = [NSMutableData dataWithData:iv];
[resultData appendData:cipherData];
return resultData;
}
+ (NSString *)hexStringFromData:(NSData *)data {
if (!data || data.length == 0) {
return nil;
}
NSMutableString *hexString = [NSMutableString stringWithCapacity:data.length * 2];
const unsigned char *bytes = data.bytes;
for (NSInteger i = 0; i < data.length; i++) {
[hexString appendFormat:@"%02x", bytes[i]];
}
return [hexString copy];
}
+ (NSData *)dataFromHexString:(NSString *)hexString {
if (!hexString || hexString.length == 0) {
return nil;
}
//
NSString *cleanedString = [hexString stringByReplacingOccurrencesOfString:@" " withString:@""];
//
if (cleanedString.length % 2 != 0) {
return nil;
}
NSMutableData *data = [NSMutableData dataWithCapacity:cleanedString.length / 2];
for (NSUInteger i = 0; i < cleanedString.length; i += 2) {
NSString *byteString = [cleanedString substringWithRange:NSMakeRange(i, 2)];
NSScanner *scanner = [NSScanner scannerWithString:byteString];
unsigned int byteValue;
if (![scanner scanHexInt:&byteValue]) {
return nil;
}
uint8_t byte = (uint8_t)byteValue;
[data appendBytes:&byte length:1];
}
return data;
}
+ (NSString *)hmacSha256:(NSString *)data key:(NSString *)key {
if (!data || !key) {
return nil;
}
// NSData
NSData *keyData = [self dataFromHexString:key];
if (!keyData) {
return nil;
}
//
NSData *dataToSign = [data dataUsingEncoding:NSUTF8StringEncoding];
// HMAC
uint8_t digestBytes[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, dataToSign.bytes, dataToSign.length, digestBytes);
//
NSData *hmacData = [NSData dataWithBytes:digestBytes length:CC_SHA256_DIGEST_LENGTH];
//
return [self hexStringFromData:hmacData];
}
+ (NSData *)decryptDataAESCBC:(NSData *)ciphertext
withKey:(NSData *)key
error:(NSError **)error {
//
if (ciphertext == nil || [ciphertext length] <= kCCBlockSizeAES128 || key == nil || [key length] != kCCKeySizeAES128) {
if (error) {
*error = [NSError errorWithDomain:ALICLOUD_HTTPDNS_ERROR_DOMAIN
code:ALICLOUD_HTTPDNS_ENCRYPT_INVALID_PARAMS_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Invalid input parameters for decryption"}];
}
return nil;
}
// IV16
NSData *iv = [ciphertext subdataWithRange:NSMakeRange(0, kCCBlockSizeAES128)];
NSData *actualCiphertext = [ciphertext subdataWithRange:NSMakeRange(kCCBlockSizeAES128, ciphertext.length - kCCBlockSizeAES128)];
//
size_t bufferSize = actualCiphertext.length + kCCBlockSizeAES128;
size_t decryptedSize = 0;
//
NSMutableData *decryptedData = [NSMutableData dataWithLength:bufferSize];
//
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES,
kCCOptionPKCS7Padding,
[key bytes],
[key length],
[iv bytes],
[actualCiphertext bytes],
[actualCiphertext length],
[decryptedData mutableBytes],
[decryptedData length],
&decryptedSize);
if (cryptStatus != kCCSuccess) {
if (error) {
*error = [NSError errorWithDomain:ALICLOUD_HTTPDNS_ERROR_DOMAIN
code:ALICLOUD_HTTPDNS_ENCRYPT_FAILED_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Decryption failed with status: %d", cryptStatus]}];
}
return nil;
}
//
[decryptedData setLength:decryptedSize];
return decryptedData;
}
+ (void)processCustomTTL:(HttpdnsHostObject *)hostObject forHost:(NSString *)host {
[self processCustomTTL:hostObject forHost:host service:[HttpDnsService sharedInstance]];
}
+ (void)processCustomTTL:(HttpdnsHostObject *)hostObject forHost:(NSString *)host service:(HttpDnsService *)service {
if (!hostObject || !host) {
return;
}
HttpDnsService *dnsService = service ?: [HttpDnsService sharedInstance];
if (dnsService.ttlDelegate && [dnsService.ttlDelegate respondsToSelector:@selector(httpdnsHost:ipType:ttl:)]) {
if ([self isNotEmptyArray:[hostObject getV4Ips]]) {
int64_t customV4TTL = [dnsService.ttlDelegate httpdnsHost:host ipType:AlicloudHttpDNS_IPTypeV4 ttl:hostObject.v4ttl];
if (customV4TTL > 0) {
hostObject.v4ttl = customV4TTL;
}
}
if ([self isNotEmptyArray:[hostObject getV6Ips]]) {
int64_t customV6TTL = [dnsService.ttlDelegate httpdnsHost:host ipType:AlicloudHttpDNS_IPTypeV6 ttl:hostObject.v6ttl];
if (customV6TTL > 0) {
hostObject.v6ttl = customV6TTL;
}
}
}
}
@end