diff --git a/Podfile b/Podfile index 14554cdd..86444868 100644 --- a/Podfile +++ b/Podfile @@ -62,6 +62,8 @@ target 'xplan-ios' do pod 'QGVAPlayer' pod 'IQKeyboardManager', '~> 6.5.5' pod 'TZImagePickerController' + #上传音乐 + pod 'CocoaAsyncSocket',:modular_headers => true #调试 pod 'LookinServer', :configurations => ['Debug'] diff --git a/Podfile.lock b/Podfile.lock index ddd69082..2ad079ad 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -17,6 +17,7 @@ PODS: - AgoraRtcEngine_iOS (3.0.1.1) - BaiduMobStatCodeless (5.3.5) - Base64 (1.1.2) + - CocoaAsyncSocket (7.6.5) - FFPopup (1.1.5) - HappyDNS (0.3.17) - IAPHelper (1.1) @@ -92,6 +93,7 @@ DEPENDENCIES: - AgoraRtcEngine_iOS (~> 3.0.1) - BaiduMobStatCodeless (~> 5.3.5) - Base64 + - CocoaAsyncSocket - FFPopup - IAPHelper - IQKeyboardManager (~> 6.5.5) @@ -128,6 +130,7 @@ DEPENDENCIES: SPEC REPOS: https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: + - CocoaAsyncSocket - IQKeyboardManager - JXPagingView - NIMSDK_LITE @@ -178,6 +181,7 @@ SPEC CHECKSUMS: AgoraRtcEngine_iOS: 8ccceaaecff2e80ab28fcd33f3dfd2b417eb5365 BaiduMobStatCodeless: b3c73335cc1a5d464540111ff08857fc33cae656 Base64: cecfb41a004124895a7bcee567a89bae5a89d49b + CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 FFPopup: a208dcee8db3e54ec4a88fcd6481f6f5d85b7a83 HappyDNS: 848ef73e24f2b0e2752064223ce2dc0dd88900ea IAPHelper: fd74f53b0ac142eed085777b88b86a11746a2dd4 @@ -215,6 +219,6 @@ SPEC CHECKSUMS: YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 -PODFILE CHECKSUM: d02ba4da989cc65b5e850b0ff42a9ef68dcc1f1e +PODFILE CHECKSUM: 034aba5260596f012774aa92cf2207105b5af99f COCOAPODS: 1.11.2 diff --git a/xplan-ios.xcodeproj/project.pbxproj b/xplan-ios.xcodeproj/project.pbxproj index 3f4a8b3c..bf13268c 100644 --- a/xplan-ios.xcodeproj/project.pbxproj +++ b/xplan-ios.xcodeproj/project.pbxproj @@ -728,6 +728,38 @@ E8E70D9226F2F60C00F03460 /* XPMineItemModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E70D9126F2F60C00F03460 /* XPMineItemModel.m */; }; E8E7DAE82744F5EF00C631CC /* XPGiftStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E7DAE72744F5EF00C631CC /* XPGiftStorage.m */; }; E8E7DAEB2745158500C631CC /* XPGiftUserInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E7DAEA2745158500C631CC /* XPGiftUserInfoModel.m */; }; + E8E859C128264C2300EE4857 /* MyHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8598428264C2200EE4857 /* MyHTTPConnection.m */; }; + E8E859C228264C2300EE4857 /* HTTPErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8598D28264C2200EE4857 /* HTTPErrorResponse.m */; }; + E8E859C328264C2300EE4857 /* HTTPDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8598E28264C2200EE4857 /* HTTPDataResponse.m */; }; + E8E859C428264C2300EE4857 /* HTTPDynamicFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599028264C2200EE4857 /* HTTPDynamicFileResponse.m */; }; + E8E859C528264C2300EE4857 /* HTTPFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599128264C2200EE4857 /* HTTPFileResponse.m */; }; + E8E859C628264C2300EE4857 /* HTTPAsyncFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599228264C2200EE4857 /* HTTPAsyncFileResponse.m */; }; + E8E859C728264C2300EE4857 /* HTTPRedirectResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599328264C2200EE4857 /* HTTPRedirectResponse.m */; }; + E8E859C828264C2300EE4857 /* HTTPMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599928264C2200EE4857 /* HTTPMessage.m */; }; + E8E859C928264C2300EE4857 /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599A28264C2200EE4857 /* HTTPConnection.m */; }; + E8E859CA28264C2300EE4857 /* WebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E8599B28264C2200EE4857 /* WebSocket.m */; }; + E8E859CB28264C2300EE4857 /* MultipartMessageHeaderField.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A028264C2200EE4857 /* MultipartMessageHeaderField.m */; }; + E8E859CC28264C2300EE4857 /* MultipartFormDataParser.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A128264C2200EE4857 /* MultipartFormDataParser.m */; }; + E8E859CD28264C2300EE4857 /* MultipartMessageHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A228264C2200EE4857 /* MultipartMessageHeader.m */; }; + E8E859CE28264C2300EE4857 /* HTTPAuthenticationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A428264C2200EE4857 /* HTTPAuthenticationRequest.m */; }; + E8E859CF28264C2300EE4857 /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A628264C2200EE4857 /* DDNumber.m */; }; + E8E859D028264C2300EE4857 /* DDData.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859A728264C2200EE4857 /* DDData.m */; }; + E8E859D128264C2300EE4857 /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859AA28264C2200EE4857 /* DDRange.m */; }; + E8E859D228264C2300EE4857 /* HTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859AC28264C2200EE4857 /* HTTPServer.m */; }; + E8E859D328264C2300EE4857 /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859B128264C2200EE4857 /* DDTTYLogger.m */; }; + E8E859D428264C2300EE4857 /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859B428264C2200EE4857 /* DDASLLogger.m */; }; + E8E859D528264C2300EE4857 /* ContextFilterLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859B928264C2200EE4857 /* ContextFilterLogFormatter.m */; }; + E8E859D628264C2300EE4857 /* DispatchQueueLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859BA28264C2200EE4857 /* DispatchQueueLogFormatter.m */; }; + E8E859D728264C2300EE4857 /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859BB28264C2200EE4857 /* DDLog.m */; }; + E8E859D828264C2300EE4857 /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859BD28264C2200EE4857 /* DDAbstractDatabaseLogger.m */; }; + E8E859D928264C2300EE4857 /* DDFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859BE28264C2200EE4857 /* DDFileLogger.m */; }; + E8E859DA28264C2300EE4857 /* SJXCSMIPHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859C028264C2200EE4857 /* SJXCSMIPHelper.m */; }; + E8E859DF28264D7B00EE4857 /* js in Resources */ = {isa = PBXBuildFile; fileRef = E8E859DC28264D7B00EE4857 /* js */; }; + E8E859E028264D7B00EE4857 /* css in Resources */ = {isa = PBXBuildFile; fileRef = E8E859DD28264D7B00EE4857 /* css */; }; + E8E859E128264D7B00EE4857 /* images in Resources */ = {isa = PBXBuildFile; fileRef = E8E859DE28264D7B00EE4857 /* images */; }; + E8E859E428264D8800EE4857 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = E8E859E228264D8800EE4857 /* index.html */; }; + E8E859E528264D8800EE4857 /* upload.html in Resources */ = {isa = PBXBuildFile; fileRef = E8E859E328264D8800EE4857 /* upload.html */; }; + E8E859E928264F4500EE4857 /* XPRoomTransferMusicViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E859E828264F4500EE4857 /* XPRoomTransferMusicViewController.m */; }; E8EE827D272B9A2300A17217 /* XPRoomSendTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8EE827C272B9A2300A17217 /* XPRoomSendTextView.m */; }; E8EEB8F226FC2050007C6EBA /* SDPhotoBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = E8EEB8EC26FC2050007C6EBA /* SDPhotoBrowser.m */; }; E8EEB8F326FC2050007C6EBA /* SDWaitingView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8EEB8ED26FC2050007C6EBA /* SDWaitingView.m */; }; @@ -2213,6 +2245,67 @@ E8E7DAE72744F5EF00C631CC /* XPGiftStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPGiftStorage.m; sourceTree = ""; }; E8E7DAE92745158500C631CC /* XPGiftUserInfoModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPGiftUserInfoModel.h; sourceTree = ""; }; E8E7DAEA2745158500C631CC /* XPGiftUserInfoModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPGiftUserInfoModel.m; sourceTree = ""; }; + E8E8598428264C2200EE4857 /* MyHTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyHTTPConnection.m; sourceTree = ""; }; + E8E8598628264C2200EE4857 /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = ""; }; + E8E8598728264C2200EE4857 /* HTTPLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPLogging.h; sourceTree = ""; }; + E8E8598828264C2200EE4857 /* HTTPMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPMessage.h; sourceTree = ""; }; + E8E8598928264C2200EE4857 /* WebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocket.h; sourceTree = ""; }; + E8E8598A28264C2200EE4857 /* HTTPAuthenticationRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPAuthenticationRequest.h; sourceTree = ""; }; + E8E8598C28264C2200EE4857 /* HTTPAsyncFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPAsyncFileResponse.h; sourceTree = ""; }; + E8E8598D28264C2200EE4857 /* HTTPErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPErrorResponse.m; sourceTree = ""; }; + E8E8598E28264C2200EE4857 /* HTTPDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPDataResponse.m; sourceTree = ""; }; + E8E8598F28264C2200EE4857 /* HTTPRedirectResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRedirectResponse.h; sourceTree = ""; }; + E8E8599028264C2200EE4857 /* HTTPDynamicFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPDynamicFileResponse.m; sourceTree = ""; }; + E8E8599128264C2200EE4857 /* HTTPFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPFileResponse.m; sourceTree = ""; }; + E8E8599228264C2200EE4857 /* HTTPAsyncFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPAsyncFileResponse.m; sourceTree = ""; }; + E8E8599328264C2200EE4857 /* HTTPRedirectResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPRedirectResponse.m; sourceTree = ""; }; + E8E8599428264C2200EE4857 /* HTTPDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPDataResponse.h; sourceTree = ""; }; + E8E8599528264C2200EE4857 /* HTTPErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPErrorResponse.h; sourceTree = ""; }; + E8E8599628264C2200EE4857 /* HTTPFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPFileResponse.h; sourceTree = ""; }; + E8E8599728264C2200EE4857 /* HTTPDynamicFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPDynamicFileResponse.h; sourceTree = ""; }; + E8E8599828264C2200EE4857 /* HTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPServer.h; sourceTree = ""; }; + E8E8599928264C2200EE4857 /* HTTPMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPMessage.m; sourceTree = ""; }; + E8E8599A28264C2200EE4857 /* HTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnection.m; sourceTree = ""; }; + E8E8599B28264C2200EE4857 /* WebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebSocket.m; sourceTree = ""; }; + E8E8599C28264C2200EE4857 /* HTTPResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPResponse.h; sourceTree = ""; }; + E8E8599E28264C2200EE4857 /* MultipartFormDataParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartFormDataParser.h; sourceTree = ""; }; + E8E8599F28264C2200EE4857 /* MultipartMessageHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeader.h; sourceTree = ""; }; + E8E859A028264C2200EE4857 /* MultipartMessageHeaderField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeaderField.m; sourceTree = ""; }; + E8E859A128264C2200EE4857 /* MultipartFormDataParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartFormDataParser.m; sourceTree = ""; }; + E8E859A228264C2200EE4857 /* MultipartMessageHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeader.m; sourceTree = ""; }; + E8E859A328264C2200EE4857 /* MultipartMessageHeaderField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeaderField.h; sourceTree = ""; }; + E8E859A428264C2200EE4857 /* HTTPAuthenticationRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPAuthenticationRequest.m; sourceTree = ""; }; + E8E859A628264C2200EE4857 /* DDNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDNumber.m; sourceTree = ""; }; + E8E859A728264C2200EE4857 /* DDData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDData.m; sourceTree = ""; }; + E8E859A828264C2200EE4857 /* DDRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDRange.h; sourceTree = ""; }; + E8E859A928264C2200EE4857 /* DDNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDNumber.h; sourceTree = ""; }; + E8E859AA28264C2200EE4857 /* DDRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDRange.m; sourceTree = ""; }; + E8E859AB28264C2200EE4857 /* DDData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDData.h; sourceTree = ""; }; + E8E859AC28264C2200EE4857 /* HTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPServer.m; sourceTree = ""; }; + E8E859AD28264C2200EE4857 /* SJXCSMIPHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SJXCSMIPHelper.h; sourceTree = ""; }; + E8E859AE28264C2200EE4857 /* MyHTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyHTTPConnection.h; sourceTree = ""; }; + E8E859B128264C2200EE4857 /* DDTTYLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDTTYLogger.m; sourceTree = ""; }; + E8E859B228264C2200EE4857 /* DDLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDLog.h; sourceTree = ""; }; + E8E859B328264C2200EE4857 /* DDAbstractDatabaseLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAbstractDatabaseLogger.h; sourceTree = ""; }; + E8E859B428264C2200EE4857 /* DDASLLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDASLLogger.m; sourceTree = ""; }; + E8E859B528264C2200EE4857 /* DDFileLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDFileLogger.h; sourceTree = ""; }; + E8E859B728264C2200EE4857 /* ContextFilterLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextFilterLogFormatter.h; sourceTree = ""; }; + E8E859B828264C2200EE4857 /* DispatchQueueLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DispatchQueueLogFormatter.h; sourceTree = ""; }; + E8E859B928264C2200EE4857 /* ContextFilterLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextFilterLogFormatter.m; sourceTree = ""; }; + E8E859BA28264C2200EE4857 /* DispatchQueueLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DispatchQueueLogFormatter.m; sourceTree = ""; }; + E8E859BB28264C2200EE4857 /* DDLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDLog.m; sourceTree = ""; }; + E8E859BC28264C2200EE4857 /* DDTTYLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDTTYLogger.h; sourceTree = ""; }; + E8E859BD28264C2200EE4857 /* DDAbstractDatabaseLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDAbstractDatabaseLogger.m; sourceTree = ""; }; + E8E859BE28264C2200EE4857 /* DDFileLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDFileLogger.m; sourceTree = ""; }; + E8E859BF28264C2200EE4857 /* DDASLLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDASLLogger.h; sourceTree = ""; }; + E8E859C028264C2200EE4857 /* SJXCSMIPHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SJXCSMIPHelper.m; sourceTree = ""; }; + E8E859DC28264D7B00EE4857 /* js */ = {isa = PBXFileReference; lastKnownFileType = folder; path = js; sourceTree = ""; }; + E8E859DD28264D7B00EE4857 /* css */ = {isa = PBXFileReference; lastKnownFileType = folder; path = css; sourceTree = ""; }; + E8E859DE28264D7B00EE4857 /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = images; sourceTree = ""; }; + E8E859E228264D8800EE4857 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; + E8E859E328264D8800EE4857 /* upload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = upload.html; sourceTree = ""; }; + E8E859E728264F4500EE4857 /* XPRoomTransferMusicViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPRoomTransferMusicViewController.h; sourceTree = ""; }; + E8E859E828264F4500EE4857 /* XPRoomTransferMusicViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPRoomTransferMusicViewController.m; sourceTree = ""; }; E8EE827B272B9A2300A17217 /* XPRoomSendTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPRoomSendTextView.h; sourceTree = ""; }; E8EE827C272B9A2300A17217 /* XPRoomSendTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPRoomSendTextView.m; sourceTree = ""; }; E8EEB8EB26FC2050007C6EBA /* SDPhotoBrowserConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDPhotoBrowserConfig.h; sourceTree = ""; }; @@ -2710,6 +2803,7 @@ 189DD5A726DFA09700AB55B1 /* Tool */ = { isa = PBXGroup; children = ( + E8E8598328264C2200EE4857 /* CocoaHttpServer */, 9B09979F27F19D1C00EB8F14 /* QGVAPlayer */, 9B33E3C827D85379003B0E62 /* File */, E80CBDE727D0C528001E1EC2 /* Timer */, @@ -5160,6 +5254,7 @@ E8AEAED8271413530017FCE0 /* View */ = { isa = PBXGroup; children = ( + E8E859E628264F0000EE4857 /* BackMusic */, 9B6E8570281AAD260041A321 /* RoomRecommend */, 9B1B72A228002F76003FACE9 /* AnchorPK */, 9B92C01A27E0BAEB0044C5EA /* NobleTrumpet */, @@ -6095,6 +6190,153 @@ path = ModuleKit; sourceTree = ""; }; + E8E8598328264C2200EE4857 /* CocoaHttpServer */ = { + isa = PBXGroup; + children = ( + E8E859DB28264D5000EE4857 /* Web */, + E8E8598528264C2200EE4857 /* Core */, + E8E859AE28264C2200EE4857 /* MyHTTPConnection.h */, + E8E8598428264C2200EE4857 /* MyHTTPConnection.m */, + E8E859AD28264C2200EE4857 /* SJXCSMIPHelper.h */, + E8E859C028264C2200EE4857 /* SJXCSMIPHelper.m */, + E8E859AF28264C2200EE4857 /* Vendor */, + ); + path = CocoaHttpServer; + sourceTree = ""; + }; + E8E8598528264C2200EE4857 /* Core */ = { + isa = PBXGroup; + children = ( + E8E859A528264C2200EE4857 /* Categories */, + E8E8598A28264C2200EE4857 /* HTTPAuthenticationRequest.h */, + E8E859A428264C2200EE4857 /* HTTPAuthenticationRequest.m */, + E8E8598628264C2200EE4857 /* HTTPConnection.h */, + E8E8599A28264C2200EE4857 /* HTTPConnection.m */, + E8E8598728264C2200EE4857 /* HTTPLogging.h */, + E8E8598828264C2200EE4857 /* HTTPMessage.h */, + E8E8599928264C2200EE4857 /* HTTPMessage.m */, + E8E8599C28264C2200EE4857 /* HTTPResponse.h */, + E8E8599828264C2200EE4857 /* HTTPServer.h */, + E8E859AC28264C2200EE4857 /* HTTPServer.m */, + E8E8599D28264C2200EE4857 /* Mime */, + E8E8598B28264C2200EE4857 /* Responses */, + E8E8598928264C2200EE4857 /* WebSocket.h */, + E8E8599B28264C2200EE4857 /* WebSocket.m */, + ); + path = Core; + sourceTree = ""; + }; + E8E8598B28264C2200EE4857 /* Responses */ = { + isa = PBXGroup; + children = ( + E8E8598C28264C2200EE4857 /* HTTPAsyncFileResponse.h */, + E8E8598D28264C2200EE4857 /* HTTPErrorResponse.m */, + E8E8598E28264C2200EE4857 /* HTTPDataResponse.m */, + E8E8598F28264C2200EE4857 /* HTTPRedirectResponse.h */, + E8E8599028264C2200EE4857 /* HTTPDynamicFileResponse.m */, + E8E8599128264C2200EE4857 /* HTTPFileResponse.m */, + E8E8599228264C2200EE4857 /* HTTPAsyncFileResponse.m */, + E8E8599328264C2200EE4857 /* HTTPRedirectResponse.m */, + E8E8599428264C2200EE4857 /* HTTPDataResponse.h */, + E8E8599528264C2200EE4857 /* HTTPErrorResponse.h */, + E8E8599628264C2200EE4857 /* HTTPFileResponse.h */, + E8E8599728264C2200EE4857 /* HTTPDynamicFileResponse.h */, + ); + path = Responses; + sourceTree = ""; + }; + E8E8599D28264C2200EE4857 /* Mime */ = { + isa = PBXGroup; + children = ( + E8E8599E28264C2200EE4857 /* MultipartFormDataParser.h */, + E8E8599F28264C2200EE4857 /* MultipartMessageHeader.h */, + E8E859A028264C2200EE4857 /* MultipartMessageHeaderField.m */, + E8E859A128264C2200EE4857 /* MultipartFormDataParser.m */, + E8E859A228264C2200EE4857 /* MultipartMessageHeader.m */, + E8E859A328264C2200EE4857 /* MultipartMessageHeaderField.h */, + ); + path = Mime; + sourceTree = ""; + }; + E8E859A528264C2200EE4857 /* Categories */ = { + isa = PBXGroup; + children = ( + E8E859A628264C2200EE4857 /* DDNumber.m */, + E8E859A728264C2200EE4857 /* DDData.m */, + E8E859A828264C2200EE4857 /* DDRange.h */, + E8E859A928264C2200EE4857 /* DDNumber.h */, + E8E859AA28264C2200EE4857 /* DDRange.m */, + E8E859AB28264C2200EE4857 /* DDData.h */, + ); + path = Categories; + sourceTree = ""; + }; + E8E859AF28264C2200EE4857 /* Vendor */ = { + isa = PBXGroup; + children = ( + E8E859B028264C2200EE4857 /* CocoaLumberjack */, + ); + path = Vendor; + sourceTree = ""; + }; + E8E859B028264C2200EE4857 /* CocoaLumberjack */ = { + isa = PBXGroup; + children = ( + E8E859B328264C2200EE4857 /* DDAbstractDatabaseLogger.h */, + E8E859BD28264C2200EE4857 /* DDAbstractDatabaseLogger.m */, + E8E859BF28264C2200EE4857 /* DDASLLogger.h */, + E8E859B428264C2200EE4857 /* DDASLLogger.m */, + E8E859B528264C2200EE4857 /* DDFileLogger.h */, + E8E859BE28264C2200EE4857 /* DDFileLogger.m */, + E8E859B228264C2200EE4857 /* DDLog.h */, + E8E859BB28264C2200EE4857 /* DDLog.m */, + E8E859BC28264C2200EE4857 /* DDTTYLogger.h */, + E8E859B128264C2200EE4857 /* DDTTYLogger.m */, + E8E859B628264C2200EE4857 /* Extensions */, + ); + path = CocoaLumberjack; + sourceTree = ""; + }; + E8E859B628264C2200EE4857 /* Extensions */ = { + isa = PBXGroup; + children = ( + E8E859B728264C2200EE4857 /* ContextFilterLogFormatter.h */, + E8E859B928264C2200EE4857 /* ContextFilterLogFormatter.m */, + E8E859B828264C2200EE4857 /* DispatchQueueLogFormatter.h */, + E8E859BA28264C2200EE4857 /* DispatchQueueLogFormatter.m */, + ); + path = Extensions; + sourceTree = ""; + }; + E8E859DB28264D5000EE4857 /* Web */ = { + isa = PBXGroup; + children = ( + E8E859E228264D8800EE4857 /* index.html */, + E8E859E328264D8800EE4857 /* upload.html */, + E8E859DD28264D7B00EE4857 /* css */, + E8E859DE28264D7B00EE4857 /* images */, + E8E859DC28264D7B00EE4857 /* js */, + ); + path = Web; + sourceTree = ""; + }; + E8E859E628264F0000EE4857 /* BackMusic */ = { + isa = PBXGroup; + children = ( + E8E859EA28264F4C00EE4857 /* View */, + ); + path = BackMusic; + sourceTree = ""; + }; + E8E859EA28264F4C00EE4857 /* View */ = { + isa = PBXGroup; + children = ( + E8E859E728264F4500EE4857 /* XPRoomTransferMusicViewController.h */, + E8E859E828264F4500EE4857 /* XPRoomTransferMusicViewController.m */, + ); + path = View; + sourceTree = ""; + }; E8EEB8EA26FC2050007C6EBA /* SDPhotoBrowser */ = { isa = PBXGroup; children = ( @@ -6196,6 +6438,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E8E859E128264D7B00EE4857 /* images in Resources */, + E8E859E028264D7B00EE4857 /* css in Resources */, E8937AC7276C3EDE00B2C7E1 /* svga_member_in_lv40.svga in Resources */, E8A1E461276220DA00B294CA /* candyTree.svga in Resources */, E8937AC9276C3EDE00B2C7E1 /* svga_member_in_lv90.svga in Resources */, @@ -6206,8 +6450,10 @@ E8937AC8276C3EDE00B2C7E1 /* svga_member_in_lv70.svga in Resources */, E8DEC98B27637EA50078CB70 /* candyTree_open.svga in Resources */, E8A1E460276220DA00B294CA /* candyTree_banner.svga in Resources */, + E8E859E428264D8800EE4857 /* index.html in Resources */, E8937ACA276C3EDE00B2C7E1 /* svga_member_in_lv50.svga in Resources */, E8937ACD276C3EDE00B2C7E1 /* svga_member_in_lv30.svga in Resources */, + E8E859E528264D8800EE4857 /* upload.html in Resources */, E88863C3278E7BCC004BCFAB /* crossRoomPk_progress.svga in Resources */, E8A1E462276220DA00B294CA /* candyTree_light.svga in Resources */, E8937ACF276C99C300B2C7E1 /* Launch Screen.storyboard in Resources */, @@ -6215,6 +6461,7 @@ 189DD53926DE255600AB55B1 /* Assets.xcassets in Resources */, E8DEC98C27637EA50078CB70 /* candyTree_transform.svga in Resources */, 9B85F3502806A34B006EDF51 /* anchorPK_progress.svga in Resources */, + E8E859DF28264D7B00EE4857 /* js in Resources */, 186A52E726FC559700D67B2C /* yw_1222_0769.jpg in Resources */, E8937ACB276C3EDE00B2C7E1 /* svga_member_in_lv60.svga in Resources */, 186A52E326FC559200D67B2C /* RPSDK.bundle in Resources */, @@ -6303,6 +6550,7 @@ E82D5C7D276B343300858D6D /* YYAnimatedImageView+ImageShow.m in Sources */, E851E45F27FF0FEB002F3ACB /* XPGuildSearchNavView.m in Sources */, E8B846C726FDB45000A777FE /* XPMineUserInfoAlbumProtocol.h in Sources */, + E8E859D128264C2300EE4857 /* DDRange.m in Sources */, E8DEC992276441AA0078CB70 /* XPCandyTreeAnimationModel.m in Sources */, 9B1EF3D527E8294B00554295 /* XPMineDressEmptyCollectionViewCell.m in Sources */, E890BC07273CF1800007C46B /* XPGiftCountCollectionViewCell.m in Sources */, @@ -6313,6 +6561,7 @@ E8A86E0427BA38DB001C21F9 /* SudCommon.m in Sources */, E8EEB90326FC31DC007C6EBA /* XPMineUserInfoProtocol.h in Sources */, E8232603274E4AA0003A3332 /* ThemeColor+UserCard.m in Sources */, + E8E859C228264C2300EE4857 /* HTTPErrorResponse.m in Sources */, E87A24F1272935920086A794 /* XPMessageRemoteExtModel.m in Sources */, E8D34D5028080362009C4835 /* XPMineDataGiftTableViewCell.m in Sources */, 9B2A12E427846D7100CED41B /* NobleRechargeModel.m in Sources */, @@ -6322,6 +6571,7 @@ E8D34D4A28080335009C4835 /* XPMineDataSkillCardTableViewCell.m in Sources */, E87C0AA027D9DE6400CB2241 /* RoomFaceSendInfoModel.m in Sources */, E88B5CB226FB1CFF00DA9178 /* XPMineTeenagerProtocol.h in Sources */, + E8E859C928264C2300EE4857 /* HTTPConnection.m in Sources */, 9BD9A18027A0EFC7004186FE /* XPMineVisitorTableViewCell.m in Sources */, E824546626F5FF6000BE8163 /* XPMineResetPayPasswordProtocol.h in Sources */, E800807227FD6C3D0055A8AB /* XPClanPresenter.m in Sources */, @@ -6372,6 +6622,7 @@ 9BB54966278303EB0090CD26 /* XPNobleCenterNavView.m in Sources */, 9B92A36327980DCC00AD168F /* Api+SkillCard.m in Sources */, E88B5CB526FB20B800DA9178 /* XPMineTeenagerPwdView.m in Sources */, + E8E859C128264C2300EE4857 /* MyHTTPConnection.m in Sources */, 9BDA3E7A27FD43EF00517FE6 /* XPAnchorFansTeamEntranceView.m in Sources */, E81C279826EB3AC40031E639 /* LoginForgetPasswordProtocol.h in Sources */, E8C6FFD42754AA87004DC9F0 /* XPNoteView.m in Sources */, @@ -6395,6 +6646,7 @@ E8B825CA26EA1231009E8E9F /* LoginVerifCodeViewController.m in Sources */, 189DD76226E60DDC00AB55B1 /* Api+Login.m in Sources */, E8412FA62779BED1006E1101 /* XPRoomSettingTableViewCell.m in Sources */, + E8E859C828264C2300EE4857 /* HTTPMessage.m in Sources */, 9B7D80502753AA9D003DAC0C /* UITableView+NIMScrollToBottom.m in Sources */, 9BEE3D0E27853BD000C83219 /* ThemeColor+NobleCenter.m in Sources */, 18E7B22626E8CDCF0064BC9B /* XplanFlutterBoostDelegate.m in Sources */, @@ -6413,6 +6665,7 @@ E86596512701A1C000846EBD /* StatisticsService.m in Sources */, E824544026F58F9400BE8163 /* XPMinePayPwdViewController.m in Sources */, E8C1670928067DAA00ECB15C /* XPMineGuildSuperAdminSetViewController.m in Sources */, + E8E859DA28264C2300EE4857 /* SJXCSMIPHelper.m in Sources */, E890BC04273CF0500007C46B /* XPGiftCountModel.m in Sources */, E855516A280599A7005F293F /* XPGuildEmptyCollectionViewCell.m in Sources */, E899C68C275093B800E189E5 /* XPUserCardMicroItemModel.m in Sources */, @@ -6495,6 +6748,7 @@ E873EB0C2809850D0071030D /* MessageContentCustomView.m in Sources */, E884C36F2743AAC800E1EBED /* AttachmentModel.m in Sources */, E8AC721926F46E0B007D6E91 /* XPMineSettingItemModel.m in Sources */, + E8E859D428264C2300EE4857 /* DDASLLogger.m in Sources */, E82325E6274CCAFA003A3332 /* XPShareInfoModel.m in Sources */, 189DD55A26DE39D200AB55B1 /* BaseMvpPresenter.m in Sources */, E83ABEF9280EAF3F00322EE4 /* MessageContentOpenLiveView.m in Sources */, @@ -6524,6 +6778,7 @@ E81C27AE26EF39AB0031E639 /* AppDelegate+ThirdConfig.m in Sources */, E877A7F127842B2F00EFACED /* XPRoomDatingWebAlertView.m in Sources */, E80016382804140D00D6D17A /* XPMineGuildIncomeStatisViewController.m in Sources */, + E8E859C728264C2300EE4857 /* HTTPRedirectResponse.m in Sources */, E8E70D8326F2F51A00F03460 /* XPMineHeadView.m in Sources */, E8B825D026EA3825009E8E9F /* LoginPasswordViewController.m in Sources */, 9BEE3D142785884A00C83219 /* XPNobleCenterResidueView.m in Sources */, @@ -6549,6 +6804,7 @@ 9B92C02227E0BD040044C5EA /* XPNobleTrumpetModel.m in Sources */, 18F404C927609A4300A6C548 /* MessagePresenter.m in Sources */, 9BC9DAEF27E33B3F009EE409 /* XPRoomGiftAnimationParser.m in Sources */, + E8E859CB28264C2300EE4857 /* MultipartMessageHeaderField.m in Sources */, 9B2A12DB2783F88800CED41B /* XPNoblePrivilegeCell.m in Sources */, E81366E326F0A1FC0076364C /* LoginBindPhoneViewController.m in Sources */, 9B2EA7CC2804245500ED17BF /* XPAnchorPKPanelUserView.m in Sources */, @@ -6575,6 +6831,8 @@ 9B0E1C5926E77022005D4442 /* BaseNavigationController.m in Sources */, E8C6FFD027548256004DC9F0 /* XPHomeListCollectionViewCell.m in Sources */, E84BF7DD277C765400EF8877 /* XPRoomRoleEmptyTableViewCell.m in Sources */, + E8E859C428264C2300EE4857 /* HTTPDynamicFileResponse.m in Sources */, + E8E859D028264C2300EE4857 /* DDData.m in Sources */, E800162F2803FF6200D6D17A /* GuildPersonIncomeRecordModel.m in Sources */, E83ABEFD280EB5E200322EE4 /* ContentOpenLiveInfoModel.m in Sources */, E8E5E1A027C36E3F00F457D8 /* HomeBannerInfoModel.m in Sources */, @@ -6594,6 +6852,7 @@ E8395331276A03AE00CF2F24 /* Api+DressUp.m in Sources */, E8DEC99E2764A5B60078CB70 /* XPRoomMoreMenuViewController.m in Sources */, E82325F2274E2DE6003A3332 /* XPUserCardViewController.m in Sources */, + E8E859D228264C2300EE4857 /* HTTPServer.m in Sources */, E8C167182806A03800ECB15C /* XPSuperAdminSetPresenter.m in Sources */, 9B6E856E281AABAB0041A321 /* XPRoomRecommendModel.m in Sources */, E8C6FFCA27548120004DC9F0 /* XPHomePresenter.m in Sources */, @@ -6610,6 +6869,8 @@ E81C27A026EEF83D0031E639 /* XPHtmlUrl.m in Sources */, E8F1558D28124D5200EE8C06 /* MessageConentAudioView.m in Sources */, E8E20BDB281645300033B688 /* SessionInfoViewController.m in Sources */, + E8E859CA28264C2300EE4857 /* WebSocket.m in Sources */, + E8E859CC28264C2300EE4857 /* MultipartFormDataParser.m in Sources */, E800807C27FD84980055A8AB /* GuildInfoModel.m in Sources */, E8A03DF0276303D40098D9EA /* XPCandyTreeRankTableViewCell.m in Sources */, 9BCF58532798FDA1008401A4 /* XPSkillCardModel.m in Sources */, @@ -6644,6 +6905,7 @@ 189DD67E26E1FD8900AB55B1 /* UIImage+Utils.m in Sources */, E824545626F5E51900BE8163 /* XPMineVerifIdentityViewController.m in Sources */, E82D5C73276AE94800858D6D /* CarModel.m in Sources */, + E8E859D528264C2300EE4857 /* ContextFilterLogFormatter.m in Sources */, 9B92A35C27980A2900AD168F /* XPSkillCardHeadView.m in Sources */, 186A534726FC6ED900D67B2C /* TTAlertConfig.m in Sources */, 18F403EE2758CF2F00A6C548 /* MessageContentImage.m in Sources */, @@ -6679,6 +6941,7 @@ E84150B827747B8B00A7F548 /* XPFirstRechargeViewController.m in Sources */, 9BBC02872786D75C0007C24B /* XPNobleUpgradeLevelView.m in Sources */, E8E20BDE28164D3A0033B688 /* SessionNavView.m in Sources */, + E8E859CE28264C2300EE4857 /* HTTPAuthenticationRequest.m in Sources */, 9B92A3442797EE6500AD168F /* XPMatchManagePresenter.m in Sources */, E88B5CBD26FB3BDF00DA9178 /* XPTeenagerAlertView.m in Sources */, 9B86D87D2817EA0900494FCD /* XPNobleSettingViewController.m in Sources */, @@ -6688,6 +6951,7 @@ E8D48250278D68BA003C1D08 /* XPAcrpssRoomPKPanelView.m in Sources */, E800807627FD6D930055A8AB /* Api+Guild.m in Sources */, 18F404C3276098F100A6C548 /* Api+Message.m in Sources */, + E8E859D828264C2300EE4857 /* DDAbstractDatabaseLogger.m in Sources */, E8C6FFED27550CC2004DC9F0 /* HomeSearchResultModel.m in Sources */, E80016292803D5C500D6D17A /* XPGuildIncomeRecordTableViewCell.m in Sources */, 9BB549592782E6A30090CD26 /* XPNobleCenterPresenter.m in Sources */, @@ -6744,6 +7008,7 @@ E8EEB90126FC31B6007C6EBA /* XPMineUserInfoPresenter.m in Sources */, 18F404BB2760982000A6C548 /* ChatLimitModel.m in Sources */, 9BE9F10527FF04CF00667200 /* XPAnchorFansTaskTableViewCell.m in Sources */, + E8E859D328264C2300EE4857 /* DDTTYLogger.m in Sources */, 9B92A33C2797E38100AD168F /* XPMineHeadItemTableViewCell.m in Sources */, 189DD56526DE465A00AB55B1 /* LoginViewController.m in Sources */, 9B9DD94A281BC17600DBA903 /* XPCandyTreeMoreRuleCell.m in Sources */, @@ -6758,6 +7023,7 @@ 9B1EF3D227E81C0600554295 /* XPMineDressUpBubbleViewController.m in Sources */, E8A1E4512762082A00B294CA /* Api+CandyTree.m in Sources */, E839533C276A0CCD00CF2F24 /* XPMineCarTableViewCell.m in Sources */, + E8E859D928264C2300EE4857 /* DDFileLogger.m in Sources */, E8EEB8FE26FC2DF8007C6EBA /* XPMineUserInfoCustomNavView.m in Sources */, E839532A276A002800CF2F24 /* XPMineDressUpViewController.m in Sources */, E88B5CAD26FB16A800DA9178 /* XPMineTeenagerDesView.m in Sources */, @@ -6789,6 +7055,7 @@ 9B85F3532806AB9A006EDF51 /* XPAnchorPKResultView.m in Sources */, E8DEC99527648FA50078CB70 /* ClientConfig.m in Sources */, 9B6E8577281ABECC0041A321 /* XPRoomInsideRecommendEmptyCell.m in Sources */, + E8E859D628264C2300EE4857 /* DispatchQueueLogFormatter.m in Sources */, E880B3A6278BD69900A83B0D /* XPAcrossRoomPKTableViewCell.m in Sources */, E8EEB90926FC579A007C6EBA /* XPMineUserInfoEditTableViewCell.m in Sources */, E8F1559028125E2D00EE8C06 /* MessageAudioCenter.m in Sources */, @@ -6800,6 +7067,7 @@ 1808072D2731598F001FD836 /* XPNetImageYYLabel.m in Sources */, E800806827FD3B520055A8AB /* XPClanMenuView.m in Sources */, E896EFAF2771AF0F00AD2CC1 /* XPMineFriendEmptyTableViewCell.m in Sources */, + E8E859E928264F4500EE4857 /* XPRoomTransferMusicViewController.m in Sources */, 18486213271EA9DA005FC5DC /* RtcManager.m in Sources */, E8D34D5628080393009C4835 /* XPMineDataGiftCollectionViewCell.m in Sources */, 186A536926FC6F2E00D67B2C /* XPShareView.m in Sources */, @@ -6822,6 +7090,7 @@ 186A536B26FC6F2E00D67B2C /* XPShareItemCell.m in Sources */, E8B846C526FDB41A00A777FE /* XPMineUserInfolbumPresenter.m in Sources */, E8D34D532808037E009C4835 /* XPMineDataSkillDataCollectionViewCell.m in Sources */, + E8E859C528264C2300EE4857 /* HTTPFileResponse.m in Sources */, E83DB47D2746372300D8CBD1 /* XPRoomGiftBannerView.m in Sources */, 9BAA5FED277A1BBE007453F3 /* XPPrivacyViewController.m in Sources */, E873EB02280922720071030D /* XPMineUserInfoEmptyCollectionViewCell.m in Sources */, @@ -6858,6 +7127,7 @@ 9B33E3CE27D8540C003B0E62 /* XPVoiceCardViewController.m in Sources */, 9B3A1DF4280571000058E2DD /* XPAnchorPKInviteView.m in Sources */, 9B2489BC27C4C056006CFB85 /* XPMineVisitorEmptyTableViewCell.m in Sources */, + E8E859CD28264C2300EE4857 /* MultipartMessageHeader.m in Sources */, E878893F273A54F500BF1D57 /* XPGiftPresenter.m in Sources */, E855516D28059A01005F293F /* XPGuildIncomeDetailPresenter.m in Sources */, E8A03DF62763367F0098D9EA /* XPCandyTreeEmptyableViewCell.m in Sources */, @@ -6905,6 +7175,7 @@ E800806227FD373D0055A8AB /* XPClanMemberTableViewCell.m in Sources */, E80016452804268E00D6D17A /* XPMineClanIncomeStatisViewController.m in Sources */, E84150BF27747BD300A7F548 /* Api+FirstRecharge.m in Sources */, + E8E859CF28264C2300EE4857 /* DDNumber.m in Sources */, E84B0E422727EE0A008818C6 /* XPRoomMessageHeaderView.m in Sources */, E8C6FFDD2754CF5D004DC9F0 /* HomeRecommendRoomModel.m in Sources */, E8ACEFEC27C8C22C00F66D1A /* XPHomeHappyViewController.m in Sources */, @@ -6915,6 +7186,7 @@ E89D60BA271D643A001F8895 /* Api+Room.m in Sources */, E872308926E89BE000B90D4F /* LoginPhoneViewController.m in Sources */, E8C6FFEA2755040B004DC9F0 /* XPHomeSearchNavView.m in Sources */, + E8E859C628264C2300EE4857 /* HTTPAsyncFileResponse.m in Sources */, E877A7EB2783E24700EFACED /* DatingStageView.m in Sources */, E8001635280410BD00D6D17A /* XPGuildIncomeSectionView.m in Sources */, E8395339276A0CC100CF2F24 /* XPMineHeadwearTableViewCell.m in Sources */, @@ -6929,6 +7201,7 @@ E8395334276A03C300CF2F24 /* XPMineDressUpPresenter.m in Sources */, 9BC5C8F6277B0263007C8719 /* XPNobleCenterListViewController.m in Sources */, 189DD75926E6003C00AB55B1 /* Api.m in Sources */, + E8E859C328264C2300EE4857 /* HTTPDataResponse.m in Sources */, E87C0A9D27D9986700CB2241 /* XPRoomFaceCollectionFlowLayout.m in Sources */, 9B92A37027981F5B00AD168F /* XPSkillCardEditViewController.m in Sources */, E8133916273E532D00708B66 /* XPGiftItemCollectionViewCell.m in Sources */, @@ -6969,6 +7242,7 @@ 189DD58F26DF97E700AB55B1 /* LoginPresenter.m in Sources */, E8C167272806A68F00ECB15C /* GuildRoomInfoModel.m in Sources */, E88863C9278EBA43004BCFAB /* XPAcrossRoomPKForceEndResultView.m in Sources */, + E8E859D728264C2300EE4857 /* DDLog.m in Sources */, E896EFB22771C93B00AD2CC1 /* XPMineFriendNumberView.m in Sources */, 9BB5495F2782E9DB0090CD26 /* NobleAuthInfo.m in Sources */, 9B85F3562806DD8A006EDF51 /* XPAnchorPKFinishView.m in Sources */, diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/Contents.json b/xplan-ios/Assets.xcassets/Room/BackMusic/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/xplan-ios/Assets.xcassets/Room/BackMusic/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/Contents.json b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/Contents.json new file mode 100644 index 00000000..af5978ac --- /dev/null +++ b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_music_transfer_computer@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_music_transfer_computer@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@2x.png b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@2x.png new file mode 100644 index 00000000..e8fef146 Binary files /dev/null and b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@2x.png differ diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@3x.png b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@3x.png new file mode 100644 index 00000000..03ae264e Binary files /dev/null and b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_computer.imageset/room_music_transfer_computer@3x.png differ diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/Contents.json b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/Contents.json new file mode 100644 index 00000000..e442ae73 --- /dev/null +++ b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_music_transfer_wifi@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_music_transfer_wifi@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@2x.png b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@2x.png new file mode 100644 index 00000000..c19f3baf Binary files /dev/null and b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@2x.png differ diff --git a/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@3x.png b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@3x.png new file mode 100644 index 00000000..434f95d6 Binary files /dev/null and b/xplan-ios/Assets.xcassets/Room/BackMusic/room_music_transfer_wifi.imageset/room_music_transfer_wifi@3x.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.h new file mode 100755 index 00000000..e47b6c1a --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.h @@ -0,0 +1,14 @@ +#import + +@interface NSData (DDData) + +- (NSData *)md5Digest; + +- (NSData *)sha1Digest; + +- (NSString *)hexStringValue; + +- (NSString *)base64Encoded; +- (NSData *)base64Decoded; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.m new file mode 100755 index 00000000..10e92bfa --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.m @@ -0,0 +1,158 @@ +#import "DDData.h" +#import + + +@implementation NSData (DDData) + +static char encodingTable[64] = { +'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', +'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', +'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', +'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; + +- (NSData *)md5Digest +{ + unsigned char result[CC_MD5_DIGEST_LENGTH]; + + CC_MD5([self bytes], (CC_LONG)[self length], result); + return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; +} + +- (NSData *)sha1Digest +{ + unsigned char result[CC_SHA1_DIGEST_LENGTH]; + + CC_SHA1([self bytes], (CC_LONG)[self length], result); + return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH]; +} + +- (NSString *)hexStringValue +{ + NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)]; + + const unsigned char *dataBuffer = [self bytes]; + int i; + + for (i = 0; i < [self length]; ++i) + { + [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]]; + } + + return [stringBuffer copy]; +} + +- (NSString *)base64Encoded +{ + const unsigned char *bytes = [self bytes]; + NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; + unsigned long ixtext = 0; + unsigned long lentext = [self length]; + long ctremaining = 0; + unsigned char inbuf[3], outbuf[4]; + unsigned short i = 0; + unsigned short charsonline = 0, ctcopy = 0; + unsigned long ix = 0; + + while( YES ) + { + ctremaining = lentext - ixtext; + if( ctremaining <= 0 ) break; + + for( i = 0; i < 3; i++ ) { + ix = ixtext + i; + if( ix < lentext ) inbuf[i] = bytes[ix]; + else inbuf [i] = 0; + } + + outbuf [0] = (inbuf [0] & 0xFC) >> 2; + outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); + outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); + outbuf [3] = inbuf [2] & 0x3F; + ctcopy = 4; + + switch( ctremaining ) + { + case 1: + ctcopy = 2; + break; + case 2: + ctcopy = 3; + break; + } + + for( i = 0; i < ctcopy; i++ ) + [result appendFormat:@"%c", encodingTable[outbuf[i]]]; + + for( i = ctcopy; i < 4; i++ ) + [result appendString:@"="]; + + ixtext += 3; + charsonline += 4; + } + + return [NSString stringWithString:result]; +} + +- (NSData *)base64Decoded +{ + const unsigned char *bytes = [self bytes]; + NSMutableData *result = [NSMutableData dataWithCapacity:[self length]]; + + unsigned long ixtext = 0; + unsigned long lentext = [self length]; + unsigned char ch = 0; + unsigned char inbuf[4] = {0, 0, 0, 0}; + unsigned char outbuf[3] = {0, 0, 0}; + short i = 0, ixinbuf = 0; + BOOL flignore = NO; + BOOL flendtext = NO; + + while( YES ) + { + if( ixtext >= lentext ) break; + ch = bytes[ixtext++]; + flignore = NO; + + if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; + else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; + else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; + else if( ch == '+' ) ch = 62; + else if( ch == '=' ) flendtext = YES; + else if( ch == '/' ) ch = 63; + else flignore = YES; + + if( ! flignore ) + { + short ctcharsinbuf = 3; + BOOL flbreak = NO; + + if( flendtext ) + { + if( ! ixinbuf ) break; + if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; + else ctcharsinbuf = 2; + ixinbuf = 3; + flbreak = YES; + } + + inbuf [ixinbuf++] = ch; + + if( ixinbuf == 4 ) + { + ixinbuf = 0; + outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); + outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); + outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); + + for( i = 0; i < ctcharsinbuf; i++ ) + [result appendBytes:&outbuf[i] length:1]; + } + + if( flbreak ) break; + } + } + + return [NSData dataWithData:result]; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.h new file mode 100755 index 00000000..cded5b94 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.h @@ -0,0 +1,12 @@ +#import + + +@interface NSNumber (DDNumber) + ++ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; ++ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; + ++ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; ++ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.m new file mode 100755 index 00000000..d5b3602a --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.m @@ -0,0 +1,88 @@ +#import "DDNumber.h" + + +@implementation NSNumber (DDNumber) + ++ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum +{ + if(str == nil) + { + *pNum = 0; + return NO; + } + + errno = 0; + + // On both 32-bit and 64-bit machines, long long = 64 bit + + *pNum = strtoll([str UTF8String], NULL, 10); + + if(errno != 0) + return NO; + else + return YES; +} + ++ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum +{ + if(str == nil) + { + *pNum = 0; + return NO; + } + + errno = 0; + + // On both 32-bit and 64-bit machines, unsigned long long = 64 bit + + *pNum = strtoull([str UTF8String], NULL, 10); + + if(errno != 0) + return NO; + else + return YES; +} + ++ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum +{ + if(str == nil) + { + *pNum = 0; + return NO; + } + + errno = 0; + + // On LP64, NSInteger = long = 64 bit + // Otherwise, NSInteger = int = long = 32 bit + + *pNum = strtol([str UTF8String], NULL, 10); + + if(errno != 0) + return NO; + else + return YES; +} + ++ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum +{ + if(str == nil) + { + *pNum = 0; + return NO; + } + + errno = 0; + + // On LP64, NSUInteger = unsigned long = 64 bit + // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit + + *pNum = strtoul([str UTF8String], NULL, 10); + + if(errno != 0) + return NO; + else + return YES; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.h new file mode 100755 index 00000000..2fb4862c --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.h @@ -0,0 +1,56 @@ +/** + * DDRange is the functional equivalent of a 64 bit NSRange. + * The HTTP Server is designed to support very large files. + * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. + * This only supports a range of up to 4 gigabytes. + * By defining our own variant, we can support a range up to 16 exabytes. + * + * All effort is given such that DDRange functions EXACTLY the same as NSRange. +**/ + +#import +#import + +@class NSString; + +typedef struct _DDRange { + UInt64 location; + UInt64 length; +} DDRange; + +typedef DDRange *DDRangePointer; + +NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { + DDRange r; + r.location = loc; + r.length = len; + return r; +} + +NS_INLINE UInt64 DDMaxRange(DDRange range) { + return (range.location + range.length); +} + +NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { + return (loc - range.location < range.length); +} + +NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { + return ((range1.location == range2.location) && (range1.length == range2.length)); +} + +FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); +FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); +FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); +FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); + +NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); + +@interface NSValue (NSValueDDRangeExtensions) + ++ (NSValue *)valueWithDDRange:(DDRange)range; +- (DDRange)ddrangeValue; + +- (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.m new file mode 100755 index 00000000..acef37ae --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.m @@ -0,0 +1,104 @@ +#import "DDRange.h" +#import "DDNumber.h" + +DDRange DDUnionRange(DDRange range1, DDRange range2) +{ + DDRange result; + + result.location = MIN(range1.location, range2.location); + result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location; + + return result; +} + +DDRange DDIntersectionRange(DDRange range1, DDRange range2) +{ + DDRange result; + + if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) + { + return DDMakeRange(0, 0); + } + + result.location = MAX(range1.location, range2.location); + result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location; + + return result; +} + +NSString *DDStringFromRange(DDRange range) +{ + return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; +} + +DDRange DDRangeFromString(NSString *aString) +{ + DDRange result = DDMakeRange(0, 0); + + // NSRange will ignore '-' characters, but not '+' characters + NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; + + NSScanner *scanner = [NSScanner scannerWithString:aString]; + [scanner setCharactersToBeSkipped:[cset invertedSet]]; + + NSString *str1 = nil; + NSString *str2 = nil; + + BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; + BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; + + if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; + if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; + + return result; +} + +NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) +{ + // Comparison basis: + // Which range would you encouter first if you started at zero, and began walking towards infinity. + // If you encouter both ranges at the same time, which range would end first. + + if(pDDRange1->location < pDDRange2->location) + { + return NSOrderedAscending; + } + if(pDDRange1->location > pDDRange2->location) + { + return NSOrderedDescending; + } + if(pDDRange1->length < pDDRange2->length) + { + return NSOrderedAscending; + } + if(pDDRange1->length > pDDRange2->length) + { + return NSOrderedDescending; + } + + return NSOrderedSame; +} + +@implementation NSValue (NSValueDDRangeExtensions) + ++ (NSValue *)valueWithDDRange:(DDRange)range +{ + return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; +} + +- (DDRange)ddrangeValue +{ + DDRange result; + [self getValue:&result]; + return result; +} + +- (NSInteger)ddrangeCompare:(NSValue *)other +{ + DDRange r1 = [self ddrangeValue]; + DDRange r2 = [other ddrangeValue]; + + return DDRangeCompare(&r1, &r2); +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.h new file mode 100755 index 00000000..983e1620 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.h @@ -0,0 +1,45 @@ +#import + +#if TARGET_OS_IPHONE + // Note: You may need to add the CFNetwork Framework to your project + #import +#endif + +@class HTTPMessage; + + +@interface HTTPAuthenticationRequest : NSObject +{ + BOOL isBasic; + BOOL isDigest; + + NSString *base64Credentials; + + NSString *username; + NSString *realm; + NSString *nonce; + NSString *uri; + NSString *qop; + NSString *nc; + NSString *cnonce; + NSString *response; +} +- (id)initWithRequest:(HTTPMessage *)request; + +- (BOOL)isBasic; +- (BOOL)isDigest; + +// Basic +- (NSString *)base64Credentials; + +// Digest +- (NSString *)username; +- (NSString *)realm; +- (NSString *)nonce; +- (NSString *)uri; +- (NSString *)qop; +- (NSString *)nc; +- (NSString *)cnonce; +- (NSString *)response; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.m new file mode 100755 index 00000000..d575fcde --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.m @@ -0,0 +1,194 @@ +#import "HTTPAuthenticationRequest.h" +#import "HTTPMessage.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface HTTPAuthenticationRequest (PrivateAPI) +- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; +- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; +@end + + +@implementation HTTPAuthenticationRequest + +- (id)initWithRequest:(HTTPMessage *)request +{ + if ((self = [super init])) + { + NSString *authInfo = [request headerField:@"Authorization"]; + + isBasic = NO; + if ([authInfo length] >= 6) + { + isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; + } + + isDigest = NO; + if ([authInfo length] >= 7) + { + isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; + } + + if (isBasic) + { + NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy]; + CFStringTrimWhitespace((__bridge CFMutableStringRef)temp); + + base64Credentials = [temp copy]; + } + + if (isDigest) + { + username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo]; + realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo]; + nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo]; + uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo]; + + // It appears from RFC 2617 that the qop is to be given unquoted + // Tests show that Firefox performs this way, but Safari does not + // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote + qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; + if(qop && ([qop characterAtIndex:0] == '"')) + { + qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; + } + + nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo]; + cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo]; + response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo]; + } + } + return self; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Accessors: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isBasic { + return isBasic; +} + +- (BOOL)isDigest { + return isDigest; +} + +- (NSString *)base64Credentials { + return base64Credentials; +} + +- (NSString *)username { + return username; +} + +- (NSString *)realm { + return realm; +} + +- (NSString *)nonce { + return nonce; +} + +- (NSString *)uri { + return uri; +} + +- (NSString *)qop { + return qop; +} + +- (NSString *)nc { + return nc; +} + +- (NSString *)cnonce { + return cnonce; +} + +- (NSString *)response { + return response; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Retrieves a "Sub Header Field Value" from a given header field value. + * The sub header field is expected to be quoted. + * + * In the following header field: + * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" + * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". +**/ +- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header +{ + if (header==nil || header.length == 0) return @""; + NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; + if(startRange.location == NSNotFound){ + // The param was not found anywhere in the header + return nil; + } + NSUInteger postStartRangeLocation = startRange.location + startRange.length; + NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; + NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); + + NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; + if(endRange.location == NSNotFound) + { + // The ending double-quote was not found anywhere in the header + return nil; + } + + NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); + return [header substringWithRange:subHeaderRange]; +} + +/** + * Retrieves a "Sub Header Field Value" from a given header field value. + * The sub header field is expected to not be quoted. + * + * In the following header field: + * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" + * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". +**/ +- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header +{ + NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; + if(startRange.location == NSNotFound) + { + // The param was not found anywhere in the header + return nil; + } + + NSUInteger postStartRangeLocation = startRange.location + startRange.length; + NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; + NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); + + NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; + if(endRange.location == NSNotFound) + { + // The ending comma was not found anywhere in the header + // However, if the nonquoted param is at the end of the string, there would be no comma + // This is only possible if there are no spaces anywhere + NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; + if(endRange2.location != NSNotFound) + { + return nil; + } + else + { + return [header substringWithRange:postStartRange]; + } + } + else + { + NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); + return [header substringWithRange:subHeaderRange]; + } +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.h new file mode 100755 index 00000000..80b9576f --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.h @@ -0,0 +1,119 @@ +#import + +@class GCDAsyncSocket; +@class HTTPMessage; +@class HTTPServer; +@class WebSocket; +@protocol HTTPResponse; + + +#define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface HTTPConfig : NSObject +{ + HTTPServer __unsafe_unretained *server; + NSString __strong *documentRoot; + dispatch_queue_t queue; +} + +- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot; +- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; + +@property (nonatomic, unsafe_unretained, readonly) HTTPServer *server; +@property (nonatomic, strong, readonly) NSString *documentRoot; +@property (nonatomic, readonly) dispatch_queue_t queue; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface HTTPConnection : NSObject +{ + dispatch_queue_t connectionQueue; + GCDAsyncSocket *asyncSocket; + HTTPConfig *config; + + BOOL started; + + HTTPMessage *request; + unsigned int numHeaderLines; + + BOOL sentResponseHeaders; + + NSString *nonce; + long lastNC; + + NSObject *httpResponse; + + NSMutableArray *ranges; + NSMutableArray *ranges_headers; + NSString *ranges_boundry; + int rangeIndex; + + UInt64 requestContentLength; + UInt64 requestContentLengthReceived; + UInt64 requestChunkSize; + UInt64 requestChunkSizeReceived; + + NSMutableArray *responseDataSizes; +} + +- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig; + +- (void)start; +- (void)stop; + +- (void)startConnection; + +- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; +- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; + +- (BOOL)isSecureServer; +- (NSArray *)sslIdentityAndCertificates; + +- (BOOL)isPasswordProtected:(NSString *)path; +- (BOOL)useDigestAccessAuthentication; +- (NSString *)realm; +- (NSString *)passwordForUser:(NSString *)username; + +- (NSDictionary *)parseParams:(NSString *)query; +- (NSDictionary *)parseGetParams; + +- (NSString *)requestURI; + +- (NSArray *)directoryIndexFileNames; +- (NSString *)filePathForURI:(NSString *)path; +- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; +- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; +- (WebSocket *)webSocketForURI:(NSString *)path; + +- (void)prepareForBodyWithSize:(UInt64)contentLength; +- (void)processBodyData:(NSData *)postDataChunk; +- (void)finishBody; + +- (void)handleVersionNotSupported:(NSString *)version; +- (void)handleAuthenticationFailed; +- (void)handleResourceNotFound; +- (void)handleInvalidRequest:(NSData *)data; +- (void)handleUnknownMethod:(NSString *)method; + +- (NSData *)preprocessResponse:(HTTPMessage *)response; +- (NSData *)preprocessErrorResponse:(HTTPMessage *)response; + +- (void)finishResponse; + +- (BOOL)shouldDie; +- (void)die; + +@end + +@interface HTTPConnection (AsynchronousHTTPResponse) +- (void)responseHasAvailableData:(NSObject *)sender; +- (void)responseDidAbort:(NSObject *)sender; +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.m new file mode 100755 index 00000000..2143c782 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.m @@ -0,0 +1,2708 @@ +#import "GCDAsyncSocket.h" +#import "HTTPServer.h" +#import "HTTPConnection.h" +#import "HTTPMessage.h" +#import "HTTPResponse.h" +#import "HTTPAuthenticationRequest.h" +#import "DDNumber.h" +#import "DDRange.h" +#import "DDData.h" +#import "HTTPFileResponse.h" +#import "HTTPAsyncFileResponse.h" +#import "WebSocket.h" +#import "HTTPLogging.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels: off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; + +// Define chunk size used to read in data for responses +// This is how much data will be read from disk into RAM at a time +#if TARGET_OS_IPHONE + #define READ_CHUNKSIZE (1024 * 256) +#else + #define READ_CHUNKSIZE (1024 * 512) +#endif + +// Define chunk size used to read in POST upload data +#if TARGET_OS_IPHONE + #define POST_CHUNKSIZE (1024 * 256) +#else + #define POST_CHUNKSIZE (1024 * 512) +#endif + +// Define the various timeouts (in seconds) for various parts of the HTTP process +#define TIMEOUT_READ_FIRST_HEADER_LINE 30 +#define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30 +#define TIMEOUT_READ_BODY -1 +#define TIMEOUT_WRITE_HEAD 30 +#define TIMEOUT_WRITE_BODY -1 +#define TIMEOUT_WRITE_ERROR 30 +#define TIMEOUT_NONCE 300 + +// Define the various limits +// MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n) +// MAX_HEADER_LINES : Max number of lines in a single header (including first GET line) +#define MAX_HEADER_LINE_LENGTH 8190 +#define MAX_HEADER_LINES 100 +// MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n) +#define MAX_CHUNK_LINE_LENGTH 200 + +// Define the various tags we'll use to differentiate what it is we're currently doing +#define HTTP_REQUEST_HEADER 10 +#define HTTP_REQUEST_BODY 11 +#define HTTP_REQUEST_CHUNK_SIZE 12 +#define HTTP_REQUEST_CHUNK_DATA 13 +#define HTTP_REQUEST_CHUNK_TRAILER 14 +#define HTTP_REQUEST_CHUNK_FOOTER 15 +#define HTTP_PARTIAL_RESPONSE 20 +#define HTTP_PARTIAL_RESPONSE_HEADER 21 +#define HTTP_PARTIAL_RESPONSE_BODY 22 +#define HTTP_CHUNKED_RESPONSE_HEADER 30 +#define HTTP_CHUNKED_RESPONSE_BODY 31 +#define HTTP_CHUNKED_RESPONSE_FOOTER 32 +#define HTTP_PARTIAL_RANGE_RESPONSE_BODY 40 +#define HTTP_PARTIAL_RANGES_RESPONSE_BODY 50 +#define HTTP_RESPONSE 90 +#define HTTP_FINAL_RESPONSE 91 + +// A quick note about the tags: +// +// The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent. +// That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE, +// it is assumed that the response is now completely sent. +// Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards. +// Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response. +// +// If you are sending multiple data segments in a custom response, make sure that only the last segment has +// the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other +// tag of your own invention. + +@interface HTTPConnection (PrivateAPI) +- (void)startReadingRequest; +- (void)sendResponseHeadersAndBody; +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation HTTPConnection + +static dispatch_queue_t recentNonceQueue; +static NSMutableArray *recentNonces; + +/** + * This method is automatically called (courtesy of Cocoa) before the first instantiation of this class. + * We use it to initialize any static variables. +**/ ++ (void)initialize +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + // Initialize class variables + recentNonceQueue = dispatch_queue_create("HTTPConnection-Nonce", NULL); + recentNonces = [[NSMutableArray alloc] initWithCapacity:5]; + }); +} + +/** + * Generates and returns an authentication nonce. + * A nonce is a server-specified string uniquely generated for each 401 response. + * The default implementation uses a single nonce for each session. +**/ ++ (NSString *)generateNonce +{ + // We use the Core Foundation UUID class to generate a nonce value for us + // UUIDs (Universally Unique Identifiers) are 128-bit values guaranteed to be unique. + CFUUIDRef theUUID = CFUUIDCreate(NULL); + NSString *newNonce = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); + CFRelease(theUUID); + + // We have to remember that the HTTP protocol is stateless. + // Even though with version 1.1 persistent connections are the norm, they are not guaranteed. + // Thus if we generate a nonce for this connection, + // it should be honored for other connections in the near future. + // + // In fact, this is absolutely necessary in order to support QuickTime. + // When QuickTime makes it's initial connection, it will be unauthorized, and will receive a nonce. + // It then disconnects, and creates a new connection with the nonce, and proper authentication. + // If we don't honor the nonce for the second connection, QuickTime will repeat the process and never connect. + + dispatch_async(recentNonceQueue, ^{ @autoreleasepool { + + [recentNonces addObject:newNonce]; + }}); + + double delayInSeconds = TIMEOUT_NONCE; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); + dispatch_after(popTime, recentNonceQueue, ^{ @autoreleasepool { + + [recentNonces removeObject:newNonce]; + }}); + + return newNonce; +} + +/** + * Returns whether or not the given nonce is in the list of recently generated nonce's. +**/ ++ (BOOL)hasRecentNonce:(NSString *)recentNonce +{ + __block BOOL result = NO; + + dispatch_sync(recentNonceQueue, ^{ @autoreleasepool { + + result = [recentNonces containsObject:recentNonce]; + }}); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Init, Dealloc: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Sole Constructor. + * Associates this new HTTP connection with the given AsyncSocket. + * This HTTP connection object will become the socket's delegate and take over responsibility for the socket. +**/ +- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig +{ + if ((self = [super init])) + { + HTTPLogTrace(); + + if (aConfig.queue) + { + connectionQueue = aConfig.queue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(connectionQueue); + #endif + } + else + { + connectionQueue = dispatch_queue_create("HTTPConnection", NULL); + } + + // Take over ownership of the socket + asyncSocket = newSocket; + [asyncSocket setDelegate:self delegateQueue:connectionQueue]; + + // Store configuration + config = aConfig; + + // Initialize lastNC (last nonce count). + // Used with digest access authentication. + // These must increment for each request from the client. + lastNC = 0; + + // Create a new HTTP message + request = [[HTTPMessage alloc] initEmptyRequest]; + + numHeaderLines = 0; + + responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5]; + } + return self; +} + +/** + * Standard Deconstructor. +**/ +- (void)dealloc +{ + HTTPLogTrace(); + + #if !OS_OBJECT_USE_OBJC + dispatch_release(connectionQueue); + #endif + + [asyncSocket setDelegate:nil delegateQueue:NULL]; + [asyncSocket disconnect]; + + if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) + { + [httpResponse connectionDidClose]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Method Support +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns whether or not the server will accept messages of a given method + * at a particular URI. +**/ +- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path +{ + HTTPLogTrace(); + + // Override me to support methods such as POST. + // + // Things you may want to consider: + // - Does the given path represent a resource that is designed to accept this method? + // - If accepting an upload, is the size of the data being uploaded too big? + // To do this you can check the requestContentLength variable. + // + // For more information, you can always access the HTTPMessage request variable. + // + // You should fall through with a call to [super supportsMethod:method atPath:path] + // + // See also: expectsRequestBodyFromMethod:atPath: + + if ([method isEqualToString:@"GET"]) + return YES; + + if ([method isEqualToString:@"HEAD"]) + return YES; + + return NO; +} + +/** + * Returns whether or not the server expects a body from the given method. + * + * In other words, should the server expect a content-length header and associated body from this method. + * This would be true in the case of a POST, where the client is sending data, + * or for something like PUT where the client is supposed to be uploading a file. +**/ +- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path +{ + HTTPLogTrace(); + + // Override me to add support for other methods that expect the client + // to send a body along with the request header. + // + // You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path] + // + // See also: supportsMethod:atPath: + + if ([method isEqualToString:@"POST"]) + return YES; + + if ([method isEqualToString:@"PUT"]) + return YES; + + return NO; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark HTTPS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns whether or not the server is configured to be a secure server. + * In other words, all connections to this server are immediately secured, thus only secure connections are allowed. + * This is the equivalent of having an https server, where it is assumed that all connections must be secure. + * If this is the case, then unsecure connections will not be allowed on this server, and a separate unsecure server + * would need to be run on a separate port in order to support unsecure connections. + * + * Note: In order to support secure connections, the sslIdentityAndCertificates method must be implemented. +**/ +- (BOOL)isSecureServer +{ + HTTPLogTrace(); + + // Override me to create an https server... + + return NO; +} + +/** + * This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings. + * It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef. +**/ +- (NSArray *)sslIdentityAndCertificates +{ + HTTPLogTrace(); + + // Override me to provide the proper required SSL identity. + + return nil; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Password Protection +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns whether or not the requested resource is password protected. + * In this generic implementation, nothing is password protected. +**/ +- (BOOL)isPasswordProtected:(NSString *)path +{ + HTTPLogTrace(); + + // Override me to provide password protection... + // You can configure it for the entire server, or based on the current request + + return NO; +} + +/** + * Returns whether or not the authentication challenge should use digest access authentication. + * The alternative is basic authentication. + * + * If at all possible, digest access authentication should be used because it's more secure. + * Basic authentication sends passwords in the clear and should be avoided unless using SSL/TLS. +**/ +- (BOOL)useDigestAccessAuthentication +{ + HTTPLogTrace(); + + // Override me to customize the authentication scheme + // Make sure you understand the security risks of using the weaker basic authentication + + return YES; +} + +/** + * Returns the authentication realm. + * In this generic implmentation, a default realm is used for the entire server. +**/ +- (NSString *)realm +{ + HTTPLogTrace(); + + // Override me to provide a custom realm... + // You can configure it for the entire server, or based on the current request + + return @"defaultRealm@host.com"; +} + +/** + * Returns the password for the given username. +**/ +- (NSString *)passwordForUser:(NSString *)username +{ + HTTPLogTrace(); + + // Override me to provide proper password authentication + // You can configure a password for the entire server, or custom passwords for users and/or resources + + // Security Note: + // A nil password means no access at all. (Such as for user doesn't exist) + // An empty string password is allowed, and will be treated as any other password. (To support anonymous access) + + return nil; +} + +/** + * Returns whether or not the user is properly authenticated. +**/ +- (BOOL)isAuthenticated +{ + HTTPLogTrace(); + + // Extract the authentication information from the Authorization header + HTTPAuthenticationRequest *auth = [[HTTPAuthenticationRequest alloc] initWithRequest:request]; + + if ([self useDigestAccessAuthentication]) + { + // Digest Access Authentication (RFC 2617) + + if(![auth isDigest]) + { + // User didn't send proper digest access authentication credentials + return NO; + } + + if ([auth username] == nil) + { + // The client didn't provide a username + // Most likely they didn't provide any authentication at all + return NO; + } + + NSString *password = [self passwordForUser:[auth username]]; + if (password == nil) + { + // No access allowed (username doesn't exist in system) + return NO; + } + + NSString *url = [[request url] relativeString]; + + if (![url isEqualToString:[auth uri]]) + { + // Requested URL and Authorization URI do not match + // This could be a replay attack + // IE - attacker provides same authentication information, but requests a different resource + return NO; + } + + // The nonce the client provided will most commonly be stored in our local (cached) nonce variable + if (![nonce isEqualToString:[auth nonce]]) + { + // The given nonce may be from another connection + // We need to search our list of recent nonce strings that have been recently distributed + if ([[self class] hasRecentNonce:[auth nonce]]) + { + // Store nonce in local (cached) nonce variable to prevent array searches in the future + nonce = [[auth nonce] copy]; + + // The client has switched to using a different nonce value + // This may happen if the client tries to get a file in a directory with different credentials. + // The previous credentials wouldn't work, and the client would receive a 401 error + // along with a new nonce value. The client then uses this new nonce value and requests the file again. + // Whatever the case may be, we need to reset lastNC, since that variable is on a per nonce basis. + lastNC = 0; + } + else + { + // We have no knowledge of ever distributing such a nonce. + // This could be a replay attack from a previous connection in the past. + return NO; + } + } + + long authNC = strtol([[auth nc] UTF8String], NULL, 16); + + if (authNC <= lastNC) + { + // The nc value (nonce count) hasn't been incremented since the last request. + // This could be a replay attack. + return NO; + } + lastNC = authNC; + + NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", [auth username], [auth realm], password]; + NSString *HA2str = [NSString stringWithFormat:@"%@:%@", [request method], [auth uri]]; + + NSString *HA1 = [[[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; + + NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; + + NSString *responseStr = [NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@", + HA1, [auth nonce], [auth nc], [auth cnonce], [auth qop], HA2]; + + NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; + + return [response isEqualToString:[auth response]]; + } + else + { + // Basic Authentication + + if (![auth isBasic]) + { + // User didn't send proper base authentication credentials + return NO; + } + + // Decode the base 64 encoded credentials + NSString *base64Credentials = [auth base64Credentials]; + + NSData *temp = [[base64Credentials dataUsingEncoding:NSUTF8StringEncoding] base64Decoded]; + + NSString *credentials = [[NSString alloc] initWithData:temp encoding:NSUTF8StringEncoding]; + + // The credentials should be of the form "username:password" + // The username is not allowed to contain a colon + + NSRange colonRange = [credentials rangeOfString:@":"]; + + if (colonRange.length == 0) + { + // Malformed credentials + return NO; + } + + NSString *credUsername = [credentials substringToIndex:colonRange.location]; + NSString *credPassword = [credentials substringFromIndex:(colonRange.location + colonRange.length)]; + + NSString *password = [self passwordForUser:credUsername]; + if (password == nil) + { + // No access allowed (username doesn't exist in system) + return NO; + } + + return [password isEqualToString:credPassword]; + } +} + +/** + * Adds a digest access authentication challenge to the given response. +**/ +- (void)addDigestAuthChallenge:(HTTPMessage *)response +{ + HTTPLogTrace(); + + NSString *authFormat = @"Digest realm=\"%@\", qop=\"auth\", nonce=\"%@\""; + NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm], [[self class] generateNonce]]; + + [response setHeaderField:@"WWW-Authenticate" value:authInfo]; +} + +/** + * Adds a basic authentication challenge to the given response. +**/ +- (void)addBasicAuthChallenge:(HTTPMessage *)response +{ + HTTPLogTrace(); + + NSString *authFormat = @"Basic realm=\"%@\""; + NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm]]; + + [response setHeaderField:@"WWW-Authenticate" value:authInfo]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Core +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Starting point for the HTTP connection after it has been fully initialized (including subclasses). + * This method is called by the HTTP server. +**/ +- (void)start +{ + dispatch_async(connectionQueue, ^{ @autoreleasepool { + + if (!started) + { + started = YES; + [self startConnection]; + } + }}); +} + +/** + * This method is called by the HTTPServer if it is asked to stop. + * The server, in turn, invokes stop on each HTTPConnection instance. +**/ +- (void)stop +{ + dispatch_async(connectionQueue, ^{ @autoreleasepool { + + // Disconnect the socket. + // The socketDidDisconnect delegate method will handle everything else. + [asyncSocket disconnect]; + }}); +} + +/** + * Starting point for the HTTP connection. +**/ +- (void)startConnection +{ + // Override me to do any custom work before the connection starts. + // + // Be sure to invoke [super startConnection] when you're done. + + HTTPLogTrace(); + + if ([self isSecureServer]) + { + // We are configured to be an HTTPS server. + // That is, we secure via SSL/TLS the connection prior to any communication. + + NSArray *certificates = [self sslIdentityAndCertificates]; + + if ([certificates count] > 0) + { + // All connections are assumed to be secure. Only secure connections are allowed on this server. + NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; + + // Configure this connection as the server + [settings setObject:[NSNumber numberWithBool:YES] + forKey:(NSString *)kCFStreamSSLIsServer]; + + [settings setObject:certificates + forKey:(NSString *)kCFStreamSSLCertificates]; + + // Configure this connection to use the highest possible SSL level + [settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL + forKey:(NSString *)kCFStreamSSLLevel]; + + [asyncSocket startTLS:settings]; + } + } + + [self startReadingRequest]; +} + +/** + * Starts reading an HTTP request. +**/ +- (void)startReadingRequest +{ + HTTPLogTrace(); + + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE + maxLength:MAX_HEADER_LINE_LENGTH + tag:HTTP_REQUEST_HEADER]; +} + +/** + * Parses the given query string. + * + * For example, if the query is "q=John%20Mayer%20Trio&num=50" + * then this method would return the following dictionary: + * { + * q = "John Mayer Trio" + * num = "50" + * } +**/ +- (NSDictionary *)parseParams:(NSString *)query +{ + NSArray *components = [query componentsSeparatedByString:@"&"]; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]]; + + NSUInteger i; + for (i = 0; i < [components count]; i++) + { + NSString *component = [components objectAtIndex:i]; + if ([component length] > 0) + { + NSRange range = [component rangeOfString:@"="]; + if (range.location != NSNotFound) + { + NSString *escapedKey = [component substringToIndex:(range.location + 0)]; + NSString *escapedValue = [component substringFromIndex:(range.location + 1)]; + + if ([escapedKey length] > 0) + { + CFStringRef k, v; + + k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR("")); + v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR("")); + + NSString *key, *value; + + key = (__bridge_transfer NSString *)k; + value = (__bridge_transfer NSString *)v; + + if (key) + { + if (value) + [result setObject:value forKey:key]; + else + [result setObject:[NSNull null] forKey:key]; + } + } + } + } + } + + return result; +} + +/** + * Parses the query variables in the request URI. + * + * For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50" + * then this method would return the following dictionary: + * { + * q = "John Mayer Trio" + * num = "50" + * } +**/ +- (NSDictionary *)parseGetParams +{ + if(![request isHeaderComplete]) return nil; + + NSDictionary *result = nil; + + NSURL *url = [request url]; + if(url) + { + NSString *query = [url query]; + if (query) + { + result = [self parseParams:query]; + } + } + + return result; +} + +/** + * Attempts to parse the given range header into a series of sequential non-overlapping ranges. + * If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned. + * Otherwise, NO is returned, and the range request should be ignored. + **/ +- (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength +{ + HTTPLogTrace(); + + // Examples of byte-ranges-specifier values (assuming an entity-body of length 10000): + // + // - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499 + // + // - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999 + // + // - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500 + // + // - Or bytes=9500- + // + // - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1 + // + // - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive): + // bytes=500-600,601-999 + // bytes=500-700,601-999 + // + + NSRange eqsignRange = [rangeHeader rangeOfString:@"="]; + + if(eqsignRange.location == NSNotFound) return NO; + + NSUInteger tIndex = eqsignRange.location; + NSUInteger fIndex = eqsignRange.location + eqsignRange.length; + + NSMutableString *rangeType = [[rangeHeader substringToIndex:tIndex] mutableCopy]; + NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy]; + + CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType); + CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue); + + if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO; + + NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","]; + + if([rangeComponents count] == 0) return NO; + + ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]]; + + rangeIndex = 0; + + // Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects. + // Since DDRange consists of UInt64 values, the range extends up to 16 exabytes. + + NSUInteger i; + for (i = 0; i < [rangeComponents count]; i++) + { + NSString *rangeComponent = [rangeComponents objectAtIndex:i]; + + NSRange dashRange = [rangeComponent rangeOfString:@"-"]; + + if (dashRange.location == NSNotFound) + { + // We're dealing with an individual byte number + + UInt64 byteIndex; + if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO; + + if(byteIndex >= contentLength) return NO; + + [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]]; + } + else + { + // We're dealing with a range of bytes + + tIndex = dashRange.location; + fIndex = dashRange.location + dashRange.length; + + NSString *r1str = [rangeComponent substringToIndex:tIndex]; + NSString *r2str = [rangeComponent substringFromIndex:fIndex]; + + UInt64 r1, r2; + + BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1]; + BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2]; + + if (!hasR1) + { + // We're dealing with a "-[#]" range + // + // r2 is the number of ending bytes to include in the range + + if(!hasR2) return NO; + if(r2 > contentLength) return NO; + + UInt64 startIndex = contentLength - r2; + + [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]]; + } + else if (!hasR2) + { + // We're dealing with a "[#]-" range + // + // r1 is the starting index of the range, which goes all the way to the end + + if(r1 >= contentLength) return NO; + + [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]]; + } + else + { + // We're dealing with a normal "[#]-[#]" range + // + // Note: The range is inclusive. So 0-1 has a length of 2 bytes. + + if(r1 > r2) return NO; + if(r2 >= contentLength) return NO; + + [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]]; + } + } + } + + if([ranges count] == 0) return NO; + + // Now make sure none of the ranges overlap + + for (i = 0; i < [ranges count] - 1; i++) + { + DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue]; + + NSUInteger j; + for (j = i+1; j < [ranges count]; j++) + { + DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue]; + + DDRange iRange = DDIntersectionRange(range1, range2); + + if(iRange.length != 0) + { + return NO; + } + } + } + + // Sort the ranges + + [ranges sortUsingSelector:@selector(ddrangeCompare:)]; + + return YES; +} + +- (NSString *)requestURI +{ + if(request == nil) return nil; + + return [[request url] relativeString]; +} + +/** + * This method is called after a full HTTP request has been received. + * The current request is in the HTTPMessage request variable. +**/ +- (void)replyToHTTPRequest +{ + HTTPLogTrace(); + + if (HTTP_LOG_VERBOSE) + { + NSData *tempData = [request messageData]; + + NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding]; + HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr); + } + + // Check the HTTP version + // We only support version 1.0 and 1.1 + + NSString *version = [request version]; + if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0]) + { + [self handleVersionNotSupported:version]; + return; + } + + // Extract requested URI + NSString *uri = [self requestURI]; + + // Check for WebSocket request + if ([WebSocket isWebSocketRequest:request]) + { + HTTPLogVerbose(@"isWebSocket"); + + WebSocket *ws = [self webSocketForURI:uri]; + + if (ws == nil) + { + [self handleResourceNotFound]; + } + else + { + [ws start]; + + [[config server] addWebSocket:ws]; + + // The WebSocket should now be the delegate of the underlying socket. + // But gracefully handle the situation if it forgot. + if ([asyncSocket delegate] == self) + { + HTTPLogWarn(@"%@[%p]: WebSocket forgot to set itself as socket delegate", THIS_FILE, self); + + // Disconnect the socket. + // The socketDidDisconnect delegate method will handle everything else. + [asyncSocket disconnect]; + } + else + { + // The WebSocket is using the socket, + // so make sure we don't disconnect it in the dealloc method. + asyncSocket = nil; + + [self die]; + + // Note: There is a timing issue here that should be pointed out. + // + // A bug that existed in previous versions happend like so: + // - We invoked [self die] + // - This caused us to get released, and our dealloc method to start executing + // - Meanwhile, AsyncSocket noticed a disconnect, and began to dispatch a socketDidDisconnect at us + // - The dealloc method finishes execution, and our instance gets freed + // - The socketDidDisconnect gets run, and a crash occurs + // + // So the issue we want to avoid is releasing ourself when there is a possibility + // that AsyncSocket might be gearing up to queue a socketDidDisconnect for us. + // + // In this particular situation notice that we invoke [asyncSocket delegate]. + // This method is synchronous concerning AsyncSocket's internal socketQueue. + // Which means we can be sure, when it returns, that AsyncSocket has already + // queued any delegate methods for us if it was going to. + // And if the delegate methods are queued, then we've been properly retained. + // Meaning we won't get released / dealloc'd until the delegate method has finished executing. + // + // In this rare situation, the die method will get invoked twice. + } + } + + return; + } + + // Check Authentication (if needed) + // If not properly authenticated for resource, issue Unauthorized response + if ([self isPasswordProtected:uri] && ![self isAuthenticated]) + { + [self handleAuthenticationFailed]; + return; + } + + // Extract the method + NSString *method = [request method]; + + // Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag: + + // Respond properly to HTTP 'GET' and 'HEAD' commands + httpResponse = [self httpResponseForMethod:method URI:uri]; + + if (httpResponse == nil) + { + [self handleResourceNotFound]; + return; + } + + [self sendResponseHeadersAndBody]; +} + +/** + * Prepares a single-range response. + * + * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. +**/ +- (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength +{ + HTTPLogTrace(); + + // Status Code 206 - Partial Content + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; + + DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; + + NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length]; + [response setHeaderField:@"Content-Length" value:contentLengthStr]; + + NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; + NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; + [response setHeaderField:@"Content-Range" value:contentRangeStr]; + + return response; +} + +/** + * Prepares a multi-range response. + * + * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. +**/ +- (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength +{ + HTTPLogTrace(); + + // Status Code 206 - Partial Content + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; + + // We have to send each range using multipart/byteranges + // So each byterange has to be prefix'd and suffix'd with the boundry + // Example: + // + // HTTP/1.1 206 Partial Content + // Content-Length: 220 + // Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6 + // + // + // --4554d24e986f76dd6 + // Content-Range: bytes 0-25/4025 + // + // [...] + // --4554d24e986f76dd6 + // Content-Range: bytes 3975-4024/4025 + // + // [...] + // --4554d24e986f76dd6-- + + ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]]; + + CFUUIDRef theUUID = CFUUIDCreate(NULL); + ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); + CFRelease(theUUID); + + NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry]; + NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; + + UInt64 actualContentLength = 0; + + NSUInteger i; + for (i = 0; i < [ranges count]; i++) + { + DDRange range = [[ranges objectAtIndex:i] ddrangeValue]; + + NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; + NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; + NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal]; + + NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr]; + NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding]; + + [ranges_headers addObject:fullHeaderData]; + + actualContentLength += [fullHeaderData length]; + actualContentLength += range.length; + } + + NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; + + actualContentLength += [endingBoundryData length]; + + NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength]; + [response setHeaderField:@"Content-Length" value:contentLengthStr]; + + NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry]; + [response setHeaderField:@"Content-Type" value:contentTypeStr]; + + return response; +} + +/** + * Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding. + * This consists of the size of the data, in hexadecimal, followed by a CRLF. +**/ +- (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length +{ + return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding]; +} + +/** + * Returns the data that signals the end of a chunked transfer. +**/ +- (NSData *)chunkedTransferFooter +{ + // Each data chunk is preceded by a size line (in hex and including a CRLF), + // followed by the data itself, followed by another CRLF. + // After every data chunk has been sent, a zero size line is sent, + // followed by optional footer (which are just more headers), + // and followed by a CRLF on a line by itself. + + return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (void)sendResponseHeadersAndBody +{ + if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)]) + { + if ([httpResponse delayResponseHeaders]) + { + return; + } + } + + BOOL isChunked = NO; + + if ([httpResponse respondsToSelector:@selector(isChunked)]) + { + isChunked = [httpResponse isChunked]; + } + + // If a response is "chunked", this simply means the HTTPResponse object + // doesn't know the content-length in advance. + + UInt64 contentLength = 0; + + if (!isChunked) + { + contentLength = [httpResponse contentLength]; + } + + // Check for specific range request + NSString *rangeHeader = [request headerField:@"Range"]; + + BOOL isRangeRequest = NO; + + // If the response is "chunked" then we don't know the exact content-length. + // This means we'll be unable to process any range requests. + // This is because range requests might include a range like "give me the last 100 bytes" + + if (!isChunked && rangeHeader) + { + if ([self parseRangeRequest:rangeHeader withContentLength:contentLength]) + { + isRangeRequest = YES; + } + } + + HTTPMessage *response; + + if (!isRangeRequest) + { + // Create response + // Default status code: 200 - OK + NSInteger status = 200; + + if ([httpResponse respondsToSelector:@selector(status)]) + { + status = [httpResponse status]; + } + response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1]; + + if (isChunked) + { + [response setHeaderField:@"Transfer-Encoding" value:@"chunked"]; + } + else + { + NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength]; + [response setHeaderField:@"Content-Length" value:contentLengthStr]; + } + } + else + { + if ([ranges count] == 1) + { + response = [self newUniRangeResponse:contentLength]; + } + else + { + response = [self newMultiRangeResponse:contentLength]; + } + } + + BOOL isZeroLengthResponse = !isChunked && (contentLength == 0); + + // If they issue a 'HEAD' command, we don't have to include the file + // If they issue a 'GET' command, we need to include the file + + if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse) + { + NSData *responseData = [self preprocessResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; + + sentResponseHeaders = YES; + } + else + { + // Write the header response + NSData *responseData = [self preprocessResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; + + sentResponseHeaders = YES; + + // Now we need to send the body of the response + if (!isRangeRequest) + { + // Regular request + NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + if (isChunked) + { + NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; + [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; + + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; + + if ([httpResponse isDone]) + { + NSData *footer = [self chunkedTransferFooter]; + [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; + } + else + { + NSData *footer = [GCDAsyncSocket CRLFData]; + [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; + } + } + else + { + long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; + } + } + } + else + { + // Client specified a byte range in request + + if ([ranges count] == 1) + { + // Client is requesting a single range + DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; + + [httpResponse setOffset:range.location]; + + NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; + + NSData *data = [httpResponse readDataOfLength:bytesToRead]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; + } + } + else + { + // Client is requesting multiple ranges + // We have to send each range using multipart/byteranges + + // Write range header + NSData *rangeHeaderData = [ranges_headers objectAtIndex:0]; + [asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; + + // Start writing range body + DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; + + [httpResponse setOffset:range.location]; + + NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; + + NSData *data = [httpResponse readDataOfLength:bytesToRead]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; + } + } + } + } + +} + +/** + * Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue. + * + * We keep track of this information in order to keep our memory footprint low while + * working with asynchronous HTTPResponse objects. +**/ +- (NSUInteger)writeQueueSize +{ + NSUInteger result = 0; + + NSUInteger i; + for(i = 0; i < [responseDataSizes count]; i++) + { + result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue]; + } + + return result; +} + +/** + * Sends more data, if needed, without growing the write queue over its approximate size limit. + * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. + * + * This method should only be called for standard (non-range) responses. +**/ +- (void)continueSendingStandardResponseBody +{ + HTTPLogTrace(); + + // This method is called when either asyncSocket has finished writing one of the response data chunks, + // or when an asynchronous HTTPResponse object informs us that it has more available data for us to send. + // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, + // and shove it onto asyncSocket's write queue. + // Doing so could negatively affect the memory footprint of the application. + // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. + // + // Note that this does not affect the rate at which the HTTPResponse object may generate data. + // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. + // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely + // use the calls to readDataOfLength as an indication to start generating more data. + // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate + // at which the socket is able to send it. + + NSUInteger writeQueueSize = [self writeQueueSize]; + + if(writeQueueSize >= READ_CHUNKSIZE) return; + + NSUInteger available = READ_CHUNKSIZE - writeQueueSize; + NSData *data = [httpResponse readDataOfLength:available]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + BOOL isChunked = NO; + + if ([httpResponse respondsToSelector:@selector(isChunked)]) + { + isChunked = [httpResponse isChunked]; + } + + if (isChunked) + { + NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; + [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; + + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; + + if([httpResponse isDone]) + { + NSData *footer = [self chunkedTransferFooter]; + [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; + } + else + { + NSData *footer = [GCDAsyncSocket CRLFData]; + [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; + } + } + else + { + long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; + } + } +} + +/** + * Sends more data, if needed, without growing the write queue over its approximate size limit. + * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. + * + * This method should only be called for single-range responses. +**/ +- (void)continueSendingSingleRangeResponseBody +{ + HTTPLogTrace(); + + // This method is called when either asyncSocket has finished writing one of the response data chunks, + // or when an asynchronous response informs us that is has more available data for us to send. + // In the case of the asynchronous response, we don't want to blindly grab the new data, + // and shove it onto asyncSocket's write queue. + // Doing so could negatively affect the memory footprint of the application. + // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. + // + // Note that this does not affect the rate at which the HTTPResponse object may generate data. + // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. + // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely + // use the calls to readDataOfLength as an indication to start generating more data. + // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate + // at which the socket is able to send it. + + NSUInteger writeQueueSize = [self writeQueueSize]; + + if(writeQueueSize >= READ_CHUNKSIZE) return; + + DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; + + UInt64 offset = [httpResponse offset]; + UInt64 bytesRead = offset - range.location; + UInt64 bytesLeft = range.length - bytesRead; + + if (bytesLeft > 0) + { + NSUInteger available = READ_CHUNKSIZE - writeQueueSize; + NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; + + NSData *data = [httpResponse readDataOfLength:bytesToRead]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; + } + } +} + +/** + * Sends more data, if needed, without growing the write queue over its approximate size limit. + * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. + * + * This method should only be called for multi-range responses. +**/ +- (void)continueSendingMultiRangeResponseBody +{ + HTTPLogTrace(); + + // This method is called when either asyncSocket has finished writing one of the response data chunks, + // or when an asynchronous HTTPResponse object informs us that is has more available data for us to send. + // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, + // and shove it onto asyncSocket's write queue. + // Doing so could negatively affect the memory footprint of the application. + // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. + // + // Note that this does not affect the rate at which the HTTPResponse object may generate data. + // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. + // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely + // use the calls to readDataOfLength as an indication to start generating more data. + // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate + // at which the socket is able to send it. + + NSUInteger writeQueueSize = [self writeQueueSize]; + + if(writeQueueSize >= READ_CHUNKSIZE) return; + + DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; + + UInt64 offset = [httpResponse offset]; + UInt64 bytesRead = offset - range.location; + UInt64 bytesLeft = range.length - bytesRead; + + if (bytesLeft > 0) + { + NSUInteger available = READ_CHUNKSIZE - writeQueueSize; + NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; + + NSData *data = [httpResponse readDataOfLength:bytesToRead]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; + } + } + else + { + if (++rangeIndex < [ranges count]) + { + // Write range header + NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex]; + [asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; + + // Start writing range body + range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; + + [httpResponse setOffset:range.location]; + + NSUInteger available = READ_CHUNKSIZE - writeQueueSize; + NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available; + + NSData *data = [httpResponse readDataOfLength:bytesToRead]; + + if ([data length] > 0) + { + [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; + + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; + } + } + else + { + // We're not done yet - we still have to send the closing boundry tag + NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; + NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; + + [asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Responses +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns an array of possible index pages. + * For example: {"index.html", "index.htm"} +**/ +- (NSArray *)directoryIndexFileNames +{ + HTTPLogTrace(); + + // Override me to support other index pages. + + return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil]; +} + +- (NSString *)filePathForURI:(NSString *)path +{ + return [self filePathForURI:path allowDirectory:NO]; +} + +/** + * Converts relative URI path into full file-system path. +**/ +- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory +{ + HTTPLogTrace(); + + // Override me to perform custom path mapping. + // For example you may want to use a default file other than index.html, or perhaps support multiple types. + + NSString *documentRoot = [config documentRoot]; + + // Part 0: Validate document root setting. + // + // If there is no configured documentRoot, + // then it makes no sense to try to return anything. + + if (documentRoot == nil) + { + HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self); + return nil; + } + + // Part 1: Strip parameters from the url + // + // E.g.: /page.html?q=22&var=abc -> /page.html + + NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES]; + if (docRoot == nil) + { + HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self); + return nil; + } + + NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath]; + + // Part 2: Append relative path to document root (base path) + // + // E.g.: relativePath="/images/icon.png" + // documentRoot="/Users/robbie/Sites" + // fullPath="/Users/robbie/Sites/images/icon.png" + // + // We also standardize the path. + // + // E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html" + + NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath]; + + if ([relativePath isEqualToString:@"/"]) + { + fullPath = [fullPath stringByAppendingString:@"/"]; + } + + // Part 3: Prevent serving files outside the document root. + // + // Sneaky requests may include ".." in the path. + // + // E.g.: relativePath="../Documents/TopSecret.doc" + // documentRoot="/Users/robbie/Sites" + // fullPath="/Users/robbie/Documents/TopSecret.doc" + // + // E.g.: relativePath="../Sites_Secret/TopSecret.doc" + // documentRoot="/Users/robbie/Sites" + // fullPath="/Users/robbie/Sites_Secret/TopSecret" + + if (![documentRoot hasSuffix:@"/"]) + { + documentRoot = [documentRoot stringByAppendingString:@"/"]; + } + + if (![fullPath hasPrefix:documentRoot]) + { + HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self); + return nil; + } + + // Part 4: Search for index page if path is pointing to a directory + if (!allowDirectory) + { + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir) + { + NSArray *indexFileNames = [self directoryIndexFileNames]; + + for (NSString *indexFileName in indexFileNames) + { + NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir) + { + return indexFilePath; + } + } + + // No matching index files found in directory + return nil; + } + } + + return fullPath; +} + +/** + * This method is called to get a response for a request. + * You may return any object that adopts the HTTPResponse protocol. + * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse. + * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response. + * HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response. +**/ +- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path +{ + HTTPLogTrace(); + + // Override me to provide custom responses. + + NSString *filePath = [self filePathForURI:path allowDirectory:NO]; + + BOOL isDir = NO; + + if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] && !isDir) + { + return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self]; + + // Use me instead for asynchronous file IO. + // Generally better for larger files. + + // return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease]; + } + + return nil; +} + +- (WebSocket *)webSocketForURI:(NSString *)path +{ + HTTPLogTrace(); + + // Override me to provide custom WebSocket responses. + // To do so, simply override the base WebSocket implementation, and add your custom functionality. + // Then return an instance of your custom WebSocket here. + // + // For example: + // + // if ([path isEqualToString:@"/myAwesomeWebSocketStream"]) + // { + // return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease]; + // } + // + // return [super webSocketForURI:path]; + + return nil; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Uploads +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method is called after receiving all HTTP headers, but before reading any of the request body. +**/ +- (void)prepareForBodyWithSize:(UInt64)contentLength +{ + // Override me to allocate buffers, file handles, etc. +} + +/** + * This method is called to handle data read from a POST / PUT. + * The given data is part of the request body. +**/ +- (void)processBodyData:(NSData *)postDataChunk +{ + // Override me to do something useful with a POST / PUT. + // If the post is small, such as a simple form, you may want to simply append the data to the request. + // If the post is big, such as a file upload, you may want to store the file to disk. + // + // Remember: In order to support LARGE POST uploads, the data is read in chunks. + // This prevents a 50 MB upload from being stored in RAM. + // The size of the chunks are limited by the POST_CHUNKSIZE definition. + // Therefore, this method may be called multiple times for the same POST request. +} + +/** + * This method is called after the request body has been fully read but before the HTTP request is processed. +**/ +- (void)finishBody +{ + // Override me to perform any final operations on an upload. + // For example, if you were saving the upload to disk this would be + // the hook to flush any pending data to disk and maybe close the file. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Called if the HTML version is other than what is supported +**/ +- (void)handleVersionNotSupported:(NSString *)version +{ + // Override me for custom error handling of unsupported http version responses + // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. + // You can also use preprocessErrorResponse: to add an optional HTML body. + + HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]); + + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1]; + [response setHeaderField:@"Content-Length" value:@"0"]; + + NSData *responseData = [self preprocessErrorResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; + +} + +/** + * Called if the authentication information was required and absent, or if authentication failed. +**/ +- (void)handleAuthenticationFailed +{ + // Override me for custom handling of authentication challenges + // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. + // You can also use preprocessErrorResponse: to add an optional HTML body. + + HTTPLogInfo(@"HTTP Server: Error 401 - Unauthorized (%@)", [self requestURI]); + + // Status Code 401 - Unauthorized + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:401 description:nil version:HTTPVersion1_1]; + [response setHeaderField:@"Content-Length" value:@"0"]; + + if ([self useDigestAccessAuthentication]) + { + [self addDigestAuthChallenge:response]; + } + else + { + [self addBasicAuthChallenge:response]; + } + + NSData *responseData = [self preprocessErrorResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; + +} + +/** + * Called if we receive some sort of malformed HTTP request. + * The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket. + * The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length. +**/ +- (void)handleInvalidRequest:(NSData *)data +{ + // Override me for custom error handling of invalid HTTP requests + // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. + // You can also use preprocessErrorResponse: to add an optional HTML body. + + HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]); + + // Status Code 400 - Bad Request + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1]; + [response setHeaderField:@"Content-Length" value:@"0"]; + [response setHeaderField:@"Connection" value:@"close"]; + + NSData *responseData = [self preprocessErrorResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; + + + // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. + // We do this because we couldn't parse the request, + // so we won't be able to recover and move on to another request afterwards. + // In other words, we wouldn't know where the first request ends and the second request begins. +} + +/** + * Called if we receive a HTTP request with a method other than GET or HEAD. +**/ +- (void)handleUnknownMethod:(NSString *)method +{ + // Override me for custom error handling of 405 method not allowed responses. + // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. + // You can also use preprocessErrorResponse: to add an optional HTML body. + // + // See also: supportsMethod:atPath: + + HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]); + + // Status code 405 - Method Not Allowed + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1]; + [response setHeaderField:@"Content-Length" value:@"0"]; + [response setHeaderField:@"Connection" value:@"close"]; + + NSData *responseData = [self preprocessErrorResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; + + + // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. + // We do this because the method may include an http body. + // Since we can't be sure, we should close the connection. +} + +/** + * Called if we're unable to find the requested resource. +**/ +- (void)handleResourceNotFound +{ + // Override me for custom error handling of 404 not found responses + // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. + // You can also use preprocessErrorResponse: to add an optional HTML body. + + HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]); + + // Status Code 404 - Not Found + HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1]; + [response setHeaderField:@"Content-Length" value:@"0"]; + + NSData *responseData = [self preprocessErrorResponse:response]; + [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Headers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header. +**/ +- (NSString *)dateAsString:(NSDate *)date +{ + // From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency): + // + // "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, + // it is typically more efficient to cache a single instance than to create and dispose of multiple instances. + // One approach is to use a static variable." + // + // This was discovered to be true in massive form via issue #46: + // + // "Was doing some performance benchmarking using instruments and httperf. Using this single optimization + // I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant. + // The culprit? Why, NSDateFormatter, of course!" + // + // Thus, we are using a static NSDateFormatter here. + + static NSDateFormatter *df; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + // Example: Sun, 06 Nov 1994 08:49:37 GMT + + df = [[NSDateFormatter alloc] init]; + [df setFormatterBehavior:NSDateFormatterBehavior10_4]; + [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + [df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"]; + [df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; + + // For some reason, using zzz in the format string produces GMT+00:00 + }); + + return [df stringFromDate:date]; +} + +/** + * This method is called immediately prior to sending the response headers. + * This method adds standard header fields, and then converts the response to an NSData object. +**/ +- (NSData *)preprocessResponse:(HTTPMessage *)response +{ + HTTPLogTrace(); + + // Override me to customize the response headers + // You'll likely want to add your own custom headers, and then return [super preprocessResponse:response] + + // Add standard headers + NSString *now = [self dateAsString:[NSDate date]]; + [response setHeaderField:@"Date" value:now]; + + // Add server capability headers + [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; + + // Add optional response headers + if ([httpResponse respondsToSelector:@selector(httpHeaders)]) + { + NSDictionary *responseHeaders = [httpResponse httpHeaders]; + + NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; + NSString *key; + + while ((key = [keyEnumerator nextObject])) + { + NSString *value = [responseHeaders objectForKey:key]; + + [response setHeaderField:key value:value]; + } + } + + return [response messageData]; +} + +/** + * This method is called immediately prior to sending the response headers (for an error). + * This method adds standard header fields, and then converts the response to an NSData object. +**/ +- (NSData *)preprocessErrorResponse:(HTTPMessage *)response +{ + HTTPLogTrace(); + + // Override me to customize the error response headers + // You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response] + // + // Notes: + // You can use [response statusCode] to get the type of error. + // You can use [response setBody:data] to add an optional HTML body. + // If you add a body, don't forget to update the Content-Length. + // + // if ([response statusCode] == 404) + // { + // NSString *msg = @"Error 404 - Not Found"; + // NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; + // + // [response setBody:msgData]; + // + // NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]]; + // [response setHeaderField:@"Content-Length" value:contentLengthStr]; + // } + + // Add standard headers + NSString *now = [self dateAsString:[NSDate date]]; + [response setHeaderField:@"Date" value:now]; + + // Add server capability headers + [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; + + // Add optional response headers + if ([httpResponse respondsToSelector:@selector(httpHeaders)]) + { + NSDictionary *responseHeaders = [httpResponse httpHeaders]; + + NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; + NSString *key; + + while((key = [keyEnumerator nextObject])) + { + NSString *value = [responseHeaders objectForKey:key]; + + [response setHeaderField:key value:value]; + } + } + + return [response messageData]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark GCDAsyncSocket Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method is called after the socket has successfully read data from the stream. + * Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length. +**/ +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag +{ + if (tag == HTTP_REQUEST_HEADER) + { + // Append the header line to the http message + BOOL result = [request appendData:data]; + if (!result) + { + HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self); + + [self handleInvalidRequest:data]; + } + else if (![request isHeaderComplete]) + { + // We don't have a complete header yet + // That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header + if (++numHeaderLines > MAX_HEADER_LINES) + { + // Reached the maximum amount of header lines in a single HTTP request + // This could be an attempted DOS attack + [asyncSocket disconnect]; + + // Explictly return to ensure we don't do anything after the socket disconnect + return; + } + else + { + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE + maxLength:MAX_HEADER_LINE_LENGTH + tag:HTTP_REQUEST_HEADER]; + } + } + else + { + // We have an entire HTTP request header from the client + + // Extract the method (such as GET, HEAD, POST, etc) + NSString *method = [request method]; + + // Extract the uri (such as "/index.html") + NSString *uri = [self requestURI]; + + // Check for a Transfer-Encoding field + NSString *transferEncoding = [request headerField:@"Transfer-Encoding"]; + + // Check for a Content-Length field + NSString *contentLength = [request headerField:@"Content-Length"]; + + // Content-Length MUST be present for upload methods (such as POST or PUT) + // and MUST NOT be present for other methods. + BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri]; + + if (expectsUpload) + { + if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"]) + { + requestContentLength = -1; + } + else + { + if (contentLength == nil) + { + HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length", + THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + + if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) + { + HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", + THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + } + } + else + { + if (contentLength != nil) + { + // Received Content-Length header for method not expecting an upload. + // This better be zero... + + if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) + { + HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", + THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + + if (requestContentLength > 0) + { + HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length", + THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + } + + requestContentLength = 0; + requestContentLengthReceived = 0; + } + + // Check to make sure the given method is supported + if (![self supportsMethod:method atPath:uri]) + { + // The method is unsupported - either in general, or for this specific request + // Send a 405 - Method not allowed response + [self handleUnknownMethod:method]; + return; + } + + if (expectsUpload) + { + // Reset the total amount of data received for the upload + requestContentLengthReceived = 0; + + // Prepare for the upload + [self prepareForBodyWithSize:requestContentLength]; + + if (requestContentLength > 0) + { + // Start reading the request body + if (requestContentLength == -1) + { + // Chunked transfer + + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_BODY + maxLength:MAX_CHUNK_LINE_LENGTH + tag:HTTP_REQUEST_CHUNK_SIZE]; + } + else + { + NSUInteger bytesToRead; + if (requestContentLength < POST_CHUNKSIZE) + bytesToRead = (NSUInteger)requestContentLength; + else + bytesToRead = POST_CHUNKSIZE; + + [asyncSocket readDataToLength:bytesToRead + withTimeout:TIMEOUT_READ_BODY + tag:HTTP_REQUEST_BODY]; + } + } + else + { + // Empty upload + [self finishBody]; + [self replyToHTTPRequest]; + } + } + else + { + // Now we need to reply to the request + [self replyToHTTPRequest]; + } + } + } + else + { + BOOL doneReadingRequest = NO; + + // A chunked message body contains a series of chunks, + // followed by a line with "0" (zero), + // followed by optional footers (just like headers), + // and a blank line. + // + // Each chunk consists of two parts: + // + // 1. A line with the size of the chunk data, in hex, + // possibly followed by a semicolon and extra parameters you can ignore (none are currently standard), + // and ending with CRLF. + // 2. The data itself, followed by CRLF. + // + // Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE + // Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER + // where the trailer is the CRLF that follows the data. + // + // The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER. + + if (tag == HTTP_REQUEST_CHUNK_SIZE) + { + // We have just read in a line with the size of the chunk data, in hex, + // possibly followed by a semicolon and extra parameters that can be ignored, + // and ending with CRLF. + + NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + errno = 0; // Reset errno before calling strtoull() to ensure it is always zero on success + requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16); + requestChunkSizeReceived = 0; + + if (errno != 0) + { + HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + + if (requestChunkSize > 0) + { + NSUInteger bytesToRead; + bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE; + + [asyncSocket readDataToLength:bytesToRead + withTimeout:TIMEOUT_READ_BODY + tag:HTTP_REQUEST_CHUNK_DATA]; + } + else + { + // This is the "0" (zero) line, + // which is to be followed by optional footers (just like headers) and finally a blank line. + + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_BODY + maxLength:MAX_HEADER_LINE_LENGTH + tag:HTTP_REQUEST_CHUNK_FOOTER]; + } + + return; + } + else if (tag == HTTP_REQUEST_CHUNK_DATA) + { + // We just read part of the actual data. + + requestContentLengthReceived += [data length]; + requestChunkSizeReceived += [data length]; + + [self processBodyData:data]; + + UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived; + if (bytesLeft > 0) + { + NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; + + [asyncSocket readDataToLength:bytesToRead + withTimeout:TIMEOUT_READ_BODY + tag:HTTP_REQUEST_CHUNK_DATA]; + } + else + { + // We've read in all the data for this chunk. + // The data is followed by a CRLF, which we need to read (and basically ignore) + + [asyncSocket readDataToLength:2 + withTimeout:TIMEOUT_READ_BODY + tag:HTTP_REQUEST_CHUNK_TRAILER]; + } + + return; + } + else if (tag == HTTP_REQUEST_CHUNK_TRAILER) + { + // This should be the CRLF following the data. + // Just ensure it's a CRLF. + + if (![data isEqualToData:[GCDAsyncSocket CRLFData]]) + { + HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self); + + [self handleInvalidRequest:nil]; + return; + } + + // Now continue with the next chunk + + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_BODY + maxLength:MAX_CHUNK_LINE_LENGTH + tag:HTTP_REQUEST_CHUNK_SIZE]; + + } + else if (tag == HTTP_REQUEST_CHUNK_FOOTER) + { + if (++numHeaderLines > MAX_HEADER_LINES) + { + // Reached the maximum amount of header lines in a single HTTP request + // This could be an attempted DOS attack + [asyncSocket disconnect]; + + // Explictly return to ensure we don't do anything after the socket disconnect + return; + } + + if ([data length] > 2) + { + // We read in a footer. + // In the future we may want to append these to the request. + // For now we ignore, and continue reading the footers, waiting for the final blank line. + + [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] + withTimeout:TIMEOUT_READ_BODY + maxLength:MAX_HEADER_LINE_LENGTH + tag:HTTP_REQUEST_CHUNK_FOOTER]; + } + else + { + doneReadingRequest = YES; + } + } + else // HTTP_REQUEST_BODY + { + // Handle a chunk of data from the POST body + + requestContentLengthReceived += [data length]; + [self processBodyData:data]; + + if (requestContentLengthReceived < requestContentLength) + { + // We're not done reading the post body yet... + + UInt64 bytesLeft = requestContentLength - requestContentLengthReceived; + + NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; + + [asyncSocket readDataToLength:bytesToRead + withTimeout:TIMEOUT_READ_BODY + tag:HTTP_REQUEST_BODY]; + } + else + { + doneReadingRequest = YES; + } + } + + // Now that the entire body has been received, we need to reply to the request + + if (doneReadingRequest) + { + [self finishBody]; + [self replyToHTTPRequest]; + } + } +} + +/** + * This method is called after the socket has successfully written data to the stream. +**/ +- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag +{ + BOOL doneSendingResponse = NO; + + if (tag == HTTP_PARTIAL_RESPONSE_BODY) + { + // Update the amount of data we have in asyncSocket's write queue + if ([responseDataSizes count] > 0) { + [responseDataSizes removeObjectAtIndex:0]; + } + + // We only wrote a part of the response - there may be more + [self continueSendingStandardResponseBody]; + } + else if (tag == HTTP_CHUNKED_RESPONSE_BODY) + { + // Update the amount of data we have in asyncSocket's write queue. + // This will allow asynchronous responses to continue sending more data. + if ([responseDataSizes count] > 0) { + [responseDataSizes removeObjectAtIndex:0]; + } + // Don't continue sending the response yet. + // The chunked footer that was sent after the body will tell us if we have more data to send. + } + else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER) + { + // Normal chunked footer indicating we have more data to send (non final footer). + [self continueSendingStandardResponseBody]; + } + else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY) + { + // Update the amount of data we have in asyncSocket's write queue + if ([responseDataSizes count] > 0) { + [responseDataSizes removeObjectAtIndex:0]; + } + // We only wrote a part of the range - there may be more + [self continueSendingSingleRangeResponseBody]; + } + else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY) + { + // Update the amount of data we have in asyncSocket's write queue + if ([responseDataSizes count] > 0) { + [responseDataSizes removeObjectAtIndex:0]; + } + // We only wrote part of the range - there may be more, or there may be more ranges + [self continueSendingMultiRangeResponseBody]; + } + else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE) + { + // Update the amount of data we have in asyncSocket's write queue + if ([responseDataSizes count] > 0) + { + [responseDataSizes removeObjectAtIndex:0]; + } + + doneSendingResponse = YES; + } + + if (doneSendingResponse) + { + // Inform the http response that we're done + if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) + { + [httpResponse connectionDidClose]; + } + + + if (tag == HTTP_FINAL_RESPONSE) + { + // Cleanup after the last request + [self finishResponse]; + + // Terminate the connection + [asyncSocket disconnect]; + + // Explictly return to ensure we don't do anything after the socket disconnects + return; + } + else + { + if ([self shouldDie]) + { + // Cleanup after the last request + // Note: Don't do this before calling shouldDie, as it needs the request object still. + [self finishResponse]; + + // The only time we should invoke [self die] is from socketDidDisconnect, + // or if the socket gets taken over by someone else like a WebSocket. + + [asyncSocket disconnect]; + } + else + { + // Cleanup after the last request + [self finishResponse]; + + // Prepare for the next request + + // If this assertion fails, it likely means you overrode the + // finishBody method and forgot to call [super finishBody]. + NSAssert(request == nil, @"Request not properly released in finishBody"); + + request = [[HTTPMessage alloc] initEmptyRequest]; + + numHeaderLines = 0; + sentResponseHeaders = NO; + + // And start listening for more requests + [self startReadingRequest]; + } + } + } +} + +/** + * Sent after the socket has been disconnected. +**/ +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + HTTPLogTrace(); + + asyncSocket = nil; + + [self die]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark HTTPResponse Notifications +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method may be called by asynchronous HTTPResponse objects. + * That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method. + * + * This informs us that the response object has generated more data that we may be able to send. +**/ +- (void)responseHasAvailableData:(NSObject *)sender +{ + HTTPLogTrace(); + + // We always dispatch this asynchronously onto our connectionQueue, + // even if the connectionQueue is the current queue. + // + // We do this to give the HTTPResponse classes the flexibility to call + // this method whenever they want, even from within a readDataOfLength method. + + dispatch_async(connectionQueue, ^{ @autoreleasepool { + + if (sender != httpResponse) + { + HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); + return; + } + + if (!sentResponseHeaders) + { + [self sendResponseHeadersAndBody]; + } + else + { + if (ranges == nil) + { + [self continueSendingStandardResponseBody]; + } + else + { + if ([ranges count] == 1) + [self continueSendingSingleRangeResponseBody]; + else + [self continueSendingMultiRangeResponseBody]; + } + } + }}); +} + +/** + * This method is called if the response encounters some critical error, + * and it will be unable to fullfill the request. +**/ +- (void)responseDidAbort:(NSObject *)sender +{ + HTTPLogTrace(); + + // We always dispatch this asynchronously onto our connectionQueue, + // even if the connectionQueue is the current queue. + // + // We do this to give the HTTPResponse classes the flexibility to call + // this method whenever they want, even from within a readDataOfLength method. + + dispatch_async(connectionQueue, ^{ @autoreleasepool { + + if (sender != httpResponse) + { + HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); + return; + } + + [asyncSocket disconnectAfterWriting]; + }}); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Post Request +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method is called after each response has been fully sent. + * Since a single connection may handle multiple request/responses, this method may be called multiple times. + * That is, it will be called after completion of each response. +**/ +- (void)finishResponse +{ + HTTPLogTrace(); + + // Override me if you want to perform any custom actions after a response has been fully sent. + // This is the place to release memory or resources associated with the last request. + // + // If you override this method, you should take care to invoke [super finishResponse] at some point. + + request = nil; + + httpResponse = nil; + + ranges = nil; + ranges_headers = nil; + ranges_boundry = nil; +} + +/** + * This method is called after each successful response has been fully sent. + * It determines whether the connection should stay open and handle another request. +**/ +- (BOOL)shouldDie +{ + HTTPLogTrace(); + + // Override me if you have any need to force close the connection. + // You may do so by simply returning YES. + // + // If you override this method, you should take care to fall through with [super shouldDie] + // instead of returning NO. + + + BOOL shouldDie = NO; + + NSString *version = [request version]; + if ([version isEqualToString:HTTPVersion1_1]) + { + // HTTP version 1.1 + // Connection should only be closed if request included "Connection: close" header + + NSString *connection = [request headerField:@"Connection"]; + + shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame)); + } + else if ([version isEqualToString:HTTPVersion1_0]) + { + // HTTP version 1.0 + // Connection should be closed unless request included "Connection: Keep-Alive" header + + NSString *connection = [request headerField:@"Connection"]; + + if (connection == nil) + shouldDie = YES; + else + shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame; + } + + return shouldDie; +} + +- (void)die +{ + HTTPLogTrace(); + + // Override me if you want to perform any custom actions when a connection is closed. + // Then call [super die] when you're done. + // + // See also the finishResponse method. + // + // Important: There is a rare timing condition where this method might get invoked twice. + // If you override this method, you should be prepared for this situation. + + // Inform the http response that we're done + if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) + { + [httpResponse connectionDidClose]; + } + + // Release the http response so we don't call it's connectionDidClose method again in our dealloc method + httpResponse = nil; + + // Post notification of dead connection + // This will allow our server to release us from its array of connections + [[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation HTTPConfig + +@synthesize server; +@synthesize documentRoot; +@synthesize queue; + +- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot +{ + if ((self = [super init])) + { + server = aServer; + documentRoot = aDocumentRoot; + } + return self; +} + +- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q +{ + if ((self = [super init])) + { + server = aServer; + + documentRoot = [aDocumentRoot stringByStandardizingPath]; + if ([documentRoot hasSuffix:@"/"]) + { + documentRoot = [documentRoot stringByAppendingString:@"/"]; + } + + if (q) + { + queue = q; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(queue); + #endif + } + } + return self; +} + +- (void)dealloc +{ + #if !OS_OBJECT_USE_OBJC + if (queue) dispatch_release(queue); + #endif +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPLogging.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPLogging.h new file mode 100755 index 00000000..55b7c6b2 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPLogging.h @@ -0,0 +1,136 @@ +/** + * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack. + * + * The Google Code page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * Here's what you need to know concerning how logging is setup for CocoaHTTPServer: + * + * There are 4 log levels: + * - Error + * - Warning + * - Info + * - Verbose + * + * In addition to this, there is a Trace flag that can be enabled. + * When tracing is enabled, it spits out the methods that are being called. + * + * Please note that tracing is separate from the log levels. + * For example, one could set the log level to warning, and enable tracing. + * + * All logging is asynchronous, except errors. + * To use logging within your own custom files, follow the steps below. + * + * Step 1: + * Import this header in your implementation file: + * + * #import "HTTPLogging.h" + * + * Step 2: + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; + * + * If you wish to enable tracing, you could do something like this: + * + * // Debug levels: off, error, warn, info, verbose + * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE; + * + * Step 3: + * Replace your NSLog statements with HTTPLog statements according to the severity of the message. + * + * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!"); + * + * HTTPLog works exactly the same as NSLog. + * This means you can pass it multiple variables just like NSLog. +**/ + +#import "DDLog.h" + +// Define logging context for every log message coming from the HTTP server. +// The logging context can be extracted from the DDLogMessage from within the logging framework, +// which gives loggers, formatters, and filters the ability to optionally process them differently. + +#define HTTP_LOG_CONTEXT 80 + +// Configure log levels. + +#define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001 +#define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010 +#define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100 +#define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000 + +#define HTTP_LOG_LEVEL_OFF 0 // 0...00000 +#define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001 +#define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011 +#define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111 +#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111 + +// Setup fine grained logging. +// The first 4 bits are being used by the standard log levels (0 - 3) +// +// We're going to add tracing, but NOT as a log level. +// Tracing can be turned on and off independently of log level. + +#define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000 + +// Setup the usual boolean macros. + +#define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR) +#define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN) +#define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO) +#define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE) +#define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE) + +// Configure asynchronous logging. +// We follow the default configuration, +// but we reserve a special macro to easily disable asynchronous logging for debugging purposes. + +#define HTTP_LOG_ASYNC_ENABLED YES + +#define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED) +#define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED) +#define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED) +#define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED) +#define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED) + +// Define logging primitives. + +#define HTTPLogError(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogWarn(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogInfo(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogVerbose(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogTrace() LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ + HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD) + +#define HTTPLogTrace2(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + + +#define HTTPLogCError(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogCWarn(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogCInfo(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogCVerbose(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + +#define HTTPLogCTrace() LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ + HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__) + +#define HTTPLogCTrace2(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ + HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) + diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.h new file mode 100755 index 00000000..68f97626 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.h @@ -0,0 +1,48 @@ +/** + * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class. +**/ + +#import + +#if TARGET_OS_IPHONE + // Note: You may need to add the CFNetwork Framework to your project + #import +#endif + +#define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0) +#define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1) + + +@interface HTTPMessage : NSObject +{ + CFHTTPMessageRef message; +} + +- (id)initEmptyRequest; + +- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; + +- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; + +- (BOOL)appendData:(NSData *)data; + +- (BOOL)isHeaderComplete; + +- (NSString *)version; + +- (NSString *)method; +- (NSURL *)url; + +- (NSInteger)statusCode; + +- (NSDictionary *)allHeaderFields; +- (NSString *)headerField:(NSString *)headerField; + +- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; + +- (NSData *)messageData; + +- (NSData *)body; +- (void)setBody:(NSData *)body; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.m new file mode 100755 index 00000000..f95eefa8 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.m @@ -0,0 +1,113 @@ +#import "HTTPMessage.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + + +@implementation HTTPMessage + +- (id)initEmptyRequest +{ + if ((self = [super init])) + { + message = CFHTTPMessageCreateEmpty(NULL, YES); + } + return self; +} + +- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version +{ + if ((self = [super init])) + { + message = CFHTTPMessageCreateRequest(NULL, + (__bridge CFStringRef)method, + (__bridge CFURLRef)url, + (__bridge CFStringRef)version); + } + return self; +} + +- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version +{ + if ((self = [super init])) + { + message = CFHTTPMessageCreateResponse(NULL, + (CFIndex)code, + (__bridge CFStringRef)description, + (__bridge CFStringRef)version); + } + return self; +} + +- (void)dealloc +{ + if (message) + { + CFRelease(message); + } +} + +- (BOOL)appendData:(NSData *)data +{ + return CFHTTPMessageAppendBytes(message, [data bytes], [data length]); +} + +- (BOOL)isHeaderComplete +{ + return CFHTTPMessageIsHeaderComplete(message); +} + +- (NSString *)version +{ + return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message); +} + +- (NSString *)method +{ + return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message); +} + +- (NSURL *)url +{ + return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message); +} + +- (NSInteger)statusCode +{ + return (NSInteger)CFHTTPMessageGetResponseStatusCode(message); +} + +- (NSDictionary *)allHeaderFields +{ + return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message); +} + +- (NSString *)headerField:(NSString *)headerField +{ + return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField); +} + +- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue +{ + CFHTTPMessageSetHeaderFieldValue(message, + (__bridge CFStringRef)headerField, + (__bridge CFStringRef)headerFieldValue); +} + +- (NSData *)messageData +{ + return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message); +} + +- (NSData *)body +{ + return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message); +} + +- (void)setBody:(NSData *)body +{ + CFHTTPMessageSetBody(message, (__bridge CFDataRef)body); +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPResponse.h new file mode 100755 index 00000000..d72cd053 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPResponse.h @@ -0,0 +1,149 @@ +#import + + +@protocol HTTPResponse + +/** + * Returns the length of the data in bytes. + * If you don't know the length in advance, implement the isChunked method and have it return YES. +**/ +- (UInt64)contentLength; + +/** + * The HTTP server supports range requests in order to allow things like + * file download resumption and optimized streaming on mobile devices. +**/ +- (UInt64)offset; +- (void)setOffset:(UInt64)offset; + +/** + * Returns the data for the response. + * You do not have to return data of the exact length that is given. + * You may optionally return data of a lesser length. + * However, you must never return data of a greater length than requested. + * Doing so could disrupt proper support for range requests. + * + * To support asynchronous responses, read the discussion at the bottom of this header. +**/ +- (NSData *)readDataOfLength:(NSUInteger)length; + +/** + * Should only return YES after the HTTPConnection has read all available data. + * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method. +**/ +- (BOOL)isDone; + +@optional + +/** + * If you need time to calculate any part of the HTTP response headers (status code or header fields), + * this method allows you to delay sending the headers so that you may asynchronously execute the calculations. + * Simply implement this method and return YES until you have everything you need concerning the headers. + * + * This method ties into the asynchronous response architecture of the HTTPConnection. + * You should read the full discussion at the bottom of this header. + * + * If you return YES from this method, + * the HTTPConnection will wait for you to invoke the responseHasAvailableData method. + * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers. + * + * You should only delay sending the headers until you have everything you need concerning just the headers. + * Asynchronously generating the body of the response is not an excuse to delay sending the headers. + * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method. + * + * Important: You should read the discussion at the bottom of this header. +**/ +- (BOOL)delayResponseHeaders; + +/** + * Status code for response. + * Allows for responses such as redirect (301), etc. +**/ +- (NSInteger)status; + +/** + * If you want to add any extra HTTP headers to the response, + * simply return them in a dictionary in this method. +**/ +- (NSDictionary *)httpHeaders; + +/** + * If you don't know the content-length in advance, + * implement this method in your custom response class and return YES. + * + * Important: You should read the discussion at the bottom of this header. +**/ +- (BOOL)isChunked; + +/** + * This method is called from the HTTPConnection class when the connection is closed, + * or when the connection is finished with the response. + * If your response is asynchronous, you should implement this method so you know not to + * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated). +**/ +- (void)connectionDidClose; + +@end + + +/** + * Important notice to those implementing custom asynchronous and/or chunked responses: + * + * HTTPConnection supports asynchronous responses. All you have to do in your custom response class is + * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method. + * You don't have to wait until you have all of the response ready to invoke this method. For example, if you + * generate the response in incremental chunks, you could call responseHasAvailableData after generating + * each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this. + * + * The normal flow of events for an HTTPConnection while responding to a request is like this: + * - Send http resopnse headers + * - Get data from response via readDataOfLength method. + * - Add data to asyncSocket's write queue. + * - Wait for asyncSocket to notify it that the data has been sent. + * - Get more data from response via readDataOfLength method. + * - ... continue this cycle until the entire response has been sent. + * + * With an asynchronous response, the flow is a little different. + * + * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers. + * This allows the response to asynchronously execute any code needed to calculate a part of the header. + * An example might be the response needs to generate some custom header fields, + * or perhaps the response needs to look for a resource on network-attached storage. + * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet. + * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES. + * After returning YES from this method, the HTTPConnection will wait until the response invokes its + * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders + * method to see if the response is ready to send the headers. + * This cycle will continue until the delayResponseHeaders method returns NO. + * + * You should only delay sending the response headers until you have everything you need concerning just the headers. + * Asynchronously generating the body of the response is not an excuse to delay sending the headers. + * + * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method. + * You may or may not have any available data at this point. If you don't, then simply return nil. + * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send. + * + * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked + * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and + * return nil in your readDataOfLength whenever you don't have any available data in the requested range. + * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately. + * + * It's important that you also keep in mind that the HTTP server supports range requests. + * The setOffset method is mandatory, and should not be ignored. + * Make sure you take into account the offset within the readDataOfLength method. + * You should also be aware that the HTTPConnection automatically sorts any range requests. + * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99. + * + * HTTPConnection can also help you keep your memory footprint small. + * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into + * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do + * is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection + * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should + * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand, + * while at the same time keeping your memory footprint small, and your application lightning fast. + * + * If you don't know the content-length in advanced, you should also implement the isChunked method. + * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked". + * There's a good chance that if your response is asynchronous and dynamic, it's also chunked. + * If your response is chunked, you don't need to worry about range requests. +**/ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.h new file mode 100755 index 00000000..e907389b --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.h @@ -0,0 +1,215 @@ +#import +#import "MyHTTPConnection.h" +@class GCDAsyncSocket; +@class WebSocket; + +#if TARGET_OS_IPHONE + #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0 + #define IMPLEMENTED_PROTOCOLS + #else + #define IMPLEMENTED_PROTOCOLS + #endif +#else + #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6 + #define IMPLEMENTED_PROTOCOLS + #else + #define IMPLEMENTED_PROTOCOLS + #endif +#endif + + +@interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS +{ + // Underlying asynchronous TCP/IP socket + GCDAsyncSocket *asyncSocket; + + // Dispatch queues + dispatch_queue_t serverQueue; + dispatch_queue_t connectionQueue; + void *IsOnServerQueueKey; + void *IsOnConnectionQueueKey; + + // HTTP server configuration + NSString *documentRoot; + Class connectionClass; + NSString *interface; + UInt32 port; + + // NSNetService and related variables + NSNetService *netService; + NSString *domain; + NSString *type; + NSString *name; + NSString *publishedName; + NSDictionary *txtRecordDictionary; + + // Connection management + NSMutableArray *connections; + NSMutableArray *webSockets; + NSLock *connectionsLock; + NSLock *webSocketsLock; + + BOOL isRunning; +} + +/** + * Specifies the document root to serve files from. + * For example, if you set this to "/Users//Sites", + * then it will serve files out of the local Sites directory (including subdirectories). + * + * The default value is nil. + * The default server configuration will not serve any files until this is set. + * + * If you change the documentRoot while the server is running, + * the change will affect future incoming http connections. +**/ +- (NSString *)documentRoot; +- (void)setDocumentRoot:(NSString *)value; + +/** + * The connection class is the class used to handle incoming HTTP connections. + * + * The default value is [HTTPConnection class]. + * You can override HTTPConnection, and then set this to [MyHTTPConnection class]. + * + * If you change the connectionClass while the server is running, + * the change will affect future incoming http connections. +**/ +- (Class)connectionClass; +- (void)setConnectionClass:(Class)value; + +/** + * Set what interface you'd like the server to listen on. + * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc. + * + * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). + * You may also use the special strings "localhost" or "loopback" to specify that + * the socket only accept connections from the local machine. +**/ +- (NSString *)interface; +- (void)setInterface:(NSString *)value; + +/** + * The port number to run the HTTP server on. + * + * The default port number is zero, meaning the server will automatically use any available port. + * This is the recommended port value, as it avoids possible port conflicts with other applications. + * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number. + * + * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024. + * + * You can change the port property while the server is running, but it won't affect the running server. + * To actually change the port the server is listening for connections on you'll need to restart the server. + * + * The listeningPort method will always return the port number the running server is listening for connections on. + * If the server is not running this method returns 0. +**/ +- (UInt32)port; +- (UInt32)listeningPort; +- (void)setPort:(UInt32)value; + +/** + * Bonjour domain for publishing the service. + * The default value is "local.". + * + * Note: Bonjour publishing requires you set a type. + * + * If you change the domain property after the bonjour service has already been published (server already started), + * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. +**/ +- (NSString *)domain; +- (void)setDomain:(NSString *)value; + +/** + * Bonjour name for publishing the service. + * The default value is "". + * + * If using an empty string ("") for the service name when registering, + * the system will automatically use the "Computer Name". + * Using an empty string will also handle name conflicts + * by automatically appending a digit to the end of the name. + * + * Note: Bonjour publishing requires you set a type. + * + * If you change the name after the bonjour service has already been published (server already started), + * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. + * + * The publishedName method will always return the actual name that was published via the bonjour service. + * If the service is not running this method returns nil. +**/ +- (NSString *)name; +- (NSString *)publishedName; +- (void)setName:(NSString *)value; + +/** + * Bonjour type for publishing the service. + * The default value is nil. + * The service will not be published via bonjour unless the type is set. + * + * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.". + * + * If you change the type after the bonjour service has already been published (server already started), + * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. +**/ +- (NSString *)type; +- (void)setType:(NSString *)value; + +/** + * Republishes the service via bonjour if the server is running. + * If the service was not previously published, this method will publish it (if the server is running). +**/ +- (void)republishBonjour; + +/** + * +**/ +- (NSDictionary *)TXTRecordDictionary; +- (void)setTXTRecordDictionary:(NSDictionary *)dict; + +/** + * Attempts to starts the server on the configured port, interface, etc. + * + * If an error occurs, this method returns NO and sets the errPtr (if given). + * Otherwise returns YES on success. + * + * Some examples of errors that might occur: + * - You specified the server listen on a port which is already in use by another application. + * - You specified the server listen on a port number below 1024, which requires root priviledges. + * + * Code Example: + * + * NSError *err = nil; + * if (![httpServer start:&err]) + * { + * NSLog(@"Error starting http server: %@", err); + * } +**/ +- (BOOL)start:(NSError **)errPtr; + +/** + * Stops the server, preventing it from accepting any new connections. + * You may specify whether or not you want to close the existing client connections. + * + * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO]) +**/ +- (void)stop; +- (void)stop:(BOOL)keepExistingConnections; + +- (BOOL)isRunning; + +- (void)addWebSocket:(WebSocket *)ws; + +- (NSUInteger)numberOfHTTPConnections; +- (NSUInteger)numberOfWebSocketConnections; + +/** + Get the connections in the httpserver + + @return the connections with array + */ +- (NSMutableArray *)getConnectionsInServer; + +///添加代理使用 +@property (nonatomic,weak) UIViewController * viewcontroller; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.m new file mode 100755 index 00000000..c62dea63 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.m @@ -0,0 +1,781 @@ +#import "HTTPServer.h" +#import "GCDAsyncSocket.h" +#import "HTTPConnection.h" +#import "WebSocket.h" +#import "HTTPLogging.h" +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels: off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE; + +@interface HTTPServer (PrivateAPI) + +- (void)unpublishBonjour; +- (void)publishBonjour; + ++ (void)startBonjourThreadIfNeeded; ++ (void)performBonjourBlock:(dispatch_block_t)block; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation HTTPServer + +/** + * Standard Constructor. + * Instantiates an HTTP server, but does not start it. +**/ +- (id)init +{ + if ((self = [super init])) + { + HTTPLogTrace(); + + // Setup underlying dispatch queues + serverQueue = dispatch_queue_create("HTTPServer", NULL); + connectionQueue = dispatch_queue_create("HTTPConnection", NULL); + + IsOnServerQueueKey = &IsOnServerQueueKey; + IsOnConnectionQueueKey = &IsOnConnectionQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null + + dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL); + dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL); + + // Initialize underlying GCD based tcp socket + asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:serverQueue]; + + // Use default connection class of HTTPConnection + connectionClass = [HTTPConnection self]; + + // By default bind on all available interfaces, en1, wifi etc + interface = nil; + + // Use a default port of 0 + // This will allow the kernel to automatically pick an open port for us + port = 0; + // port = 12306; + // Configure default values for bonjour service + + // Bonjour domain. Use the local domain by default + domain = @"local."; + + // If using an empty string ("") for the service name when registering, + // the system will automatically use the "Computer Name". + // Passing in an empty string will also handle name conflicts + // by automatically appending a digit to the end of the name. + name = @""; + + // Initialize arrays to hold all the HTTP and webSocket connections + connections = [[NSMutableArray alloc] init]; + webSockets = [[NSMutableArray alloc] init]; + + connectionsLock = [[NSLock alloc] init]; + webSocketsLock = [[NSLock alloc] init]; + + // Register for notifications of closed connections + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(connectionDidDie:) + name:HTTPConnectionDidDieNotification + object:nil]; + + // Register for notifications of closed websocket connections + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(webSocketDidDie:) + name:WebSocketDidDieNotification + object:nil]; + + isRunning = NO; + } + return self; +} + +/** + * Standard Deconstructor. + * Stops the server, and clients, and releases any resources connected with this instance. +**/ +- (void)dealloc +{ + HTTPLogTrace(); + + // Remove notification observer + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + // Stop the server if it's running + [self stop]; + + // Release all instance variables + + #if !OS_OBJECT_USE_OBJC + dispatch_release(serverQueue); + dispatch_release(connectionQueue); + #endif + + [asyncSocket setDelegate:nil delegateQueue:NULL]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Server Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The document root is filesystem root for the webserver. + * Thus requests for /index.html will be referencing the index.html file within the document root directory. + * All file requests are relative to this document root. +**/ +- (NSString *)documentRoot +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + result = documentRoot; + }); + + return result; +} + +- (void)setDocumentRoot:(NSString *)value +{ + HTTPLogTrace(); + + // Document root used to be of type NSURL. + // Add type checking for early warning to developers upgrading from older versions. + + if (value && ![value isKindOfClass:[NSString class]]) + { + HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter", + THIS_FILE, THIS_METHOD, NSStringFromClass([value class])); + return; + } + + NSString *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + documentRoot = valueCopy; + }); + +} + +/** + * The connection class is the class that will be used to handle connections. + * That is, when a new connection is created, an instance of this class will be intialized. + * The default connection class is HTTPConnection. + * If you use a different connection class, it is assumed that the class extends HTTPConnection +**/ +- (Class)connectionClass +{ + __block Class result; + + dispatch_sync(serverQueue, ^{ + result = connectionClass; + }); + + return result; +} + +- (void)setConnectionClass:(Class)value +{ + HTTPLogTrace(); + + dispatch_async(serverQueue, ^{ + connectionClass = value; + }); +} + +/** + * What interface to bind the listening socket to. +**/ +- (NSString *)interface +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + result = interface; + }); + + return result; +} + +- (void)setInterface:(NSString *)value +{ + NSString *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + interface = valueCopy; + }); + +} + +/** + * The port to listen for connections on. + * By default this port is initially set to zero, which allows the kernel to pick an available port for us. + * After the HTTP server has started, the port being used may be obtained by this method. +**/ +- (UInt32)port +{ + __block UInt32 result; + + dispatch_sync(serverQueue, ^{ + result = port; + // result = 12306; + }); + + return result; +} + +- (UInt32)listeningPort +{ + __block UInt32 result; + + dispatch_sync(serverQueue, ^{ + if (isRunning) + result = [asyncSocket localPort]; + else + result = 0; + // result = 12306; + }); + + return result; +} + +- (void)setPort:(UInt32)value +{ + HTTPLogTrace(); + + dispatch_async(serverQueue, ^{ + port = value; + // port = 12306; + }); +} + +/** + * Domain on which to broadcast this service via Bonjour. + * The default domain is @"local". +**/ +- (NSString *)domain +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + result = domain; + }); + + return result; +} + +- (void)setDomain:(NSString *)value +{ + HTTPLogTrace(); + + NSString *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + domain = valueCopy; + }); + +} + +/** + * The name to use for this service via Bonjour. + * The default name is an empty string, + * which should result in the published name being the host name of the computer. +**/ +- (NSString *)name +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + result = name; + }); + + return result; +} + +- (NSString *)publishedName +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + + if (netService == nil) + { + result = nil; + } + else + { + + dispatch_block_t bonjourBlock = ^{ + result = [[netService name] copy]; + }; + + [[self class] performBonjourBlock:bonjourBlock]; + } + }); + + return result; +} + +- (void)setName:(NSString *)value +{ + NSString *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + name = valueCopy; + }); + +} + +/** + * The type of service to publish via Bonjour. + * No type is set by default, and one must be set in order for the service to be published. +**/ +- (NSString *)type +{ + __block NSString *result; + + dispatch_sync(serverQueue, ^{ + result = type; + }); + + return result; +} + +- (void)setType:(NSString *)value +{ + NSString *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + type = valueCopy; + }); + +} + +/** + * The extra data to use for this service via Bonjour. +**/ +- (NSDictionary *)TXTRecordDictionary +{ + __block NSDictionary *result; + + dispatch_sync(serverQueue, ^{ + result = txtRecordDictionary; + }); + + return result; +} + +- (void)setTXTRecordDictionary:(NSDictionary *)value +{ + HTTPLogTrace(); + + NSDictionary *valueCopy = [value copy]; + + dispatch_async(serverQueue, ^{ + + txtRecordDictionary = valueCopy; + + // Update the txtRecord of the netService if it has already been published + if (netService) + { + NSNetService *theNetService = netService; + NSData *txtRecordData = nil; + if (txtRecordDictionary) + txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary]; + + dispatch_block_t bonjourBlock = ^{ + [theNetService setTXTRecordData:txtRecordData]; + }; + + [[self class] performBonjourBlock:bonjourBlock]; + } + }); + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Server Control +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)start:(NSError **)errPtr +{ + HTTPLogTrace(); + + __block BOOL success = YES; + __block NSError *err = nil; + + dispatch_sync(serverQueue, ^{ @autoreleasepool { + // port = 12306; + success = [asyncSocket acceptOnInterface:interface port:port error:&err]; + if (success) + { + HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]); + //HTTPLogInfo(@"%@: Started HTTP server on port %d", THIS_FILE, 12306); + + isRunning = YES; + [self publishBonjour]; + } + else + { + HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err); + } + }}); + + if (errPtr) + *errPtr = err; + + return success; +} + +- (void)stop +{ + [self stop:NO]; +} + +- (void)stop:(BOOL)keepExistingConnections +{ + HTTPLogTrace(); + + dispatch_sync(serverQueue, ^{ @autoreleasepool { + + // First stop publishing the service via bonjour + [self unpublishBonjour]; + + // Stop listening / accepting incoming connections + [asyncSocket disconnect]; + isRunning = NO; + + if (!keepExistingConnections) + { + // Stop all HTTP connections the server owns + [connectionsLock lock]; + for (HTTPConnection *connection in connections) + { + [connection stop]; + } + [connections removeAllObjects]; + [connectionsLock unlock]; + + // Stop all WebSocket connections the server owns + [webSocketsLock lock]; + for (WebSocket *webSocket in webSockets) + { + [webSocket stop]; + } + [webSockets removeAllObjects]; + [webSocketsLock unlock]; + } + }}); +} + +- (BOOL)isRunning +{ + __block BOOL result; + + dispatch_sync(serverQueue, ^{ + result = isRunning; + }); + + return result; +} + +- (void)addWebSocket:(WebSocket *)ws +{ + [webSocketsLock lock]; + + HTTPLogTrace(); + [webSockets addObject:ws]; + + [webSocketsLock unlock]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Server Status +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the number of http client connections that are currently connected to the server. +**/ +- (NSUInteger)numberOfHTTPConnections +{ + NSUInteger result = 0; + + [connectionsLock lock]; + result = [connections count]; + [connectionsLock unlock]; + + return result; +} + +/** + * Returns the number of websocket client connections that are currently connected to the server. +**/ +- (NSUInteger)numberOfWebSocketConnections +{ + NSUInteger result = 0; + + [webSocketsLock lock]; + result = [webSockets count]; + [webSocketsLock unlock]; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Incoming Connections +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (HTTPConfig *)config +{ + // Override me if you want to provide a custom config to the new connection. + // + // Generally this involves overriding the HTTPConfig class to include any custom settings, + // and then having this method return an instance of 'MyHTTPConfig'. + + // Note: Think you can make the server faster by putting each connection on its own queue? + // Then benchmark it before and after and discover for yourself the shocking truth! + // + // Try the apache benchmark tool (already installed on your Mac): + // $ ab -n 1000 -c 1 http://localhost:/some_path.html + + return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue]; +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket + configuration:[self config]]; + ((MyHTTPConnection *)newConnection).delegate = self.viewcontroller; + [connectionsLock lock]; + [connections addObject:newConnection]; + [connectionsLock unlock]; + + [newConnection start]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Bonjour +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)publishBonjour +{ + HTTPLogTrace(); + + NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue"); + + if (type) + { + netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:[asyncSocket localPort]]; + // netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:12306]; + [netService setDelegate:self]; + + NSNetService *theNetService = netService; + NSData *txtRecordData = nil; + if (txtRecordDictionary) + txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary]; + + dispatch_block_t bonjourBlock = ^{ + + [theNetService removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [theNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [theNetService publish]; + + // Do not set the txtRecordDictionary prior to publishing!!! + // This will cause the OS to crash!!! + if (txtRecordData) + { + [theNetService setTXTRecordData:txtRecordData]; + } + }; + + [[self class] startBonjourThreadIfNeeded]; + [[self class] performBonjourBlock:bonjourBlock]; + } +} + +- (void)unpublishBonjour +{ + HTTPLogTrace(); + + NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue"); + + if (netService) + { + NSNetService *theNetService = netService; + + dispatch_block_t bonjourBlock = ^{ + + [theNetService stop]; + }; + + [[self class] performBonjourBlock:bonjourBlock]; + + netService = nil; + } +} + +/** + * Republishes the service via bonjour if the server is running. + * If the service was not previously published, this method will publish it (if the server is running). +**/ +- (void)republishBonjour +{ + HTTPLogTrace(); + + dispatch_async(serverQueue, ^{ + + [self unpublishBonjour]; + [self publishBonjour]; + }); +} + +/** + * Called when our bonjour service has been successfully published. + * This method does nothing but output a log message telling us about the published service. +**/ +- (void)netServiceDidPublish:(NSNetService *)ns +{ + // Override me to do something here... + // + // Note: This method is invoked on our bonjour thread. + + HTTPLogInfo(@"Bonjour Service Published: domain(%@) type(%@) name(%@)", [ns domain], [ns type], [ns name]); +} + +/** + * Called if our bonjour service failed to publish itself. + * This method does nothing but output a log message telling us about the published service. +**/ +- (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict +{ + // Override me to do something here... + // + // Note: This method in invoked on our bonjour thread. + + HTTPLogWarn(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@", + [ns domain], [ns type], [ns name], errorDict); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Notifications +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted. + * It allows us to remove the connection from our array. +**/ +- (void)connectionDidDie:(NSNotification *)notification +{ + // Note: This method is called on the connection queue that posted the notification + + [connectionsLock lock]; + + HTTPLogTrace(); + [connections removeObject:[notification object]]; + + [connectionsLock unlock]; +} + +/** + * This method is automatically called when a notification of type WebSocketDidDieNotification is posted. + * It allows us to remove the websocket from our array. +**/ +- (void)webSocketDidDie:(NSNotification *)notification +{ + // Note: This method is called on the connection queue that posted the notification + + [webSocketsLock lock]; + + HTTPLogTrace(); + [webSockets removeObject:[notification object]]; + + [webSocketsLock unlock]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Bonjour Thread +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * NSNetService is runloop based, so it requires a thread with a runloop. + * This gives us two options: + * + * - Use the main thread + * - Setup our own dedicated thread + * + * Since we have various blocks of code that need to synchronously access the netservice objects, + * using the main thread becomes troublesome and a potential for deadlock. +**/ + +static NSThread *bonjourThread; + ++ (void)startBonjourThreadIfNeeded +{ + HTTPLogTrace(); + + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + + HTTPLogVerbose(@"%@: Starting bonjour thread...", THIS_FILE); + + bonjourThread = [[NSThread alloc] initWithTarget:self + selector:@selector(bonjourThread) + object:nil]; + [bonjourThread start]; + }); +} + ++ (void)bonjourThread +{ + @autoreleasepool { + + HTTPLogVerbose(@"%@: BonjourThread: Started", THIS_FILE); + + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for 10,000 years. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(donothingatall:) + userInfo:nil + repeats:YES]; +#pragma clang diagnostic pop + + [[NSRunLoop currentRunLoop] run]; + + HTTPLogVerbose(@"%@: BonjourThread: Aborted", THIS_FILE); + + } +} + ++ (void)executeBonjourBlock:(dispatch_block_t)block +{ + HTTPLogTrace(); + + NSAssert([NSThread currentThread] == bonjourThread, @"Executed on incorrect thread"); + + block(); +} + ++ (void)performBonjourBlock:(dispatch_block_t)block +{ + HTTPLogTrace(); + + [self performSelector:@selector(executeBonjourBlock:) + onThread:bonjourThread + withObject:block + waitUntilDone:YES]; +} + +- (NSMutableArray *)getConnectionsInServer { + return connections.count > 0 ? connections : [NSMutableArray array]; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.h new file mode 100755 index 00000000..9f12750f --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.h @@ -0,0 +1,65 @@ + +#import "MultipartMessageHeader.h" + +/* +Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies) +Part two: http://tools.ietf.org/html/rfc2046 (Media Types) +Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text) +Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) +Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) + +Internet message format: http://tools.ietf.org/html/rfc2822 + +Multipart/form-data http://tools.ietf.org/html/rfc2388 +*/ + +@class MultipartFormDataParser; + +//----------------------------------------------------------------- +// protocol MultipartFormDataParser +//----------------------------------------------------------------- + +@protocol MultipartFormDataParserDelegate +@optional +- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header; +- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header; +- (void) processPreambleData:(NSData*) data; +- (void) processEpilogueData:(NSData*) data; +- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header; +@end + +//----------------------------------------------------------------- +// interface MultipartFormDataParser +//----------------------------------------------------------------- + +@interface MultipartFormDataParser : NSObject { +NSMutableData* pendingData; + NSData* boundaryData; + MultipartMessageHeader* currentHeader; + + BOOL waitingForCRLF; + BOOL reachedEpilogue; + BOOL processedPreamble; + BOOL checkForContentEnd; + +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif + int currentEncoding; + NSStringEncoding formEncoding; +} + +- (BOOL) appendData:(NSData*) data; + +- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding; + +#if __has_feature(objc_arc_weak) + @property(weak, readwrite) id delegate; +#else + @property(unsafe_unretained, readwrite) id delegate; +#endif +@property(readwrite) NSStringEncoding formEncoding; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.m new file mode 100755 index 00000000..2f573ae5 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.m @@ -0,0 +1,529 @@ + +#import "MultipartFormDataParser.h" +#import "DDData.h" +#import "HTTPLogging.h" + +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + +#ifdef __x86_64__ +#define FMTNSINT "li" +#else +#define FMTNSINT "i" +#endif + + +//----------------------------------------------------------------- +// interface MultipartFormDataParser (private) +//----------------------------------------------------------------- + + +@interface MultipartFormDataParser (private) ++ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding; + +- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int) offset; +- (int) findContentEnd:(NSData*) data fromOffset:(int) offset; + +- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSUInteger) length encoding:(int) encoding; +- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data; + +- (int) processPreamble:(NSData*) workingData; + +@end + + +//----------------------------------------------------------------- +// implementation MultipartFormDataParser +//----------------------------------------------------------------- + + +@implementation MultipartFormDataParser +@synthesize delegate,formEncoding; + +- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) _formEncoding { + if( nil == (self = [super init]) ){ + return self; + } + if( nil == boundary ) { + HTTPLogWarn(@"MultipartFormDataParser: init with zero boundary"); + return nil; + } + boundaryData = [[@"\r\n--" stringByAppendingString:boundary] dataUsingEncoding:NSASCIIStringEncoding]; + + pendingData = [[NSMutableData alloc] init]; + currentEncoding = contentTransferEncoding_binary; + currentHeader = nil; + + formEncoding = _formEncoding; + reachedEpilogue = NO; + processedPreamble = NO; + + return self; +} + + +- (BOOL) appendData:(NSData *)data { + // Can't parse without boundary; + if( nil == boundaryData ) { + HTTPLogError(@"MultipartFormDataParser: Trying to parse multipart without specifying a valid boundary"); + assert(false); + return NO; + } + NSData* workingData = data; + + if( pendingData.length ) { + [pendingData appendData:data]; + workingData = pendingData; + } + + // the parser saves parse stat in the offset variable, which indicates offset of unhandled part in + // currently received chunk. Before returning, we always drop all data up to offset, leaving + // only unhandled for the next call + + int offset = 0; + + // don't parse data unless its size is greater then boundary length, so we couldn't + // misfind the boundary, if it got split into different data chunks + NSUInteger sizeToLeavePending = boundaryData.length; + + if( !reachedEpilogue && workingData.length <= sizeToLeavePending ) { + // not enough data even to start parsing. + // save to pending data. + if( !pendingData.length ) { + [pendingData appendData:data]; + } + if( checkForContentEnd ) { + if( pendingData.length >= 2 ) { + if( *(uint16_t*)(pendingData.bytes + offset) == 0x2D2D ) { + // we found the multipart end. all coming next is an epilogue. + HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); + waitingForCRLF = YES; + reachedEpilogue = YES; + offset+= 2; + } + else { + checkForContentEnd = NO; + waitingForCRLF = YES; + return YES; + } + } else { + return YES; + } + + } + else { + return YES; + } + } + while( true ) { + if( checkForContentEnd ) { + // the flag will be raised to check if the last part was the last one. + if( offset < workingData.length -1 ) { + char* bytes = (char*) workingData.bytes; + if( *(uint16_t*)(bytes + offset) == 0x2D2D ) { + // we found the multipart end. all coming next is an epilogue. + HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); + checkForContentEnd = NO; + reachedEpilogue = YES; + // still wait for CRLF, that comes after boundary, but before epilogue. + waitingForCRLF = YES; + offset += 2; + } + else { + // it's not content end, we have to wait till separator line end before next part comes + waitingForCRLF = YES; + checkForContentEnd = NO; + } + } + else { + // we haven't got enough data to check for content end. + // save current unhandled data (it may be 1 byte) to pending and recheck on next chunk received + if( offset < workingData.length ) { + [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; + } + else { + // there is no unhandled data now, wait for more chunks + [pendingData setData:[NSData data]]; + } + return YES; + } + } + if( waitingForCRLF ) { + + // the flag will be raised in the code below, meaning, we've read the boundary, but + // didnt find the end of boundary line yet. + + offset = [self offsetTillNewlineSinceOffset:offset inData:workingData]; + if( -1 == offset ) { + // didnt find the endl again. + if( offset ) { + // we still have to save the unhandled data (maybe it's 1 byte CR) + if( *((char*)workingData.bytes + workingData.length -1) == '\r' ) { + [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; + } + else { + // or save nothing if it wasnt + [pendingData setData:[NSData data]]; + } + } + return YES; + } + waitingForCRLF = NO; + } + if( !processedPreamble ) { + // got to find the first boundary before the actual content begins. + offset = [self processPreamble:workingData]; + // wait for more data for preamble + if( -1 == offset ) + return YES; + // invoke continue to skip newline after boundary. + continue; + } + + if( reachedEpilogue ) { + // parse all epilogue data to delegate. + if( [delegate respondsToSelector:@selector(processEpilogueData:)] ) { + NSData* epilogueData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length: workingData.length - offset freeWhenDone:NO]; + [delegate processEpilogueData: epilogueData]; + } + return YES; + } + + if( nil == currentHeader ) { + // nil == currentHeader is a state flag, indicating we are waiting for header now. + // whenever part is over, currentHeader is set to nil. + + // try to find CRLFCRLF bytes in the data, which indicates header end. + // we won't parse header parts, as they won't be too large. + int headerEnd = [self findHeaderEnd:workingData fromOffset:offset]; + if( -1 == headerEnd ) { + // didn't recieve the full header yet. + if( !pendingData.length) { + // store the unprocessed data till next chunks come + [pendingData appendBytes:data.bytes + offset length:data.length - offset]; + } + else { + if( offset ) { + // save the current parse state; drop all handled data and save unhandled only. + pendingData = [[NSMutableData alloc] initWithBytes: (char*) workingData.bytes + offset length:workingData.length - offset]; + } + } + return YES; + } + else { + + // let the header parser do it's job from now on. + NSData * headerData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length:headerEnd + 2 - offset freeWhenDone:NO]; + currentHeader = [[MultipartMessageHeader alloc] initWithData:headerData formEncoding:formEncoding]; + + if( nil == currentHeader ) { + // we've found the data is in wrong format. + HTTPLogError(@"MultipartFormDataParser: MultipartFormDataParser: wrong input format, coulnd't get a valid header"); + return NO; + } + if( [delegate respondsToSelector:@selector(processStartOfPartWithHeader:)] ) { + [delegate processStartOfPartWithHeader:currentHeader]; + } + + HTTPLogVerbose(@"MultipartFormDataParser: MultipartFormDataParser: Retrieved part header."); + } + // skip the two trailing \r\n, in addition to the whole header. + offset = headerEnd + 4; + } + // after we've got the header, we try to + // find the boundary in the data. + int contentEnd = [self findContentEnd:workingData fromOffset:offset]; + + if( contentEnd == -1 ) { + + // this case, we didn't find the boundary, so the data is related to the current part. + // we leave the sizeToLeavePending amount of bytes to make sure we don't include + // boundary part in processed data. + NSUInteger sizeToPass = workingData.length - offset - sizeToLeavePending; + + // if we parse BASE64 encoded data, or Quoted-Printable data, we will make sure we don't break the format + int leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding]; + sizeToPass -= leaveTrailing; + + if( sizeToPass <= 0 ) { + // wait for more data! + if( offset ) { + [pendingData setData:[NSData dataWithBytes:(char*) workingData.bytes + offset length:workingData.length - offset]]; + } + return YES; + } + // decode the chunk and let the delegate use it (store in a file, for example) + NSData* decodedData = [MultipartFormDataParser decodedDataFromData:[NSData dataWithBytesNoCopy:(char*)workingData.bytes + offset length:workingData.length - offset - sizeToLeavePending freeWhenDone:NO] encoding:currentEncoding]; + + if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { + HTTPLogVerbose(@"MultipartFormDataParser: Processed %"FMTNSINT" bytes of body",sizeToPass); + + [delegate processContent: decodedData WithHeader:currentHeader]; + } + + // store the unprocessed data till the next chunks come. + [pendingData setData:[NSData dataWithBytes:(char*)workingData.bytes + workingData.length - sizeToLeavePending length:sizeToLeavePending]]; + return YES; + } + else { + + // Here we found the boundary. + // let the delegate process it, and continue going to the next parts. + if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { + [delegate processContent:[NSData dataWithBytesNoCopy:(char*) workingData.bytes + offset length:contentEnd - offset freeWhenDone:NO] WithHeader:currentHeader]; + } + + if( [delegate respondsToSelector:@selector(processEndOfPartWithHeader:)] ){ + [delegate processEndOfPartWithHeader:currentHeader]; + HTTPLogVerbose(@"MultipartFormDataParser: End of body part"); + } + currentHeader = nil; + + // set up offset to continue with the remaining data (if any) + // cast to int because above comment suggests a small number + offset = contentEnd + (int)boundaryData.length; + checkForContentEnd = YES; + // setting the flag tells the parser to skip all the data till CRLF + } + } + return YES; +} + + +//----------------------------------------------------------------- +#pragma mark private methods + +- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data { + char* bytes = (char*) data.bytes; + NSUInteger length = data.length; + if( offset >= length - 1 ) + return -1; + + while ( *(uint16_t*)(bytes + offset) != 0x0A0D ) { + // find the trailing \r\n after the boundary. The boundary line might have any number of whitespaces before CRLF, according to rfc2046 + + // in debug, we might also want to know, if the file is somehow misformatted. +#ifdef DEBUG + if( !isspace(*(bytes+offset)) ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset) ); + } + if( !isspace(*(bytes+offset+1)) ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset+1) ); + } +#endif + offset++; + if( offset >= length ) { + // no endl found within current data + return -1; + } + } + + offset += 2; + return offset; +} + + +- (int) processPreamble:(NSData*) data { + int offset = 0; + + char* boundaryBytes = (char*) boundaryData.bytes + 2; // the first boundary won't have CRLF preceding. + char* dataBytes = (char*) data.bytes; + NSUInteger boundaryLength = boundaryData.length - 2; + NSUInteger dataLength = data.length; + + // find the boundary without leading CRLF. + while( offset < dataLength - boundaryLength +1 ) { + int i; + for( i = 0;i < boundaryLength; i++ ) { + if( boundaryBytes[i] != dataBytes[offset + i] ) + break; + } + if( i == boundaryLength ) { + break; + } + offset++; + } + + if( offset == dataLength ) { + // the end of preamble wasn't found in this chunk + NSUInteger sizeToProcess = dataLength - boundaryLength; + if( sizeToProcess > 0) { + if( [delegate respondsToSelector:@selector(processPreambleData:)] ) { + NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: data.length - offset - boundaryLength freeWhenDone:NO]; + [delegate processPreambleData:preambleData]; + HTTPLogVerbose(@"MultipartFormDataParser: processed preamble"); + } + pendingData = [NSMutableData dataWithBytes: data.bytes + data.length - boundaryLength length:boundaryLength]; + } + return -1; + } + else { + if ( offset && [delegate respondsToSelector:@selector(processPreambleData:)] ) { + NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: offset freeWhenDone:NO]; + [delegate processPreambleData:preambleData]; + } + offset +=boundaryLength; + // tells to skip CRLF after the boundary. + processedPreamble = YES; + waitingForCRLF = YES; + } + return offset; +} + + + +- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int)offset { + char* bytes = (char*) workingData.bytes; + NSUInteger inputLength = workingData.length; + uint16_t separatorBytes = 0x0A0D; + + while( true ) { + if(inputLength < offset + 3 ) { + // wait for more data + return -1; + } + if( (*((uint16_t*) (bytes+offset)) == separatorBytes) && (*((uint16_t*) (bytes+offset)+1) == separatorBytes) ) { + return offset; + } + offset++; + } + return -1; +} + + +- (int) findContentEnd:(NSData*) data fromOffset:(int) offset { + char* boundaryBytes = (char*) boundaryData.bytes; + char* dataBytes = (char*) data.bytes; + NSUInteger boundaryLength = boundaryData.length; + NSUInteger dataLength = data.length; + + while( offset < dataLength - boundaryLength +1 ) { + int i; + for( i = 0;i < boundaryLength; i++ ) { + if( boundaryBytes[i] != dataBytes[offset + i] ) + break; + } + if( i == boundaryLength ) { + return offset; + } + offset++; + } + return -1; +} + + +- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding { + // If we have BASE64 or Quoted-Printable encoded data, we have to be sure + // we don't break the format. + int sizeToLeavePending = 0; + + if( encoding == contentTransferEncoding_base64 ) { + char* bytes = (char*) data.bytes; + int i; + for( i = length - 1; i > 0; i++ ) { + if( * (uint16_t*) (bytes + i) == 0x0A0D ) { + break; + } + } + // now we've got to be sure that the length of passed data since last line + // is multiplier of 4. + sizeToLeavePending = (length - i) & ~0x11; // size to leave pending = length-i - (length-i) %4; + return sizeToLeavePending; + } + + if( encoding == contentTransferEncoding_quotedPrintable ) { + // we don't pass more less then 3 bytes anyway. + if( length <= 2 ) + return length; + // check the last bytes to be start of encoded symbol. + const char* bytes = data.bytes + length - 2; + if( bytes[0] == '=' ) + return 2; + if( bytes[1] == '=' ) + return 1; + return 0; + } + return 0; +} + + +//----------------------------------------------------------------- +#pragma mark decoding + + ++ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding { + switch (encoding) { + case contentTransferEncoding_base64: { + return [data base64Decoded]; + } break; + + case contentTransferEncoding_quotedPrintable: { + return [self decodedDataFromQuotedPrintableData:data]; + } break; + + default: { + return data; + } break; + } +} + + ++ (NSData*) decodedDataFromQuotedPrintableData:(NSData *)data { +// http://tools.ietf.org/html/rfc2045#section-6.7 + + const char hex [] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; + + NSMutableData* result = [[NSMutableData alloc] initWithLength:data.length]; + const char* bytes = (const char*) data.bytes; + int count = 0; + NSUInteger length = data.length; + while( count < length ) { + if( bytes[count] == '=' ) { + [result appendBytes:bytes length:count]; + bytes = bytes + count + 1; + length -= count + 1; + count = 0; + + if( length < 3 ) { + HTTPLogWarn(@"MultipartFormDataParser: warning, trailing '=' in quoted printable data"); + } + // soft newline + if( bytes[0] == '\r' ) { + bytes += 1; + if(bytes[1] == '\n' ) { + bytes += 2; + } + continue; + } + char encodedByte = 0; + + for( int i = 0; i < sizeof(hex); i++ ) { + if( hex[i] == bytes[0] ) { + encodedByte += i << 4; + } + if( hex[i] == bytes[1] ) { + encodedByte += i; + } + } + [result appendBytes:&encodedByte length:1]; + bytes += 2; + } + +#ifdef DEBUG + if( (unsigned char) bytes[count] > 126 ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, character with code above 126 appears in quoted printable encoded data"); + } +#endif + + count++; + } + return result; +} + + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.h new file mode 100755 index 00000000..cd436235 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.h @@ -0,0 +1,33 @@ +// +// MultipartMessagePart.h +// HttpServer +// +// Created by Валерий Гаврилов on 29.03.12. +// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. +// + +#import + + +//----------------------------------------------------------------- +// interface MultipartMessageHeader +//----------------------------------------------------------------- +enum { + contentTransferEncoding_unknown, + contentTransferEncoding_7bit, + contentTransferEncoding_8bit, + contentTransferEncoding_binary, + contentTransferEncoding_base64, + contentTransferEncoding_quotedPrintable, +}; + +@interface MultipartMessageHeader : NSObject { + NSMutableDictionary* fields; + int encoding; + NSString* contentDispositionName; +} +@property (strong,readonly) NSDictionary* fields; +@property (readonly) int encoding; + +- (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding; +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.m new file mode 100755 index 00000000..7144d7a5 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.m @@ -0,0 +1,86 @@ +// +// MultipartMessagePart.m +// HttpServer +// +// Created by Валерий Гаврилов on 29.03.12. +// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. + +#import "MultipartMessageHeader.h" +#import "MultipartMessageHeaderField.h" + +#import "HTTPLogging.h" + +//----------------------------------------------------------------- +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + +//----------------------------------------------------------------- +// implementation MultipartMessageHeader +//----------------------------------------------------------------- + + +@implementation MultipartMessageHeader +@synthesize fields,encoding; + + +- (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding { + if( nil == (self = [super init]) ) { + return self; + } + + fields = [[NSMutableDictionary alloc] initWithCapacity:1]; + + // In case encoding is not mentioned, + encoding = contentTransferEncoding_unknown; + + char* bytes = (char*)data.bytes; + NSUInteger length = data.length; + int offset = 0; + + // split header into header fields, separated by \r\n + uint16_t fields_separator = 0x0A0D; // \r\n + while( offset < length - 2 ) { + + // the !isspace condition is to support header unfolding + if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) { + NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO]; + MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding]; + if( field ) { + [fields setObject:field forKey:field.name]; + HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name); + } + else { + NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding]; + HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr); + } + + // move to the next header field + bytes += offset + 2; + length -= offset + 2; + offset = 0; + continue; + } + ++ offset; + } + + if( !fields.count ) { + // it was an empty header. + // we have to set default values. + // default header. + [fields setObject:@"text/plain" forKey:@"Content-Type"]; + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@",fields]; +} + + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.h new file mode 100755 index 00000000..77201ae6 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.h @@ -0,0 +1,23 @@ + +#import + +//----------------------------------------------------------------- +// interface MultipartMessageHeaderField +//----------------------------------------------------------------- + +@interface MultipartMessageHeaderField : NSObject { + NSString* name; + NSString* value; + NSMutableDictionary* params; +} + +@property (strong, readonly) NSString* value; +@property (strong, readonly) NSDictionary* params; +@property (strong, readonly) NSString* name; + +//- (id) initWithLine:(NSString*) line; +//- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue; + +- (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.m new file mode 100755 index 00000000..2bc3e655 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.m @@ -0,0 +1,211 @@ + +#import "MultipartMessageHeaderField.h" +#import "HTTPLogging.h" + +//----------------------------------------------------------------- +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + + +// helpers +int findChar(const char* str,NSUInteger length, char c); +NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding); + +//----------------------------------------------------------------- +// interface MultipartMessageHeaderField (private) +//----------------------------------------------------------------- + + +@interface MultipartMessageHeaderField (private) +-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding; +@end + + +//----------------------------------------------------------------- +// implementation MultipartMessageHeaderField +//----------------------------------------------------------------- + +@implementation MultipartMessageHeaderField +@synthesize name,value,params; + +- (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { + params = [[NSMutableDictionary alloc] initWithCapacity:1]; + + char* bytes = (char*)data.bytes; + NSUInteger length = data.length; + + int separatorOffset = findChar(bytes, length, ':'); + if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { + HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header."); + // tear down + return nil; + } + + // header name is always ascii encoded; + name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; + if( nil == name ) { + HTTPLogError(@"MultipartFormDataParser: Bad MIME header name."); + // tear down + return nil; + } + + // skip the separator and the next ' ' symbol + bytes += separatorOffset + 2; + length -= separatorOffset + 2; + + separatorOffset = findChar(bytes, length, ';'); + if( separatorOffset == -1 ) { + // couldn't find ';', means we don't have extra params here. + value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; + + if( nil == value ) { + HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name); + // tear down + return nil; + } + return self; + } + + value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; + HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value); + // skipe the separator and the next ' ' symbol + bytes += separatorOffset + 2; + length -= separatorOffset + 2; + + // parse the "params" part of the header + if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { + NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; + HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); + HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr); + + return nil; + } + return self; +} + +-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding { + int offset = 0; + NSString* currentParam = nil; + BOOL insideQuote = NO; + while( offset < length ) { + if( bytes[offset] == '\"' ) { + if( !offset || bytes[offset-1] != '\\' ) { + insideQuote = !insideQuote; + } + } + + // skip quoted symbols + if( insideQuote ) { + ++ offset; + continue; + } + if( bytes[offset] == '=' ) { + if( currentParam ) { + // found '=' before terminating previous param. + return NO; + } + currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; + + bytes+=offset + 1; + length -= offset + 1; + offset = 0; + continue; + } + if( bytes[offset] == ';' ) { + if( !currentParam ) { + // found ; before stating '='. + HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header"); + return NO; + } + NSString* paramValue = extractParamValue(bytes, offset,encoding); + if( nil == paramValue ) { + HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); + } + else { +#ifdef DEBUG + if( [params objectForKey:currentParam] ) { + HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); + } +#endif + [params setObject:paramValue forKey:currentParam]; + HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); + } + + currentParam = nil; + + // ';' separator has ' ' following, skip them. + bytes+=offset + 2; + length -= offset + 2; + offset = 0; + } + ++ offset; + } + + // add last param + if( insideQuote ) { + HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name); +// return YES; + } + if( currentParam ) { + NSString* paramValue = extractParamValue(bytes, length, encoding); + + if( nil == paramValue ) { + HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); + } + +#ifdef DEBUG + if( [params objectForKey:currentParam] ) { + HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); + } +#endif + [params setObject:paramValue?paramValue:@"" forKey:currentParam]; + HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); + currentParam = nil; + } + + return YES; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; +} + +@end + +int findChar(const char* str, NSUInteger length, char c) { + int offset = 0; + while( offset < length ) { + if( str[offset] == c ) + return offset; + ++ offset; + } + return -1; +} + +NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) { + if( !length ) + return nil; + NSMutableString* value = nil; + + if( bytes[0] == '"' ) { + // values may be quoted. Strip the quotes to get what we need. + value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; + } + else { + value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; + } + // restore escaped symbols + NSRange range= [value rangeOfString:@"\\"]; + while ( range.length ) { + [value deleteCharactersInRange:range]; + range.location ++; + range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; + } + return value; +} + diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.h new file mode 100755 index 00000000..ffdaa97d --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.h @@ -0,0 +1,75 @@ +#import +#import "HTTPResponse.h" + +@class HTTPConnection; + +/** + * This is an asynchronous version of HTTPFileResponse. + * It reads data from the given file asynchronously via GCD. + * + * It may be overriden to allow custom post-processing of the data that has been read from the file. + * An example of this is the HTTPDynamicFileResponse class. +**/ + +@interface HTTPAsyncFileResponse : NSObject +{ + HTTPConnection *connection; + + NSString *filePath; + UInt64 fileLength; + UInt64 fileOffset; // File offset as pertains to data given to connection + UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) + + BOOL aborted; + + NSData *data; + + int fileFD; + void *readBuffer; + NSUInteger readBufferSize; // Malloced size of readBuffer + NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is + NSUInteger readRequestLength; + dispatch_queue_t readQueue; + dispatch_source_t readSource; + BOOL readSourceSuspended; +} + +- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; +- (NSString *)filePath; + +@end + +/** + * Explanation of Variables (excluding those that are obvious) + * + * fileOffset + * This is the number of bytes that have been returned to the connection via the readDataOfLength method. + * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, + * then the fileOffset variable remains at zero. + * This variable is used in the calculation of the isDone method. + * Only after all data has been returned to the connection are we actually done. + * + * readOffset + * Represents the offset of the file descriptor. + * In other words, the file position indidcator for our read stream. + * It might be easy to think of it as the total number of bytes that have been read from the file. + * However, this isn't entirely accurate, as the setOffset: method may have caused us to + * jump ahead in the file (lseek). + * + * readBuffer + * Malloc'd buffer to hold data read from the file. + * + * readBufferSize + * Total allocation size of malloc'd buffer. + * + * readBufferOffset + * Represents the position in the readBuffer where we should store new bytes. + * + * readRequestLength + * The total number of bytes that were requested from the connection. + * It's OK if we return a lesser number of bytes to the connection. + * It's NOT OK if we return a greater number of bytes to the connection. + * Doing so would disrupt proper support for range requests. + * If, however, the response is chunked then we don't need to worry about this. + * Chunked responses inheritly don't support range requests. +**/ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.m new file mode 100755 index 00000000..f6881a34 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.m @@ -0,0 +1,405 @@ +#import "HTTPAsyncFileResponse.h" +#import "HTTPConnection.h" +#import "HTTPLogging.h" + +#import +#import + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; + +#define NULL_FD -1 + +/** + * Architecure overview: + * + * HTTPConnection will invoke our readDataOfLength: method to fetch data. + * We will return nil, and then proceed to read the data via our readSource on our readQueue. + * Once the requested amount of data has been read, we then pause our readSource, + * and inform the connection of the available data. + * + * While our read is in progress, we don't have to worry about the connection calling any other methods, + * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection. + * To safely handle this, we do a synchronous dispatch on the readQueue, + * and nilify the connection as well as cancel our readSource. + * + * In order to minimize resource consumption during a HEAD request, + * we don't open the file until we have to (until the connection starts requesting data). +**/ + +@implementation HTTPAsyncFileResponse + +- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent +{ + if ((self = [super init])) + { + HTTPLogTrace(); + + connection = parent; // Parents retain children, children do NOT retain parents + + fileFD = NULL_FD; + filePath = [fpath copy]; + if (filePath == nil) + { + HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); + + return nil; + } + + NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; + if (fileAttributes == nil) + { + HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); + + return nil; + } + + fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; + fileOffset = 0; + + aborted = NO; + + // We don't bother opening the file here. + // If this is a HEAD request we only need to know the fileLength. + } + return self; +} + +- (void)abort +{ + HTTPLogTrace(); + + [connection responseDidAbort:self]; + aborted = YES; +} + +- (void)processReadBuffer +{ + // This method is here to allow superclasses to perform post-processing of the data. + // For an example, see the HTTPDynamicFileResponse class. + // + // At this point, the readBuffer has readBufferOffset bytes available. + // This method is in charge of updating the readBufferOffset. + // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...) + + // Copy the data out of the temporary readBuffer. + data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset]; + + // Reset the read buffer. + readBufferOffset = 0; + + // Notify the connection that we have data available for it. + [connection responseHasAvailableData:self]; +} + +- (void)pauseReadSource +{ + if (!readSourceSuspended) + { + HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self); + + readSourceSuspended = YES; + dispatch_suspend(readSource); + } +} + +- (void)resumeReadSource +{ + if (readSourceSuspended) + { + HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self); + + readSourceSuspended = NO; + dispatch_resume(readSource); + } +} + +- (void)cancelReadSource +{ + HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self); + + dispatch_source_cancel(readSource); + + // Cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + + if (readSourceSuspended) + { + readSourceSuspended = NO; + dispatch_resume(readSource); + } +} + +- (BOOL)openFileAndSetupReadSource +{ + HTTPLogTrace(); + + fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK)); + if (fileFD == NULL_FD) + { + HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath); + + return NO; + } + + HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); + + readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL); + readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue); + + + dispatch_source_set_event_handler(readSource, ^{ + + HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD); + + // Determine how much data we should read. + // + // It is OK if we ask to read more bytes than exist in the file. + // It is NOT OK to over-allocate the buffer. + + unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource); + + UInt64 _bytesLeftInFile = fileLength - readOffset; + + NSUInteger bytesAvailableOnFD; + NSUInteger bytesLeftInFile; + + bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD; + bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile; + + NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset; + + NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile); + + NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft); + + // Make sure buffer is big enough for read request. + // Do not over-allocate. + + if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset)) + { + readBufferSize = bytesToRead; + readBuffer = reallocf(readBuffer, (size_t)bytesToRead); + + if (readBuffer == NULL) + { + HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); + + [self pauseReadSource]; + [self abort]; + + return; + } + } + + // Perform the read + + HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); + + ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead); + + // Check the results + if (result < 0) + { + HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); + + [self pauseReadSource]; + [self abort]; + } + else if (result == 0) + { + HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); + + [self pauseReadSource]; + [self abort]; + } + else // (result > 0) + { + HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result); + + readOffset += result; + readBufferOffset += result; + + [self pauseReadSource]; + [self processReadBuffer]; + } + + }); + + int theFileFD = fileFD; + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theReadSource = readSource; + #endif + + dispatch_source_set_cancel_handler(readSource, ^{ + + // Do not access self from within this block in any way, shape or form. + // + // Note: You access self if you reference an iVar. + + HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD); + + #if !OS_OBJECT_USE_OBJC + dispatch_release(theReadSource); + #endif + close(theFileFD); + }); + + readSourceSuspended = YES; + + return YES; +} + +- (BOOL)openFileIfNeeded +{ + if (aborted) + { + // The file operation has been aborted. + // This could be because we failed to open the file, + // or the reading process failed. + return NO; + } + + if (fileFD != NULL_FD) + { + // File has already been opened. + return YES; + } + + return [self openFileAndSetupReadSource]; +} + +- (UInt64)contentLength +{ + HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength); + + return fileLength; +} + +- (UInt64)offset +{ + HTTPLogTrace(); + + return fileOffset; +} + +- (void)setOffset:(UInt64)offset +{ + HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); + + if (![self openFileIfNeeded]) + { + // File opening failed, + // or response has been aborted due to another error. + return; + } + + fileOffset = offset; + readOffset = offset; + + off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); + if (result == -1) + { + HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); + + [self abort]; + } +} + +- (NSData *)readDataOfLength:(NSUInteger)length +{ + HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); + + if (data) + { + NSUInteger dataLength = [data length]; + + HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength); + + fileOffset += dataLength; + + NSData *result = data; + data = nil; + + return result; + } + else + { + if (![self openFileIfNeeded]) + { + // File opening failed, + // or response has been aborted due to another error. + return nil; + } + + dispatch_sync(readQueue, ^{ + + NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed."); + + readRequestLength = length; + [self resumeReadSource]; + }); + + return nil; + } +} + +- (BOOL)isDone +{ + BOOL result = (fileOffset == fileLength); + + HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); + + return result; +} + +- (NSString *)filePath +{ + return filePath; +} + +- (BOOL)isAsynchronous +{ + HTTPLogTrace(); + + return YES; +} + +- (void)connectionDidClose +{ + HTTPLogTrace(); + + if (fileFD != NULL_FD) + { + dispatch_sync(readQueue, ^{ + + // Prevent any further calls to the connection + connection = nil; + + // Cancel the readSource. + // We do this here because the readSource's eventBlock has retained self. + // In other words, if we don't cancel the readSource, we will never get deallocated. + + [self cancelReadSource]; + }); + } +} + +- (void)dealloc +{ + HTTPLogTrace(); + + #if !OS_OBJECT_USE_OBJC + if (readQueue) dispatch_release(readQueue); + #endif + + if (readBuffer) + free(readBuffer); +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.h new file mode 100755 index 00000000..50469bc2 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.h @@ -0,0 +1,13 @@ +#import +#import "HTTPResponse.h" + + +@interface HTTPDataResponse : NSObject +{ + NSUInteger offset; + NSData *data; +} + +- (id)initWithData:(NSData *)data; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.m new file mode 100755 index 00000000..3b6c26a9 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.m @@ -0,0 +1,79 @@ +#import "HTTPDataResponse.h" +#import "HTTPLogging.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; + + +@implementation HTTPDataResponse + +- (id)initWithData:(NSData *)dataParam +{ + if((self = [super init])) + { + HTTPLogTrace(); + + offset = 0; + data = dataParam; + } + return self; +} + +- (void)dealloc +{ + HTTPLogTrace(); + +} + +- (UInt64)contentLength +{ + UInt64 result = (UInt64)[data length]; + + HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); + + return result; +} + +- (UInt64)offset +{ + HTTPLogTrace(); + + return offset; +} + +- (void)setOffset:(UInt64)offsetParam +{ + HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset); + + offset = (NSUInteger)offsetParam; +} + +- (NSData *)readDataOfLength:(NSUInteger)lengthParameter +{ + HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); + + NSUInteger remaining = [data length] - offset; + NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; + + void *bytes = (void *)([data bytes] + offset); + + offset += length; + + return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; +} + +- (BOOL)isDone +{ + BOOL result = (offset == [data length]); + + HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); + + return result; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.h new file mode 100755 index 00000000..5dd1a844 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.h @@ -0,0 +1,52 @@ +#import +#import "HTTPResponse.h" +#import "HTTPAsyncFileResponse.h" + +/** + * This class is designed to assist with dynamic content. + * Imagine you have a file that you want to make dynamic: + * + * + * + *

ComputerName Control Panel

+ * ... + *
  • System Time: SysTime
  • + * + * + * + * Now you could generate the entire file in Objective-C, + * but this would be a horribly tedious process. + * Beside, you want to design the file with professional tools to make it look pretty. + * + * So all you have to do is escape your dynamic content like this: + * + * ... + *

    %%ComputerName%% Control Panel

    + * ... + *
  • System Time: %%SysTime%%
  • + * + * And then you create an instance of this class with: + * + * - separator = @"%%" + * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } + * + * This class will then perform the replacements for you, on the fly, as it reads the file data. + * This class is also asynchronous, so it will perform the file IO using its own GCD queue. + * + * All keys for the replacementDictionary must be NSString's. + * Values for the replacementDictionary may be NSString's, or any object that + * returns what you want when its description method is invoked. +**/ + +@interface HTTPDynamicFileResponse : HTTPAsyncFileResponse +{ + NSData *separator; + NSDictionary *replacementDict; +} + +- (id)initWithFilePath:(NSString *)filePath + forConnection:(HTTPConnection *)connection + separator:(NSString *)separatorStr + replacementDictionary:(NSDictionary *)dictionary; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.m new file mode 100755 index 00000000..19f69a60 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.m @@ -0,0 +1,292 @@ +#import "HTTPDynamicFileResponse.h" +#import "HTTPConnection.h" +#import "HTTPLogging.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; + +#define NULL_FD -1 + + +@implementation HTTPDynamicFileResponse + +- (id)initWithFilePath:(NSString *)fpath + forConnection:(HTTPConnection *)parent + separator:(NSString *)separatorStr + replacementDictionary:(NSDictionary *)dict +{ + if ((self = [super initWithFilePath:fpath forConnection:parent])) + { + HTTPLogTrace(); + + separator = [separatorStr dataUsingEncoding:NSUTF8StringEncoding]; + replacementDict = dict; + } + return self; +} + +- (BOOL)isChunked +{ + HTTPLogTrace(); + + return YES; +} + +- (UInt64)contentLength +{ + // This method shouldn't be called since we're using a chunked response. + // We override it just to be safe. + + HTTPLogTrace(); + + return 0; +} + +- (void)setOffset:(UInt64)offset +{ + // This method shouldn't be called since we're using a chunked response. + // We override it just to be safe. + + HTTPLogTrace(); +} + +- (BOOL)isDone +{ + BOOL result = (readOffset == fileLength) && (readBufferOffset == 0); + + HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); + + return result; +} + +- (void)processReadBuffer +{ + HTTPLogTrace(); + + // At this point, the readBuffer has readBufferOffset bytes available. + // This method is in charge of updating the readBufferOffset. + + NSUInteger bufLen = readBufferOffset; + NSUInteger sepLen = [separator length]; + + // We're going to start looking for the separator at the beginning of the buffer, + // and stop when we get to the point where the separator would no longer fit in the buffer. + + NSUInteger offset = 0; + NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0; + + // In order to do the replacement, we need to find the starting and ending separator. + // For example: + // + // %%USER_NAME%% + // + // Where "%%" is the separator. + + BOOL found1 = NO; + BOOL found2 = NO; + + NSUInteger s1 = 0; + NSUInteger s2 = 0; + + const void *sep = [separator bytes]; + + while (offset < stopOffset) + { + const void *subBuffer = readBuffer + offset; + + if (memcmp(subBuffer, sep, sepLen) == 0) + { + if (!found1) + { + // Found the first separator + + found1 = YES; + s1 = offset; + offset += sepLen; + + HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1); + } + else + { + // Found the second separator + + found2 = YES; + s2 = offset; + offset += sepLen; + + HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2); + } + + if (found1 && found2) + { + // We found our separators. + // Now extract the string between the two separators. + + NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen)); + NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen)); + + // Wish we could use the simple subdataWithRange method. + // But that method copies the bytes... + // So for performance reasons, we need to use the methods that don't copy the bytes. + + void *strBuf = readBuffer + strRange.location; + NSUInteger strLen = strRange.length; + + NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding]; + if (key) + { + // Is there a given replacement for this key? + + id value = [replacementDict objectForKey:key]; + if (value) + { + // Found the replacement value. + // Now perform the replacement in the buffer. + + HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value); + + NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding]; + NSUInteger vLength = [v length]; + + if (fullRange.length == vLength) + { + // Replacement is exactly the same size as what it is replacing + + // memcpy(void *restrict dst, const void *restrict src, size_t n); + + memcpy(readBuffer + fullRange.location, [v bytes], vLength); + } + else // (fullRange.length != vLength) + { + NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length; + + if (diff > 0) + { + // Replacement is bigger than what it is replacing. + // Make sure there is room in the buffer for the replacement. + + if (diff > (readBufferSize - bufLen)) + { + NSUInteger inc = MAX(diff, 256); + + readBufferSize += inc; + readBuffer = reallocf(readBuffer, readBufferSize); + } + } + + // Move the data that comes after the replacement. + // + // If replacement is smaller than what it is replacing, + // then we are shifting the data toward the beginning of the buffer. + // + // If replacement is bigger than what it is replacing, + // then we are shifting the data toward the end of the buffer. + // + // memmove(void *dst, const void *src, size_t n); + // + // The memmove() function copies n bytes from src to dst. + // The two areas may overlap; the copy is always done in a non-destructive manner. + + void *src = readBuffer + fullRange.location + fullRange.length; + void *dst = readBuffer + fullRange.location + vLength; + + NSUInteger remaining = bufLen - (fullRange.location + fullRange.length); + + memmove(dst, src, remaining); + + // Now copy the replacement into its location. + // + // memcpy(void *restrict dst, const void *restrict src, size_t n) + // + // The memcpy() function copies n bytes from src to dst. + // If the two areas overlap, behavior is undefined. + + memcpy(readBuffer + fullRange.location, [v bytes], vLength); + + // And don't forget to update our indices. + + bufLen += diff; + offset += diff; + stopOffset += diff; + } + } + + } + + found1 = found2 = NO; + } + } + else + { + offset++; + } + } + + // We've gone through our buffer now, and performed all the replacements that we could. + // It's now time to update the amount of available data we have. + + if (readOffset == fileLength) + { + // We've read in the entire file. + // So there can be no more replacements. + + data = [[NSData alloc] initWithBytes:readBuffer length:bufLen]; + readBufferOffset = 0; + } + else + { + // There are a couple different situations that we need to take into account here. + // + // Imagine the following file: + // My name is %%USER_NAME%% + // + // Situation 1: + // The first chunk of data we read was "My name is %%". + // So we found the first separator, but not the second. + // In this case we can only return the data that precedes the first separator. + // + // Situation 2: + // The first chunk of data we read was "My name is %". + // So we didn't find any separators, but part of a separator may be included in our buffer. + + NSUInteger available; + if (found1) + { + // Situation 1 + available = s1; + } + else + { + // Situation 2 + available = stopOffset; + } + + // Copy available data + + data = [[NSData alloc] initWithBytes:readBuffer length:available]; + + // Remove the copied data from the buffer. + // We do this by shifting the remaining data toward the beginning of the buffer. + + NSUInteger remaining = bufLen - available; + + memmove(readBuffer, readBuffer + available, remaining); + readBufferOffset = remaining; + } + + [connection responseHasAvailableData:self]; +} + +- (void)dealloc +{ + HTTPLogTrace(); + + +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.h new file mode 100755 index 00000000..ca034260 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.h @@ -0,0 +1,9 @@ +#import "HTTPResponse.h" + +@interface HTTPErrorResponse : NSObject { + NSInteger _status; +} + +- (id)initWithErrorCode:(int)httpErrorCode; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.m new file mode 100755 index 00000000..a916a0b6 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.m @@ -0,0 +1,38 @@ +#import "HTTPErrorResponse.h" + +@implementation HTTPErrorResponse + +-(id)initWithErrorCode:(int)httpErrorCode +{ + if ((self = [super init])) + { + _status = httpErrorCode; + } + + return self; +} + +- (UInt64) contentLength { + return 0; +} + +- (UInt64) offset { + return 0; +} + +- (void)setOffset:(UInt64)offset { + ; +} + +- (NSData*) readDataOfLength:(NSUInteger)length { + return nil; +} + +- (BOOL) isDone { + return YES; +} + +- (NSInteger) status { + return _status; +} +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.h new file mode 100755 index 00000000..57deaf3a --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.h @@ -0,0 +1,25 @@ +#import +#import "HTTPResponse.h" + +@class HTTPConnection; + + +@interface HTTPFileResponse : NSObject +{ + HTTPConnection *connection; + + NSString *filePath; + UInt64 fileLength; + UInt64 fileOffset; + + BOOL aborted; + + int fileFD; + void *buffer; + NSUInteger bufferSize; +} + +- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; +- (NSString *)filePath; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.m new file mode 100755 index 00000000..69bff398 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.m @@ -0,0 +1,237 @@ +#import "HTTPFileResponse.h" +#import "HTTPConnection.h" +#import "HTTPLogging.h" + +#import +#import + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; + +#define NULL_FD -1 + + +@implementation HTTPFileResponse + +- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent +{ + if((self = [super init])) + { + HTTPLogTrace(); + + connection = parent; // Parents retain children, children do NOT retain parents + + fileFD = NULL_FD; + filePath = [[fpath copy] stringByResolvingSymlinksInPath]; + if (filePath == nil) + { + HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); + + return nil; + } + + NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + if (fileAttributes == nil) + { + HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); + + return nil; + } + + fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; + fileOffset = 0; + + aborted = NO; + + // We don't bother opening the file here. + // If this is a HEAD request we only need to know the fileLength. + } + return self; +} + +- (void)abort +{ + HTTPLogTrace(); + + [connection responseDidAbort:self]; + aborted = YES; +} + +- (BOOL)openFile +{ + HTTPLogTrace(); + + fileFD = open([filePath UTF8String], O_RDONLY); + if (fileFD == NULL_FD) + { + HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); + + [self abort]; + return NO; + } + + HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); + + return YES; +} + +- (BOOL)openFileIfNeeded +{ + if (aborted) + { + // The file operation has been aborted. + // This could be because we failed to open the file, + // or the reading process failed. + return NO; + } + + if (fileFD != NULL_FD) + { + // File has already been opened. + return YES; + } + + return [self openFile]; +} + +- (UInt64)contentLength +{ + HTTPLogTrace(); + + return fileLength; +} + +- (UInt64)offset +{ + HTTPLogTrace(); + + return fileOffset; +} + +- (void)setOffset:(UInt64)offset +{ + HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); + + if (![self openFileIfNeeded]) + { + // File opening failed, + // or response has been aborted due to another error. + return; + } + + fileOffset = offset; + + off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); + if (result == -1) + { + HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); + + [self abort]; + } +} + +- (NSData *)readDataOfLength:(NSUInteger)length +{ + HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); + + if (![self openFileIfNeeded]) + { + // File opening failed, + // or response has been aborted due to another error. + return nil; + } + + // Determine how much data we should read. + // + // It is OK if we ask to read more bytes than exist in the file. + // It is NOT OK to over-allocate the buffer. + + UInt64 bytesLeftInFile = fileLength - fileOffset; + + NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); + + // Make sure buffer is big enough for read request. + // Do not over-allocate. + + if (buffer == NULL || bufferSize < bytesToRead) + { + bufferSize = bytesToRead; + buffer = reallocf(buffer, (size_t)bufferSize); + + if (buffer == NULL) + { + HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); + + [self abort]; + return nil; + } + } + + // Perform the read + + HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); + + ssize_t result = read(fileFD, buffer, bytesToRead); + + // Check the results + + if (result < 0) + { + HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); + + [self abort]; + return nil; + } + else if (result == 0) + { + HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); + + [self abort]; + return nil; + } + else // (result > 0) + { + HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result); + + fileOffset += result; + + return [NSData dataWithBytes:buffer length:result]; + } +} + +- (BOOL)isDone +{ + BOOL result = (fileOffset == fileLength); + + HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); + + return result; +} + +- (NSString *)filePath +{ + return filePath; +} + +- (void)dealloc +{ + HTTPLogTrace(); + + if (fileFD != NULL_FD) + { + HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); + + close(fileFD); + } + + if (buffer) + free(buffer); + +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.h new file mode 100755 index 00000000..19e4b0ef --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.h @@ -0,0 +1,12 @@ +#import +#import "HTTPResponse.h" + + +@interface HTTPRedirectResponse : NSObject +{ + NSString *redirectPath; +} + +- (id)initWithPath:(NSString *)redirectPath; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.m new file mode 100755 index 00000000..7b605d5a --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.m @@ -0,0 +1,73 @@ +#import "HTTPRedirectResponse.h" +#import "HTTPLogging.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; + + +@implementation HTTPRedirectResponse + +- (id)initWithPath:(NSString *)path +{ + if ((self = [super init])) + { + HTTPLogTrace(); + + redirectPath = [path copy]; + } + return self; +} + +- (UInt64)contentLength +{ + return 0; +} + +- (UInt64)offset +{ + return 0; +} + +- (void)setOffset:(UInt64)offset +{ + // Nothing to do +} + +- (NSData *)readDataOfLength:(NSUInteger)length +{ + HTTPLogTrace(); + + return nil; +} + +- (BOOL)isDone +{ + return YES; +} + +- (NSDictionary *)httpHeaders +{ + HTTPLogTrace(); + + return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"]; +} + +- (NSInteger)status +{ + HTTPLogTrace(); + + return 302; +} + +- (void)dealloc +{ + HTTPLogTrace(); + +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.h b/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.h new file mode 100755 index 00000000..e6c61cc8 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.h @@ -0,0 +1,105 @@ +#import + +@class HTTPMessage; +@class GCDAsyncSocket; + + +#define WebSocketDidDieNotification @"WebSocketDidDie" + +@interface WebSocket : NSObject +{ + dispatch_queue_t websocketQueue; + + HTTPMessage *request; + GCDAsyncSocket *asyncSocket; + + NSData *term; + + BOOL isStarted; + BOOL isOpen; + BOOL isVersion76; + + id __unsafe_unretained delegate; +} + ++ (BOOL)isWebSocketRequest:(HTTPMessage *)request; + +- (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket; + +/** + * Delegate option. + * + * In most cases it will be easier to subclass WebSocket, + * but some circumstances may lead one to prefer standard delegate callbacks instead. +**/ +@property (/* atomic */ unsafe_unretained) id delegate; + +/** + * The WebSocket class is thread-safe, generally via it's GCD queue. + * All public API methods are thread-safe, + * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue. +**/ +@property (nonatomic, readonly) dispatch_queue_t websocketQueue; + +/** + * Public API + * + * These methods are automatically called by the HTTPServer. + * You may invoke the stop method yourself to close the WebSocket manually. +**/ +- (void)start; +- (void)stop; + +/** + * Public API + * + * Sends a message over the WebSocket. + * This method is thread-safe. + **/ +- (void)sendMessage:(NSString *)msg; + +/** + * Public API + * + * Sends a message over the WebSocket. + * This method is thread-safe. + **/ +- (void)sendData:(NSData *)msg; + +/** + * Subclass API + * + * These methods are designed to be overriden by subclasses. +**/ +- (void)didOpen; +- (void)didReceiveMessage:(NSString *)msg; +- (void)didClose; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * There are two ways to create your own custom WebSocket: + * + * - Subclass it and override the methods you're interested in. + * - Use traditional delegate paradigm along with your own custom class. + * + * They both exist to allow for maximum flexibility. + * In most cases it will be easier to subclass WebSocket. + * However some circumstances may lead one to prefer standard delegate callbacks instead. + * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option. +**/ + +@protocol WebSocketDelegate +@optional + +- (void)webSocketDidOpen:(WebSocket *)ws; + +- (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg; + +- (void)webSocketDidClose:(WebSocket *)ws; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.m b/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.m new file mode 100755 index 00000000..337bec16 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.m @@ -0,0 +1,792 @@ +#import "WebSocket.h" +#import "HTTPMessage.h" +#import "GCDAsyncSocket.h" +#import "DDNumber.h" +#import "DDData.h" +#import "HTTPLogging.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// Log levels: off, error, warn, info, verbose +// Other flags : trace +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; + +#define TIMEOUT_NONE -1 +#define TIMEOUT_REQUEST_BODY 10 + +#define TAG_HTTP_REQUEST_BODY 100 +#define TAG_HTTP_RESPONSE_HEADERS 200 +#define TAG_HTTP_RESPONSE_BODY 201 + +#define TAG_PREFIX 300 +#define TAG_MSG_PLUS_SUFFIX 301 +#define TAG_MSG_WITH_LENGTH 302 +#define TAG_MSG_MASKING_KEY 303 +#define TAG_PAYLOAD_PREFIX 304 +#define TAG_PAYLOAD_LENGTH 305 +#define TAG_PAYLOAD_LENGTH16 306 +#define TAG_PAYLOAD_LENGTH64 307 + +#define WS_OP_CONTINUATION_FRAME 0 +#define WS_OP_TEXT_FRAME 1 +#define WS_OP_BINARY_FRAME 2 +#define WS_OP_CONNECTION_CLOSE 8 +#define WS_OP_PING 9 +#define WS_OP_PONG 10 + +static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame) +{ + return (frame & 0x80) ? YES : NO; +} + +static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame) +{ + return (frame & 0x80) ? YES : NO; +} + +static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame) +{ + return frame & 0x7F; +} + +@interface WebSocket (PrivateAPI) + +- (void)readRequestBody; +- (void)sendResponseBody; +- (void)sendResponseHeaders; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation WebSocket +{ + BOOL isRFC6455; + BOOL nextFrameMasked; + NSUInteger nextOpCode; + NSData *maskingKey; +} + ++ (BOOL)isWebSocketRequest:(HTTPMessage *)request +{ + // Request (Draft 75): + // + // GET /demo HTTP/1.1 + // Upgrade: WebSocket + // Connection: Upgrade + // Host: example.com + // Origin: http://example.com + // WebSocket-Protocol: sample + // + // + // Request (Draft 76): + // + // GET /demo HTTP/1.1 + // Upgrade: WebSocket + // Connection: Upgrade + // Host: example.com + // Origin: http://example.com + // Sec-WebSocket-Protocol: sample + // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 + // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 + // + // ^n:ds[4U + + // Look for Upgrade: and Connection: headers. + // If we find them, and they have the proper value, + // we can safely assume this is a websocket request. + + NSString *upgradeHeaderValue = [request headerField:@"Upgrade"]; + NSString *connectionHeaderValue = [request headerField:@"Connection"]; + + BOOL isWebSocket = YES; + + if (!upgradeHeaderValue || !connectionHeaderValue) { + isWebSocket = NO; + } + else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) { + isWebSocket = NO; + } + else if ([connectionHeaderValue rangeOfString:@"Upgrade" options:NSCaseInsensitiveSearch].location == NSNotFound) { + isWebSocket = NO; + } + + HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO")); + + return isWebSocket; +} + ++ (BOOL)isVersion76Request:(HTTPMessage *)request +{ + NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; + NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; + + BOOL isVersion76; + + if (!key1 || !key2) { + isVersion76 = NO; + } + else { + isVersion76 = YES; + } + + HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO")); + + return isVersion76; +} + ++ (BOOL)isRFC6455Request:(HTTPMessage *)request +{ + NSString *key = [request headerField:@"Sec-WebSocket-Key"]; + BOOL isRFC6455 = (key != nil); + + HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO")); + + return isRFC6455; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Setup and Teardown +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize websocketQueue; + +- (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket +{ + HTTPLogTrace(); + + if (aRequest == nil) + { + return nil; + } + + if ((self = [super init])) + { + if (HTTP_LOG_VERBOSE) + { + NSData *requestHeaders = [aRequest messageData]; + + NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding]; + HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp); + } + + websocketQueue = dispatch_queue_create("WebSocket", NULL); + request = aRequest; + + asyncSocket = socket; + [asyncSocket setDelegate:self delegateQueue:websocketQueue]; + + isOpen = NO; + isVersion76 = [[self class] isVersion76Request:request]; + isRFC6455 = [[self class] isRFC6455Request:request]; + + term = [[NSData alloc] initWithBytes:"\xFF" length:1]; + } + return self; +} + +- (void)dealloc +{ + HTTPLogTrace(); + + #if !OS_OBJECT_USE_OBJC + dispatch_release(websocketQueue); + #endif + + [asyncSocket setDelegate:nil delegateQueue:NULL]; + [asyncSocket disconnect]; +} + +- (id)delegate +{ + __block id result = nil; + + dispatch_sync(websocketQueue, ^{ + result = delegate; + }); + + return result; +} + +- (void)setDelegate:(id)newDelegate +{ + dispatch_async(websocketQueue, ^{ + delegate = newDelegate; + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Start and Stop +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Starting point for the WebSocket after it has been fully initialized (including subclasses). + * This method is called by the HTTPConnection it is spawned from. +**/ +- (void)start +{ + // This method is not exactly designed to be overriden. + // Subclasses are encouraged to override the didOpen method instead. + + dispatch_async(websocketQueue, ^{ @autoreleasepool { + + if (isStarted) return; + isStarted = YES; + + if (isVersion76) + { + [self readRequestBody]; + } + else + { + [self sendResponseHeaders]; + [self didOpen]; + } + }}); +} + +/** + * This method is called by the HTTPServer if it is asked to stop. + * The server, in turn, invokes stop on each WebSocket instance. +**/ +- (void)stop +{ + // This method is not exactly designed to be overriden. + // Subclasses are encouraged to override the didClose method instead. + + dispatch_async(websocketQueue, ^{ @autoreleasepool { + + [asyncSocket disconnect]; + }}); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark HTTP Response +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)readRequestBody +{ + HTTPLogTrace(); + + NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body"); + + [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY]; +} + +- (NSString *)originResponseHeaderValue +{ + HTTPLogTrace(); + + NSString *origin = [request headerField:@"Origin"]; + + if (origin == nil) + { + NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; + //NSString * port = @"12306"; + + return [NSString stringWithFormat:@"http://localhost:%@", port]; + } + else + { + return origin; + } +} + +- (NSString *)locationResponseHeaderValue +{ + HTTPLogTrace(); + + NSString *location; + + NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws"; + NSString *host = [request headerField:@"Host"]; + + NSString *requestUri = [[request url] relativeString]; + + if (host == nil) + { + NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; + //NSString * port = @"12306"; + location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri]; + } + else + { + location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri]; + } + + return location; +} + +- (NSString *)secWebSocketKeyResponseHeaderValue { + NSString *key = [request headerField: @"Sec-WebSocket-Key"]; + NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded; +} + +- (void)sendResponseHeaders +{ + HTTPLogTrace(); + + // Request (Draft 75): + // + // GET /demo HTTP/1.1 + // Upgrade: WebSocket + // Connection: Upgrade + // Host: example.com + // Origin: http://example.com + // WebSocket-Protocol: sample + // + // + // Request (Draft 76): + // + // GET /demo HTTP/1.1 + // Upgrade: WebSocket + // Connection: Upgrade + // Host: example.com + // Origin: http://example.com + // Sec-WebSocket-Protocol: sample + // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 + // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 + // + // ^n:ds[4U + + + // Response (Draft 75): + // + // HTTP/1.1 101 Web Socket Protocol Handshake + // Upgrade: WebSocket + // Connection: Upgrade + // WebSocket-Origin: http://example.com + // WebSocket-Location: ws://example.com/demo + // WebSocket-Protocol: sample + // + // + // Response (Draft 76): + // + // HTTP/1.1 101 WebSocket Protocol Handshake + // Upgrade: WebSocket + // Connection: Upgrade + // Sec-WebSocket-Origin: http://example.com + // Sec-WebSocket-Location: ws://example.com/demo + // Sec-WebSocket-Protocol: sample + // + // 8jKS'y:G*Co,Wxa- + + + HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101 + description:@"Web Socket Protocol Handshake" + version:HTTPVersion1_1]; + + [wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"]; + [wsResponse setHeaderField:@"Connection" value:@"Upgrade"]; + + // Note: It appears that WebSocket-Origin and WebSocket-Location + // are required for Google's Chrome implementation to work properly. + // + // If we don't send either header, Chrome will never report the WebSocket as open. + // If we only send one of the two, Chrome will immediately close the WebSocket. + // + // In addition to this it appears that Chrome's implementation is very picky of the values of the headers. + // They have to match exactly with what Chrome sent us or it will close the WebSocket. + + NSString *originValue = [self originResponseHeaderValue]; + NSString *locationValue = [self locationResponseHeaderValue]; + + NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin"; + NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location"; + + [wsResponse setHeaderField:originField value:originValue]; + [wsResponse setHeaderField:locationField value:locationValue]; + + NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue]; + if (acceptValue) { + [wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue]; + } + + NSData *responseHeaders = [wsResponse messageData]; + + + if (HTTP_LOG_VERBOSE) + { + NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding]; + HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp); + } + + [asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS]; +} + +- (NSData *)processKey:(NSString *)key +{ + HTTPLogTrace(); + + unichar c; + NSUInteger i; + NSUInteger length = [key length]; + + // Concatenate the digits into a string, + // and count the number of spaces. + + NSMutableString *numStr = [NSMutableString stringWithCapacity:10]; + long long numSpaces = 0; + + for (i = 0; i < length; i++) + { + c = [key characterAtIndex:i]; + + if (c >= '0' && c <= '9') + { + [numStr appendFormat:@"%C", c]; + } + else if (c == ' ') + { + numSpaces++; + } + } + + long long num = strtoll([numStr UTF8String], NULL, 10); + + long long resultHostNum; + + if (numSpaces == 0) + resultHostNum = 0; + else + resultHostNum = num / numSpaces; + + HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum); + + // Convert result to 4 byte big-endian (network byte order) + // and then convert to raw data. + + UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum); + + return [NSData dataWithBytes:&result length:4]; +} + +- (void)sendResponseBody:(NSData *)d3 +{ + HTTPLogTrace(); + + NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body"); + NSAssert([d3 length] == 8, @"Invalid requestBody length"); + + NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; + NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; + + NSData *d1 = [self processKey:key1]; + NSData *d2 = [self processKey:key2]; + + // Concatenated d1, d2 & d3 + + NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)]; + [d0 appendData:d1]; + [d0 appendData:d2]; + [d0 appendData:d3]; + + // Hash the data using MD5 + + NSData *responseBody = [d0 md5Digest]; + + [asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY]; + + if (HTTP_LOG_VERBOSE) + { + NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding]; + NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding]; + NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding]; + + NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding]; + + NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding]; + + HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1); + HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2); + HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3); + HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0); + HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH); + + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Core Functionality +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)didOpen +{ + HTTPLogTrace(); + + // Override me to perform any custom actions once the WebSocket has been opened. + // This method is invoked on the websocketQueue. + // + // Don't forget to invoke [super didOpen] in your method. + + // Start reading for messages + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)]; + + // Notify delegate + if ([delegate respondsToSelector:@selector(webSocketDidOpen:)]) + { + [delegate webSocketDidOpen:self]; + } +} + +- (void)sendMessage:(NSString *)msg +{ + NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; + [self sendData:msgData]; +} + +- (void)sendData:(NSData *)msgData +{ + HTTPLogTrace(); + + NSMutableData *data = nil; + + if (isRFC6455) + { + NSUInteger length = msgData.length; + if (length <= 125) + { + data = [NSMutableData dataWithCapacity:(length + 2)]; + [data appendBytes: "\x81" length:1]; + UInt8 len = (UInt8)length; + [data appendBytes: &len length:1]; + [data appendData:msgData]; + } + else if (length <= 0xFFFF) + { + data = [NSMutableData dataWithCapacity:(length + 4)]; + [data appendBytes: "\x81\x7E" length:2]; + UInt16 len = (UInt16)length; + [data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2]; + [data appendData:msgData]; + } + else + { + data = [NSMutableData dataWithCapacity:(length + 10)]; + [data appendBytes: "\x81\x7F" length:2]; + [data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8]; + [data appendData:msgData]; + } + } + else + { + data = [NSMutableData dataWithCapacity:([msgData length] + 2)]; + + [data appendBytes:"\x00" length:1]; + [data appendData:msgData]; + [data appendBytes:"\xFF" length:1]; + } + + // Remember: GCDAsyncSocket is thread-safe + + [asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0]; +} + +- (void)didReceiveMessage:(NSString *)msg +{ + HTTPLogTrace(); + + // Override me to process incoming messages. + // This method is invoked on the websocketQueue. + // + // For completeness, you should invoke [super didReceiveMessage:msg] in your method. + + // Notify delegate + if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) + { + [delegate webSocket:self didReceiveMessage:msg]; + } +} + +- (void)didClose +{ + HTTPLogTrace(); + + // Override me to perform any cleanup when the socket is closed + // This method is invoked on the websocketQueue. + // + // Don't forget to invoke [super didClose] at the end of your method. + + // Notify delegate + if ([delegate respondsToSelector:@selector(webSocketDidClose:)]) + { + [delegate webSocketDidClose:self]; + } + + // Notify HTTPServer + [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self]; +} + +#pragma mark WebSocket Frame + +- (BOOL)isValidWebSocketFrame:(UInt8)frame +{ + NSUInteger rsv = frame & 0x70; + NSUInteger opcode = frame & 0x0F; + if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF)) + { + return NO; + } + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark AsyncSocket Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-------+-+-------------+-------------------------------+ +// |F|R|R|R| opcode|M| Payload len | Extended payload length | +// |I|S|S|S| (4) |A| (7) | (16/64) | +// |N|V|V|V| |S| | (if payload len==126/127) | +// | |1|2|3| |K| | | +// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + +// | Extended payload length continued, if payload len == 127 | +// + - - - - - - - - - - - - - - - +-------------------------------+ +// | |Masking-key, if MASK set to 1 | +// +-------------------------------+-------------------------------+ +// | Masking-key (continued) | Payload Data | +// +-------------------------------- - - - - - - - - - - - - - - - + +// : Payload Data continued ... : +// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// | Payload Data continued ... | +// +---------------------------------------------------------------+ + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + HTTPLogTrace(); + + if (tag == TAG_HTTP_REQUEST_BODY) + { + [self sendResponseHeaders]; + [self sendResponseBody:data]; + [self didOpen]; + } + else if (tag == TAG_PREFIX) + { + UInt8 *pFrame = (UInt8 *)[data bytes]; + UInt8 frame = *pFrame; + + if (frame <= 0x7F) + { + [asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX]; + } + else + { + // Unsupported frame type + [self didClose]; + } + } + else if (tag == TAG_PAYLOAD_PREFIX) + { + UInt8 *pFrame = (UInt8 *)[data bytes]; + UInt8 frame = *pFrame; + + if ([self isValidWebSocketFrame: frame]) + { + nextOpCode = (frame & 0x0F); + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH]; + } + else + { + // Unsupported frame type + [self didClose]; + } + } + else if (tag == TAG_PAYLOAD_LENGTH) + { + UInt8 frame = *(UInt8 *)[data bytes]; + BOOL masked = WS_PAYLOAD_IS_MASKED(frame); + NSUInteger length = WS_PAYLOAD_LENGTH(frame); + nextFrameMasked = masked; + maskingKey = nil; + if (length <= 125) + { + if (nextFrameMasked) + { + [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; + } + [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; + } + else if (length == 126) + { + [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16]; + } + else + { + [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64]; + } + } + else if (tag == TAG_PAYLOAD_LENGTH16) + { + UInt8 *pFrame = (UInt8 *)[data bytes]; + NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1]; + if (nextFrameMasked) { + [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; + } + [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; + } + else if (tag == TAG_PAYLOAD_LENGTH64) + { + // FIXME: 64bit data size in memory? + [self didClose]; + } + else if (tag == TAG_MSG_WITH_LENGTH) + { + NSUInteger msgLength = [data length]; + if (nextFrameMasked && maskingKey) { + NSMutableData *masked = data.mutableCopy; + UInt8 *pData = (UInt8 *)masked.mutableBytes; + UInt8 *pMask = (UInt8 *)maskingKey.bytes; + for (NSUInteger i = 0; i < msgLength; i++) + { + pData[i] = pData[i] ^ pMask[i % 4]; + } + data = masked; + } + if (nextOpCode == WS_OP_TEXT_FRAME) + { + NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; + [self didReceiveMessage:msg]; + } + else + { + [self didClose]; + return; + } + + // Read next frame + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX]; + } + else if (tag == TAG_MSG_MASKING_KEY) + { + maskingKey = data.copy; + } + else + { + NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame + + NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; + + [self didReceiveMessage:msg]; + + + // Read next message + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX]; + } +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error +{ + HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error); + + [self didClose]; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.h b/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.h new file mode 100755 index 00000000..273bc191 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.h @@ -0,0 +1,58 @@ + +#import "HTTPConnection.h" + +@class MultipartFormDataParser; +@class MyHTTPConnection; + +@protocol MyHTTPConnectionDelegate +@required + +/** + 设置目标地址 + + @param server 服务器 ftp self + @return 目标地址str + */ +- (NSString *)onSetDestinationPathHttpFileTranSportDestination:(MyHTTPConnection *)server; +@optional + +/** + 文件传输并存储成功 + + @param server 服务器 ftp self + @param filePath 文件路径(含文件名) + */ +- (void)onHttpFileTranSportServer:(MyHTTPConnection *)server successWithPath:(NSString *)filePath; + +/** + 文件判断 + + @param server 服务器 ftp self + @param filePath 文件路径(含文件名) + @return 如果是重复文件,就返回NO + */ +- (BOOL)onHttpFileDataEstimateDuplicateCanPassTranSportServer:(MyHTTPConnection *)server withPath:(NSString *)filePath andFileName:(NSString *)fileName; + +@end + +@interface MyHTTPConnection : HTTPConnection { + MultipartFormDataParser* parser; + NSFileHandle* storeFile; + NSMutableArray* uploadedFiles; +} + +/** + 文件路径(含文件名) + */ +@property (nonatomic, strong) NSString *filePath; + +/** + 文件夹路径(不含文件名) + */ +@property (strong, nonatomic) NSString *destinationPath; + +/** + delegate:MyHTTPConnectionDelegate + */ +@property (nonatomic, weak) id delegate; +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.m b/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.m new file mode 100755 index 00000000..80f2af39 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.m @@ -0,0 +1,208 @@ + +#import "MyHTTPConnection.h" +#import "HTTPMessage.h" +#import "HTTPDataResponse.h" +#import "DDNumber.h" +#import "HTTPLogging.h" + +#import "MultipartFormDataParser.h" +#import "MultipartMessageHeaderField.h" +#import "HTTPDynamicFileResponse.h" +#import "HTTPFileResponse.h" + + +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; // | HTTP_LOG_FLAG_TRACE; + + +/** + * All we have to do is override appropriate methods in HTTPConnection. + **/ + +@implementation MyHTTPConnection + +- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { + + HTTPLogTrace(); + + // Add support for POST + + if ([method isEqualToString:@"POST"]) + { + if ([path isEqualToString:@"/upload.html"]) + { + return YES; + } + } + + return [super supportsMethod:method atPath:path]; +} + +- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path { + + HTTPLogTrace(); + + // Inform HTTP server that we expect a body to accompany a POST request + + if([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { + // here we need to make sure, boundary is set in header + NSString* contentType = [request headerField:@"Content-Type"]; + NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location; + if( NSNotFound == paramsSeparator ) { + return NO; + } + if( paramsSeparator >= contentType.length - 1 ) { + return NO; + } + NSString* type = [contentType substringToIndex:paramsSeparator]; + if( ![type isEqualToString:@"multipart/form-data"] ) { + // we expect multipart/form-data content type + return NO; + } + + // enumerate all params in content-type, and find boundary there + NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"]; + for( NSString* param in params ) { + paramsSeparator = [param rangeOfString:@"="].location; + if( (NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1 ) { + continue; + } + NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)]; + NSString* paramValue = [param substringFromIndex:paramsSeparator+1]; + + if( [paramName isEqualToString: @"boundary"] ) { + // let's separate the boundary from content-type, to make it more handy to handle + [request setHeaderField:@"boundary" value:paramValue]; + } + } + // check if boundary specified + if( nil == [request headerField:@"boundary"] ) { + return NO; + } + return YES; + } + return [super expectsRequestBodyFromMethod:method atPath:path]; +} + +- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { + + HTTPLogTrace(); + + if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { + + // this method will generate response with links to uploaded file + NSMutableString* filesStr = [[NSMutableString alloc] init]; + + for( NSString* filePath in uploadedFiles ) { + //generate links + [filesStr appendFormat:@" %@
    ",filePath, [filePath lastPathComponent]]; + } + NSString* templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"]; + NSDictionary* replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"]; + // use dynamic file response to apply our links to response template + return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict]; + } + if( [method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"] ) { + // let download the uploaded files + return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self]; + } + + return [super httpResponseForMethod:method URI:path]; +} + +- (void)prepareForBodyWithSize:(UInt64)contentLength { + HTTPLogTrace(); + + // set up mime parser + NSString* boundary = [request headerField:@"boundary"]; + parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding]; + parser.delegate = self; + + uploadedFiles = [[NSMutableArray alloc] init]; +} + +- (void)processBodyData:(NSData *)postDataChunk { + HTTPLogTrace(); + // append data to the parser. It will invoke callbacks to let us handle + // parsed data. + [parser appendData:postDataChunk]; +} + + +//----------------------------------------------------------------- +#pragma mark multipart form data parser delegate + + +- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header { + // in this sample, we are not interested in parts, other then file parts. + // check content disposition to find out filename + + MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"]; + NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent]; + + if ( (nil == filename) || [filename isEqualToString: @""] ) { + // it's either not a file part, or + // an empty form sent. we won't handle it. + return; + } + + // 在这里修改文件存储的位置 + + NSAssert([self.delegate respondsToSelector:@selector(onSetDestinationPathHttpFileTranSportDestination:)], @"Need to add delegate name onSetDestinationPathHttpFileTranSportDestination to add a destinationPath"); + + if ([self.delegate respondsToSelector:@selector(onSetDestinationPathHttpFileTranSportDestination:)]) { + self.destinationPath = [self.delegate onSetDestinationPathHttpFileTranSportDestination:self]; + NSAssert(self.destinationPath.length > 0 || self.destinationPath, @"destinationPath can not be nil"); + } + + //拼接文件名或者预计文件路径 + NSString* filePath = [self.destinationPath stringByAppendingPathComponent: filename]; + self.filePath = filePath; + //需要判断是否存在重复文件 + if ([self.delegate respondsToSelector:@selector(onHttpFileDataEstimateDuplicateCanPassTranSportServer:withPath:andFileName:)]) { + if (![self.delegate onHttpFileDataEstimateDuplicateCanPassTranSportServer:self withPath:self.filePath andFileName:filename]) { + return; + }; + } + + HTTPLogVerbose(@"Saving file to %@", filePath); + if(![[NSFileManager defaultManager] createDirectoryAtPath:self.destinationPath withIntermediateDirectories:true attributes:nil error:nil]) { + HTTPLogError(@"Could not create directory at path: %@", filePath); + } + if(![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) { + HTTPLogError(@"Could not create file at path: %@", filePath); + } + storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath]; + [uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]]; +} + + +- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header { + // here we just write the output from parser to the file. + if( storeFile ) { + [storeFile writeData:data]; + } +} + +- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header { + // as the file part is over, we close the file. + [storeFile closeFile]; + storeFile = nil; +} + +- (void) processPreambleData:(NSData*) data { + // if we are interested in preamble data, we could process it here. + +} + +- (void) processEpilogueData:(NSData*) data { + // if we are interested in epilogue data, we could process it here. + //用户更新歌曲数量 + [[NSNotificationCenter defaultCenter] postNotificationName:@"processEpilogueData" object:nil]; + if ([self.delegate respondsToSelector:@selector(onHttpFileTranSportServer:successWithPath:)]) { + [self.delegate onHttpFileTranSportServer:self successWithPath:self.filePath]; + } +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.h b/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.h new file mode 100755 index 00000000..d4874d63 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.h @@ -0,0 +1,19 @@ +// +// SJXCSMIPHelper.h +// LocalReader +// +// Created by shapp on 2017/7/24. +// Copyright © 2017年 sjx. All rights reserved. +// + +#import + +@interface SJXCSMIPHelper : NSObject + +/** 获取ip地址 */ ++ (NSString *)deviceIPAdress; + +#pragma mark - 获取设备当前网络IP地址 ++ (NSString *)getIPAddress:(BOOL)preferIPv4; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.m b/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.m new file mode 100755 index 00000000..2cf8fb0a --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.m @@ -0,0 +1,155 @@ +// +// SJXCSMIPHelper.m +// LocalReader +// +// Created by shapp on 2017/7/24. +// Copyright © 2017年 sjx. All rights reserved. +// + +#import "SJXCSMIPHelper.h" +#import +#import +#import + +@implementation SJXCSMIPHelper + ++ (NSString *)deviceIPAdress { + + NSString *address = @"an error occurred when obtaining ip address"; + + struct ifaddrs *interfaces = NULL; + + struct ifaddrs *temp_addr = NULL; + + int success = 0; + + success = getifaddrs(&interfaces); + + if (success == 0) { // 0 表示获取成功 + + temp_addr = interfaces; + + while (temp_addr != NULL) { + + if( temp_addr->ifa_addr->sa_family == AF_INET) { + + // Check if interface is en0 which is the wifi connection on the iPhone + + if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { + + // Get NSString from C String + + address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; + + } + + } + + temp_addr = temp_addr->ifa_next; + + } + + } + + freeifaddrs(interfaces); + + return address; + +} + + +#define IOS_CELLULAR @"pdp_ip0" +#define IOS_WIFI @"en0" +#define IOS_VPN @"utun0" +#define IP_ADDR_IPv4 @"ipv4" +#define IP_ADDR_IPv6 @"ipv6" + +#pragma mark - 获取设备当前网络IP地址 ++ (NSString *)getIPAddress:(BOOL)preferIPv4 +{ + NSArray *searchArray = preferIPv4 ? + @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] : + @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ; + + NSDictionary *addresses = [self getIPAddresses]; + NSLog(@"addresses: %@", addresses); + + __block NSString *address; + [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) + { + address = addresses[key]; + //筛选出IP地址格式 + if([self isValidatIP:address]) *stop = YES; + } ]; + return address ? address : @"0.0.0.0"; +} + ++ (BOOL)isValidatIP:(NSString *)ipAddress { + if (ipAddress.length == 0) { + return NO; + } + NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; + + NSError *error; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error]; + + if (regex != nil) { + NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])]; + + if (firstMatch) { + NSRange resultRange = [firstMatch rangeAtIndex:0]; + NSString *result=[ipAddress substringWithRange:resultRange]; + //输出结果 + NSLog(@"%@",result); + return YES; + } + } + return NO; +} + ++ (NSDictionary *)getIPAddresses +{ + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // retrieve the current interfaces - returns 0 on success + struct ifaddrs *interfaces; + if(!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *interface; + for(interface=interfaces; interface; interface=interface->ifa_next) { + if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { + continue; // deeply nested code harder to read + } + const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; + char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; + if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; + NSString *type; + if(addr->sin_family == AF_INET) { + if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { + type = IP_ADDR_IPv4; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; + if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { + type = IP_ADDR_IPv6; + } + } + if(type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addrBuf]; + } + } + } + // Free memory + freeifaddrs(interfaces); + } + return [addresses count] ? addresses : nil; +} + + + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.h new file mode 100755 index 00000000..e9f62027 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.h @@ -0,0 +1,41 @@ +#import +#import + +#import "DDLog.h" + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides a logger for the Apple System Log facility. + * + * As described in the "Getting Started" page, + * the traditional NSLog() function directs it's output to two places: + * + * - Apple System Log + * - StdErr (if stderr is a TTY) so log statements show up in Xcode console + * + * To duplicate NSLog() functionality you can simply add this logger and a tty logger. + * However, if you instead choose to use file logging (for faster performance), + * you may choose to use a file logger and a tty logger. +**/ + +@interface DDASLLogger : DDAbstractLogger +{ + aslclient client; +} + ++ (DDASLLogger *)sharedInstance; + +// Inherited from DDAbstractLogger + +// - (id )logFormatter; +// - (void)setLogFormatter:(id )formatter; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.m new file mode 100755 index 00000000..86fbdaf8 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.m @@ -0,0 +1,99 @@ +#import "DDASLLogger.h" + +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + + +@implementation DDASLLogger + +static DDASLLogger *sharedInstance; + +/** + * The runtime sends initialize to each class in a program exactly one time just before the class, + * or any class that inherits from it, is sent its first message from within the program. (Thus the + * method may never be invoked if the class is not used.) The runtime sends the initialize message to + * classes in a thread-safe manner. Superclasses receive this message before their subclasses. + * + * This method may also be called directly (assumably by accident), hence the safety mechanism. +**/ ++ (void)initialize +{ + static BOOL initialized = NO; + if (!initialized) + { + initialized = YES; + + sharedInstance = [[DDASLLogger alloc] init]; + } +} + ++ (DDASLLogger *)sharedInstance +{ + return sharedInstance; +} + +- (id)init +{ + if (sharedInstance != nil) + { + return nil; + } + + if ((self = [super init])) + { + // A default asl client is provided for the main thread, + // but background threads need to create their own client. + + client = asl_open(NULL, "com.apple.console", 0); + } + return self; +} + +- (void)logMessage:(DDLogMessage *)logMessage +{ + NSString *logMsg = logMessage->logMsg; + + if (formatter) + { + logMsg = [formatter formatLogMessage:logMessage]; + } + + if (logMsg) + { + const char *msg = [logMsg UTF8String]; + + int aslLogLevel; + switch (logMessage->logFlag) + { + // Note: By default ASL will filter anything above level 5 (Notice). + // So our mappings shouldn't go above that level. + + case LOG_FLAG_ERROR : aslLogLevel = ASL_LEVEL_CRIT; break; + case LOG_FLAG_WARN : aslLogLevel = ASL_LEVEL_ERR; break; + case LOG_FLAG_INFO : aslLogLevel = ASL_LEVEL_WARNING; break; + default : aslLogLevel = ASL_LEVEL_NOTICE; break; + } + + asl_log(client, NULL, aslLogLevel, "%s", msg); + } +} + +- (NSString *)loggerName +{ + return @"cocoa.lumberjack.aslLogger"; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h new file mode 100755 index 00000000..73769bb5 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h @@ -0,0 +1,102 @@ +#import + +#import "DDLog.h" + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides an abstract implementation of a database logger. + * + * That is, it provides the base implementation for a database logger to build atop of. + * All that is needed for a concrete database logger is to extend this class + * and override the methods in the implementation file that are prefixed with "db_". +**/ + +@interface DDAbstractDatabaseLogger : DDAbstractLogger { +@protected + NSUInteger saveThreshold; + NSTimeInterval saveInterval; + NSTimeInterval maxAge; + NSTimeInterval deleteInterval; + BOOL deleteOnEverySave; + + BOOL saveTimerSuspended; + NSUInteger unsavedCount; + dispatch_time_t unsavedTime; + dispatch_source_t saveTimer; + dispatch_time_t lastDeleteTime; + dispatch_source_t deleteTimer; +} + +/** + * Specifies how often to save the data to disk. + * Since saving is an expensive operation (disk io) it is not done after every log statement. + * These properties allow you to configure how/when the logger saves to disk. + * + * A save is done when either (whichever happens first): + * + * - The number of unsaved log entries reaches saveThreshold + * - The amount of time since the oldest unsaved log entry was created reaches saveInterval + * + * You can optionally disable the saveThreshold by setting it to zero. + * If you disable the saveThreshold you are entirely dependent on the saveInterval. + * + * You can optionally disable the saveInterval by setting it to zero (or a negative value). + * If you disable the saveInterval you are entirely dependent on the saveThreshold. + * + * It's not wise to disable both saveThreshold and saveInterval. + * + * The default saveThreshold is 500. + * The default saveInterval is 60 seconds. +**/ +@property (assign, readwrite) NSUInteger saveThreshold; +@property (assign, readwrite) NSTimeInterval saveInterval; + +/** + * It is likely you don't want the log entries to persist forever. + * Doing so would allow the database to grow infinitely large over time. + * + * The maxAge property provides a way to specify how old a log statement can get + * before it should get deleted from the database. + * + * The deleteInterval specifies how often to sweep for old log entries. + * Since deleting is an expensive operation (disk io) is is done on a fixed interval. + * + * An alternative to the deleteInterval is the deleteOnEverySave option. + * This specifies that old log entries should be deleted during every save operation. + * + * You can optionally disable the maxAge by setting it to zero (or a negative value). + * If you disable the maxAge then old log statements are not deleted. + * + * You can optionally disable the deleteInterval by setting it to zero (or a negative value). + * + * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. + * + * It's not wise to enable both deleteInterval and deleteOnEverySave. + * + * The default maxAge is 7 days. + * The default deleteInterval is 5 minutes. + * The default deleteOnEverySave is NO. +**/ +@property (assign, readwrite) NSTimeInterval maxAge; +@property (assign, readwrite) NSTimeInterval deleteInterval; +@property (assign, readwrite) BOOL deleteOnEverySave; + +/** + * Forces a save of any pending log entries (flushes log entries to disk). +**/ +- (void)savePendingLogEntries; + +/** + * Removes any log entries that are older than maxAge. +**/ +- (void)deleteOldLogEntries; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m new file mode 100755 index 00000000..5cb5ddd6 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m @@ -0,0 +1,727 @@ +#import "DDAbstractDatabaseLogger.h" +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface DDAbstractDatabaseLogger () +- (void)destroySaveTimer; +- (void)destroyDeleteTimer; +@end + +#pragma mark - + +@implementation DDAbstractDatabaseLogger + +- (id)init +{ + if ((self = [super init])) + { + saveThreshold = 500; + saveInterval = 60; // 60 seconds + maxAge = (60 * 60 * 24 * 7); // 7 days + deleteInterval = (60 * 5); // 5 minutes + } + return self; +} + +- (void)dealloc +{ + [self destroySaveTimer]; + [self destroyDeleteTimer]; + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Override Me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)db_log:(DDLogMessage *)logMessage +{ + // Override me and add your implementation. + // + // Return YES if an item was added to the buffer. + // Return NO if the logMessage was ignored. + + return NO; +} + +- (void)db_save +{ + // Override me and add your implementation. +} + +- (void)db_delete +{ + // Override me and add your implementation. +} + +- (void)db_saveAndDelete +{ + // Override me and add your implementation. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)performSaveAndSuspendSaveTimer +{ + if (unsavedCount > 0) + { + if (deleteOnEverySave) + [self db_saveAndDelete]; + else + [self db_save]; + } + + unsavedCount = 0; + unsavedTime = 0; + + if (saveTimer && !saveTimerSuspended) + { + dispatch_suspend(saveTimer); + saveTimerSuspended = YES; + } +} + +- (void)performDelete +{ + if (maxAge > 0.0) + { + [self db_delete]; + + lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Timers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)destroySaveTimer +{ + if (saveTimer) + { + dispatch_source_cancel(saveTimer); + if (saveTimerSuspended) + { + // Must resume a timer before releasing it (or it will crash) + dispatch_resume(saveTimer); + saveTimerSuspended = NO; + } + #if !OS_OBJECT_USE_OBJC + dispatch_release(saveTimer); + #endif + saveTimer = NULL; + } +} + +- (void)updateAndResumeSaveTimer +{ + if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0)) + { + uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC); + dispatch_time_t startTime = dispatch_time(unsavedTime, interval); + + dispatch_source_set_timer(saveTimer, startTime, interval, 1.0); + + if (saveTimerSuspended) + { + dispatch_resume(saveTimer); + saveTimerSuspended = NO; + } + } +} + +- (void)createSuspendedSaveTimer +{ + if ((saveTimer == NULL) && (saveInterval > 0.0)) + { + saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); + + dispatch_source_set_event_handler(saveTimer, ^{ @autoreleasepool { + + [self performSaveAndSuspendSaveTimer]; + + }}); + + saveTimerSuspended = YES; + } +} + +- (void)destroyDeleteTimer +{ + if (deleteTimer) + { + dispatch_source_cancel(deleteTimer); + #if !OS_OBJECT_USE_OBJC + dispatch_release(deleteTimer); + #endif + deleteTimer = NULL; + } +} + +- (void)updateDeleteTimer +{ + if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) + { + uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC); + dispatch_time_t startTime; + + if (lastDeleteTime > 0) + startTime = dispatch_time(lastDeleteTime, interval); + else + startTime = dispatch_time(DISPATCH_TIME_NOW, interval); + + dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0); + } +} + +- (void)createAndStartDeleteTimer +{ + if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) + { + deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); + + if (deleteTimer != NULL) { + dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool { + + [self performDelete]; + + }}); + + [self updateDeleteTimer]; + + dispatch_resume(deleteTimer); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSUInteger)saveThreshold +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSUInteger result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = saveThreshold; + }); + }); + + return result; +} + +- (void)setSaveThreshold:(NSUInteger)threshold +{ + dispatch_block_t block = ^{ @autoreleasepool { + + if (saveThreshold != threshold) + { + saveThreshold = threshold; + + // Since the saveThreshold has changed, + // we check to see if the current unsavedCount has surpassed the new threshold. + // + // If it has, we immediately save the log. + + if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) + { + [self performSaveAndSuspendSaveTimer]; + } + } + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (NSTimeInterval)saveInterval +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = saveInterval; + }); + }); + + return result; +} + +- (void)setSaveInterval:(NSTimeInterval)interval +{ + dispatch_block_t block = ^{ @autoreleasepool { + + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* saveInterval != interval */ islessgreater(saveInterval, interval)) + { + saveInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the saveInterval was previously enabled and it just got disabled, + // then we need to stop the saveTimer. (And we might as well release it.) + // + // 2. If the saveInterval was previously disabled and it just got enabled, + // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) + // + // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate save.) + + if (saveInterval > 0.0) + { + if (saveTimer == NULL) + { + // Handles #2 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self createSuspendedSaveTimer]; + [self updateAndResumeSaveTimer]; + } + else + { + // Handles #3 + // Handles #4 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateAndResumeSaveTimer]; + } + } + else if (saveTimer) + { + // Handles #1 + + [self destroySaveTimer]; + } + } + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (NSTimeInterval)maxAge +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = maxAge; + }); + }); + + return result; +} + +- (void)setMaxAge:(NSTimeInterval)interval +{ + dispatch_block_t block = ^{ @autoreleasepool { + + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* maxAge != interval */ islessgreater(maxAge, interval)) + { + NSTimeInterval oldMaxAge = maxAge; + NSTimeInterval newMaxAge = interval; + + maxAge = interval; + + // There are several cases we need to handle here. + // + // 1. If the maxAge was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the maxAge was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the maxAge was increased, + // then we don't need to do anything. + // + // 4. If the maxAge was decreased, + // then we should do an immediate delete. + + BOOL shouldDeleteNow = NO; + + if (oldMaxAge > 0.0) + { + if (newMaxAge <= 0.0) + { + // Handles #1 + + [self destroyDeleteTimer]; + } + else if (oldMaxAge > newMaxAge) + { + // Handles #4 + shouldDeleteNow = YES; + } + } + else if (newMaxAge > 0.0) + { + // Handles #2 + shouldDeleteNow = YES; + } + + if (shouldDeleteNow) + { + [self performDelete]; + + if (deleteTimer) + [self updateDeleteTimer]; + else + [self createAndStartDeleteTimer]; + } + } + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (NSTimeInterval)deleteInterval +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = deleteInterval; + }); + }); + + return result; +} + +- (void)setDeleteInterval:(NSTimeInterval)interval +{ + dispatch_block_t block = ^{ @autoreleasepool { + + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* deleteInterval != interval */ islessgreater(deleteInterval, interval)) + { + deleteInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the deleteInterval was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the deleteInterval was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate delete.) + + if (deleteInterval > 0.0) + { + if (deleteTimer == NULL) + { + // Handles #2 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a delete is needed the timer will fire immediately. + + [self createAndStartDeleteTimer]; + } + else + { + // Handles #3 + // Handles #4 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateDeleteTimer]; + } + } + else if (deleteTimer) + { + // Handles #1 + + [self destroyDeleteTimer]; + } + } + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (BOOL)deleteOnEverySave +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block BOOL result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = deleteOnEverySave; + }); + }); + + return result; +} + +- (void)setDeleteOnEverySave:(BOOL)flag +{ + dispatch_block_t block = ^{ + + deleteOnEverySave = flag; + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Public API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)savePendingLogEntries +{ + dispatch_block_t block = ^{ @autoreleasepool { + + [self performSaveAndSuspendSaveTimer]; + }}; + + if ([self isOnInternalLoggerQueue]) + block(); + else + dispatch_async(loggerQueue, block); +} + +- (void)deleteOldLogEntries +{ + dispatch_block_t block = ^{ @autoreleasepool { + + [self performDelete]; + }}; + + if ([self isOnInternalLoggerQueue]) + block(); + else + dispatch_async(loggerQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)didAddLogger +{ + // If you override me be sure to invoke [super didAddLogger]; + + [self createSuspendedSaveTimer]; + + [self createAndStartDeleteTimer]; +} + +- (void)willRemoveLogger +{ + // If you override me be sure to invoke [super willRemoveLogger]; + + [self performSaveAndSuspendSaveTimer]; + + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +- (void)logMessage:(DDLogMessage *)logMessage +{ + if ([self db_log:logMessage]) + { + BOOL firstUnsavedEntry = (++unsavedCount == 1); + + if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) + { + [self performSaveAndSuspendSaveTimer]; + } + else if (firstUnsavedEntry) + { + unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); + [self updateAndResumeSaveTimer]; + } + } +} + +- (void)flush +{ + // This method is invoked by DDLog's flushLog method. + // + // It is called automatically when the application quits, + // or if the developer invokes DDLog's flushLog method prior to crashing or something. + + [self performSaveAndSuspendSaveTimer]; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.h new file mode 100755 index 00000000..9d3eceee --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.h @@ -0,0 +1,334 @@ +#import +#import "DDLog.h" + +@class DDLogFileInfo; + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides a logger to write log statements to a file. +**/ + + +// Default configuration and safety/sanity values. +// +// maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE +// rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY +// maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES +// +// You should carefully consider the proper configuration values for your application. + +#define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB +#define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours +#define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// The LogFileManager protocol is designed to allow you to control all aspects of your log files. +// +// The primary purpose of this is to allow you to do something with the log files after they have been rolled. +// Perhaps you want to compress them to save disk space. +// Perhaps you want to upload them to an FTP server. +// Perhaps you want to run some analytics on the file. +// +// A default LogFileManager is, of course, provided. +// The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. +// +// This protocol provides various methods to fetch the list of log files. +// +// There are two variants: sorted and unsorted. +// If sorting is not necessary, the unsorted variant is obviously faster. +// The sorted variant will return an array sorted by when the log files were created, +// with the most recently created log file at index 0, and the oldest log file at the end of the array. +// +// You can fetch only the log file paths (full path including name), log file names (name only), +// or an array of DDLogFileInfo objects. +// The DDLogFileInfo class is documented below, and provides a handy wrapper that +// gives you easy access to various file attributes such as the creation date or the file size. + +@protocol DDLogFileManager +@required + +// Public properties + +/** + * The maximum number of archived log files to keep on disk. + * For example, if this property is set to 3, + * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. + * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. + * + * You may optionally disable deleting old/rolled/archived log files by setting this property to zero. +**/ +@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles; + +// Public methods + +- (NSString *)logsDirectory; + +- (NSArray *)unsortedLogFilePaths; +- (NSArray *)unsortedLogFileNames; +- (NSArray *)unsortedLogFileInfos; + +- (NSArray *)sortedLogFilePaths; +- (NSArray *)sortedLogFileNames; +- (NSArray *)sortedLogFileInfos; + +// Private methods (only to be used by DDFileLogger) + +- (NSString *)createNewLogFile; + +@optional + +// Notifications from DDFileLogger + +- (void)didArchiveLogFile:(NSString *)logFilePath; +- (void)didRollAndArchiveLogFile:(NSString *)logFilePath; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Default log file manager. + * + * All log files are placed inside the logsDirectory. + * If a specific logsDirectory isn't specified, the default directory is used. + * On Mac, this is in ~/Library/Logs/. + * On iPhone, this is in ~/Library/Caches/Logs. + * + * Log files are named "log-.txt", + * where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF]. + * + * Archived log files are automatically deleted according to the maximumNumberOfLogFiles property. +**/ +@interface DDLogFileManagerDefault : NSObject +{ + NSUInteger maximumNumberOfLogFiles; + NSString *_logsDirectory; +} + +- (id)init; +- (id)initWithLogsDirectory:(NSString *)logsDirectory; + +/* Inherited from DDLogFileManager protocol: + +@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles; + +- (NSString *)logsDirectory; + +- (NSArray *)unsortedLogFilePaths; +- (NSArray *)unsortedLogFileNames; +- (NSArray *)unsortedLogFileInfos; + +- (NSArray *)sortedLogFilePaths; +- (NSArray *)sortedLogFileNames; +- (NSArray *)sortedLogFileInfos; + +*/ + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Most users will want file log messages to be prepended with the date and time. + * Rather than forcing the majority of users to write their own formatter, + * we will supply a logical default formatter. + * Users can easily replace this formatter with their own by invoking the setLogFormatter method. + * It can also be removed by calling setLogFormatter, and passing a nil parameter. + * + * In addition to the convenience of having a logical default formatter, + * it will also provide a template that makes it easy for developers to copy and change. +**/ +@interface DDLogFileFormatterDefault : NSObject +{ + NSDateFormatter *dateFormatter; +} + +- (id)init; +- (id)initWithDateFormatter:(NSDateFormatter *)dateFormatter; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDFileLogger : DDAbstractLogger +{ + __strong id logFileManager; + + DDLogFileInfo *currentLogFileInfo; + NSFileHandle *currentLogFileHandle; + + dispatch_source_t rollingTimer; + + unsigned long long maximumFileSize; + NSTimeInterval rollingFrequency; +} + +- (id)init; +- (id)initWithLogFileManager:(id )logFileManager; + +/** + * Log File Rolling: + * + * maximumFileSize: + * The approximate maximum size to allow log files to grow. + * If a log file is larger than this value after a log statement is appended, + * then the log file is rolled. + * + * rollingFrequency + * How often to roll the log file. + * The frequency is given as an NSTimeInterval, which is a double that specifies the interval in seconds. + * Once the log file gets to be this old, it is rolled. + * + * Both the maximumFileSize and the rollingFrequency are used to manage rolling. + * Whichever occurs first will cause the log file to be rolled. + * + * For example: + * The rollingFrequency is 24 hours, + * but the log file surpasses the maximumFileSize after only 20 hours. + * The log file will be rolled at that 20 hour mark. + * A new log file will be created, and the 24 hour timer will be restarted. + * + * You may optionally disable rolling due to filesize by setting maximumFileSize to zero. + * If you do so, rolling is based solely on rollingFrequency. + * + * You may optionally disable rolling due to time by setting rollingFrequency to zero (or any non-positive number). + * If you do so, rolling is based solely on maximumFileSize. + * + * If you disable both maximumFileSize and rollingFrequency, then the log file won't ever be rolled. + * This is strongly discouraged. +**/ +@property (readwrite, assign) unsigned long long maximumFileSize; +@property (readwrite, assign) NSTimeInterval rollingFrequency; + +/** + * The DDLogFileManager instance can be used to retrieve the list of log files, + * and configure the maximum number of archived log files to keep. + * + * @see DDLogFileManager.maximumNumberOfLogFiles +**/ +@property (strong, nonatomic, readonly) id logFileManager; + + +// You can optionally force the current log file to be rolled with this method. + +- (void)rollLogFile; + +// Inherited from DDAbstractLogger + +// - (id )logFormatter; +// - (void)setLogFormatter:(id )formatter; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * DDLogFileInfo is a simple class that provides access to various file attributes. + * It provides good performance as it only fetches the information if requested, + * and it caches the information to prevent duplicate fetches. + * + * It was designed to provide quick snapshots of the current state of log files, + * and to help sort log files in an array. + * + * This class does not monitor the files, or update it's cached attribute values if the file changes on disk. + * This is not what the class was designed for. + * + * If you absolutely must get updated values, + * you can invoke the reset method which will clear the cache. +**/ +@interface DDLogFileInfo : NSObject +{ + __strong NSString *filePath; + __strong NSString *fileName; + + __strong NSDictionary *fileAttributes; + + __strong NSDate *creationDate; + __strong NSDate *modificationDate; + + unsigned long long fileSize; +} + +@property (strong, nonatomic, readonly) NSString *filePath; +@property (strong, nonatomic, readonly) NSString *fileName; + +@property (strong, nonatomic, readonly) NSDictionary *fileAttributes; + +@property (strong, nonatomic, readonly) NSDate *creationDate; +@property (strong, nonatomic, readonly) NSDate *modificationDate; + +@property (nonatomic, readonly) unsigned long long fileSize; + +@property (nonatomic, readonly) NSTimeInterval age; + +@property (nonatomic, readwrite) BOOL isArchived; + ++ (id)logFileWithPath:(NSString *)filePath; + +- (id)initWithFilePath:(NSString *)filePath; + +- (void)reset; +- (void)renameFile:(NSString *)newFileName; + +#if TARGET_IPHONE_SIMULATOR + +// So here's the situation. +// Extended attributes are perfect for what we're trying to do here (marking files as archived). +// This is exactly what extended attributes were designed for. +// +// But Apple screws us over on the simulator. +// Everytime you build-and-go, they copy the application into a new folder on the hard drive, +// and as part of the process they strip extended attributes from our log files. +// Normally, a copy of a file preserves extended attributes. +// So obviously Apple has gone to great lengths to piss us off. +// +// Thus we use a slightly different tactic for marking log files as archived in the simulator. +// That way it "just works" and there's no confusion when testing. +// +// The difference in method names is indicative of the difference in functionality. +// On the simulator we add an attribute by appending a filename extension. +// +// For example: +// log-ABC123.txt -> log-ABC123.archived.txt + +- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName; + +- (void)addExtensionAttributeWithName:(NSString *)attrName; +- (void)removeExtensionAttributeWithName:(NSString *)attrName; + +#else + +// Normal use of extended attributes used everywhere else, +// such as on Macs and on iPhone devices. + +- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName; + +- (void)addExtendedAttributeWithName:(NSString *)attrName; +- (void)removeExtendedAttributeWithName:(NSString *)attrName; + +#endif + +- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another; +- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.m new file mode 100755 index 00000000..9783628e --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.m @@ -0,0 +1,1356 @@ +#import "DDFileLogger.h" + +#import +#import +#import +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#define LOG_LEVEL 2 + +#define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) + +@interface DDLogFileManagerDefault (PrivateAPI) + +- (void)deleteOldLogFiles; +- (NSString *)defaultLogsDirectory; + +@end + +@interface DDFileLogger (PrivateAPI) + +- (void)rollLogFileNow; +- (void)maybeRollLogFileDueToAge; +- (void)maybeRollLogFileDueToSize; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogFileManagerDefault + +@synthesize maximumNumberOfLogFiles; + +- (id)init +{ + return [self initWithLogsDirectory:nil]; +} + +- (id)initWithLogsDirectory:(NSString *)aLogsDirectory +{ + if ((self = [super init])) + { + maximumNumberOfLogFiles = DEFAULT_LOG_MAX_NUM_LOG_FILES; + + if (aLogsDirectory) + _logsDirectory = [aLogsDirectory copy]; + else + _logsDirectory = [[self defaultLogsDirectory] copy]; + + NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew; + + [self addObserver:self forKeyPath:@"maximumNumberOfLogFiles" options:kvoOptions context:nil]; + +// NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]); +// NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]); + } + return self; +} + +- (void)dealloc +{ + [self removeObserver:self forKeyPath:@"maximumNumberOfLogFiles"]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey]; + NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey]; + + if ([old isEqual:new]) + { + // No change in value - don't bother with any processing. + return; + } + + if ([keyPath isEqualToString:@"maximumNumberOfLogFiles"]) + { + //NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles"); + + dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool { + + [self deleteOldLogFiles]; + }}); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Deleting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Deletes archived log files that exceed the maximumNumberOfLogFiles configuration value. +**/ +- (void)deleteOldLogFiles +{ + //NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles"); + + NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles; + if (maxNumLogFiles == 0) + { + // Unlimited - don't delete any log files + return; + } + + NSArray *sortedLogFileInfos = [self sortedLogFileInfos]; + + // Do we consider the first file? + // We are only supposed to be deleting archived files. + // In most cases, the first file is likely the log file that is currently being written to. + // So in most cases, we do not want to consider this file for deletion. + + NSUInteger count = [sortedLogFileInfos count]; + BOOL excludeFirstFile = NO; + + if (count > 0) + { + DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:0]; + + if (!logFileInfo.isArchived) + { + excludeFirstFile = YES; + } + } + + NSArray *sortedArchivedLogFileInfos; + if (excludeFirstFile) + { + count--; + sortedArchivedLogFileInfos = [sortedLogFileInfos subarrayWithRange:NSMakeRange(1, count)]; + } + else + { + sortedArchivedLogFileInfos = sortedLogFileInfos; + } + + NSUInteger i; + for (i = maxNumLogFiles; i < count; i++) + { + DDLogFileInfo *logFileInfo = [sortedArchivedLogFileInfos objectAtIndex:i]; + + //NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName); + + [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Log Files +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the path to the default logs directory. + * If the logs directory doesn't exist, this method automatically creates it. +**/ +- (NSString *)defaultLogsDirectory +{ +#if TARGET_OS_IPHONE + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; + NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"]; + +#else + NSString *appName = [[NSProcessInfo processInfo] processName]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); + NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName]; + +#endif + + return logsDirectory; +} + +- (NSString *)logsDirectory +{ + // We could do this check once, during initalization, and not bother again. + // But this way the code continues to work if the directory gets deleted while the code is running. + + if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory]) + { + NSError *err = nil; + if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory + withIntermediateDirectories:YES attributes:nil error:&err]) + { + //NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err); + } + } + + return _logsDirectory; +} + +- (BOOL)isLogFile:(NSString *)fileName +{ + // A log file has a name like "log-.txt", where is a HEX-string of 6 characters. + // + // For example: log-DFFE99.txt + + BOOL hasProperPrefix = [fileName hasPrefix:@"log-"]; + + BOOL hasProperLength = [fileName length] >= 10; + + + if (hasProperPrefix && hasProperLength) + { + NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"]; + + NSString *hex = [fileName substringWithRange:NSMakeRange(4, 6)]; + NSString *nohex = [hex stringByTrimmingCharactersInSet:hexSet]; + + if ([nohex length] == 0) + { + return YES; + } + } + + return NO; +} + +/** + * Returns an array of NSString objects, + * each of which is the filePath to an existing log file on disk. +**/ +- (NSArray *)unsortedLogFilePaths +{ + NSString *logsDirectory = [self logsDirectory]; + NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil]; + + NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]]; + + for (NSString *fileName in fileNames) + { + // Filter out any files that aren't log files. (Just for extra safety) + + if ([self isLogFile:fileName]) + { + NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName]; + + [unsortedLogFilePaths addObject:filePath]; + } + } + + return unsortedLogFilePaths; +} + +/** + * Returns an array of NSString objects, + * each of which is the fileName of an existing log file on disk. +**/ +- (NSArray *)unsortedLogFileNames +{ + NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths]; + + NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) + { + [unsortedLogFileNames addObject:[filePath lastPathComponent]]; + } + + return unsortedLogFileNames; +} + +/** + * Returns an array of DDLogFileInfo objects, + * each representing an existing log file on disk, + * and containing important information about the log file such as it's modification date and size. +**/ +- (NSArray *)unsortedLogFileInfos +{ + NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths]; + + NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) + { + DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath]; + + [unsortedLogFileInfos addObject:logFileInfo]; + } + + return unsortedLogFileInfos; +} + +/** + * Just like the unsortedLogFilePaths method, but sorts the array. + * The items in the array are sorted by modification date. + * The first item in the array will be the most recently modified log file. +**/ +- (NSArray *)sortedLogFilePaths +{ + NSArray *sortedLogFileInfos = [self sortedLogFileInfos]; + + NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) + { + [sortedLogFilePaths addObject:[logFileInfo filePath]]; + } + + return sortedLogFilePaths; +} + +/** + * Just like the unsortedLogFileNames method, but sorts the array. + * The items in the array are sorted by modification date. + * The first item in the array will be the most recently modified log file. +**/ +- (NSArray *)sortedLogFileNames +{ + NSArray *sortedLogFileInfos = [self sortedLogFileInfos]; + + NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) + { + [sortedLogFileNames addObject:[logFileInfo fileName]]; + } + + return sortedLogFileNames; +} + +/** + * Just like the unsortedLogFileInfos method, but sorts the array. + * The items in the array are sorted by modification date. + * The first item in the array will be the most recently modified log file. +**/ +- (NSArray *)sortedLogFileInfos +{ + return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Creation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Generates a short UUID suitable for use in the log file's name. + * The result will have six characters, all in the hexadecimal set [0123456789ABCDEF]. +**/ +- (NSString *)generateShortUUID +{ + CFUUIDRef uuid = CFUUIDCreate(NULL); + + CFStringRef fullStr = CFUUIDCreateString(NULL, uuid); + NSString *result = (__bridge_transfer NSString *)CFStringCreateWithSubstring(NULL, fullStr, CFRangeMake(0, 6)); + + CFRelease(fullStr); + CFRelease(uuid); + + return result; +} + +/** + * Generates a new unique log file path, and creates the corresponding log file. +**/ +- (NSString *)createNewLogFile +{ + // Generate a random log file name, and create the file (if there isn't a collision) + + NSString *logsDirectory = [self logsDirectory]; + do + { + NSString *fileName = [NSString stringWithFormat:@"log-%@.txt", [self generateShortUUID]]; + + NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) + { + //NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", fileName); + + [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; + + // Since we just created a new log file, we may need to delete some old log files + [self deleteOldLogFiles]; + + return filePath; + } + + } while(YES); +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogFileFormatterDefault + +- (id)init +{ + return [self initWithDateFormatter:nil]; +} + +- (id)initWithDateFormatter:(NSDateFormatter *)aDateFormatter +{ + if ((self = [super init])) + { + if (aDateFormatter) + { + dateFormatter = aDateFormatter; + } + else + { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style + [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"]; + } + } + return self; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage +{ + NSString *dateAndTime = [dateFormatter stringFromDate:(logMessage->timestamp)]; + + return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->logMsg]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDFileLogger + +- (id)init +{ + DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init]; + + return [self initWithLogFileManager:defaultLogFileManager]; +} + +- (id)initWithLogFileManager:(id )aLogFileManager +{ + if ((self = [super init])) + { + maximumFileSize = DEFAULT_LOG_MAX_FILE_SIZE; + rollingFrequency = DEFAULT_LOG_ROLLING_FREQUENCY; + + logFileManager = aLogFileManager; + + formatter = [[DDLogFileFormatterDefault alloc] init]; + } + return self; +} + +- (void)dealloc +{ + [currentLogFileHandle synchronizeFile]; + [currentLogFileHandle closeFile]; + + if (rollingTimer) + { + dispatch_source_cancel(rollingTimer); + rollingTimer = NULL; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Properties +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize logFileManager; + +- (unsigned long long)maximumFileSize +{ + __block unsigned long long result; + + dispatch_block_t block = ^{ + result = maximumFileSize; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, block); + }); + + return result; +} + +- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize +{ + dispatch_block_t block = ^{ @autoreleasepool { + + maximumFileSize = newMaximumFileSize; + [self maybeRollLogFileDueToSize]; + + }}; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); +} + +- (NSTimeInterval)rollingFrequency +{ + __block NSTimeInterval result; + + dispatch_block_t block = ^{ + result = rollingFrequency; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, block); + }); + + return result; +} + +- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency +{ + dispatch_block_t block = ^{ @autoreleasepool { + + rollingFrequency = newRollingFrequency; + [self maybeRollLogFileDueToAge]; + }}; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Rolling +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)scheduleTimerToRollLogFileDueToAge +{ + if (rollingTimer) + { + dispatch_source_cancel(rollingTimer); + rollingTimer = NULL; + } + + if (currentLogFileInfo == nil || rollingFrequency <= 0.0) + { + return; + } + + NSDate *logFileCreationDate = [currentLogFileInfo creationDate]; + + NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate]; + ti += rollingFrequency; + + NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti]; + +// NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge"); +// +// NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate); +// NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate); + + rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); + + dispatch_source_set_event_handler(rollingTimer, ^{ @autoreleasepool { + + [self maybeRollLogFileDueToAge]; + + }}); + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theRollingTimer = rollingTimer; + dispatch_source_set_cancel_handler(rollingTimer, ^{ + dispatch_release(theRollingTimer); + }); + #endif + + uint64_t delay = (uint64_t)([logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC); + dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay); + + dispatch_source_set_timer(rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0); + dispatch_resume(rollingTimer); +} + +- (void)rollLogFile +{ + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + dispatch_block_t block = ^{ @autoreleasepool { + + [self rollLogFileNow]; + }}; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)rollLogFileNow +{ +// NSLogVerbose(@"DDFileLogger: rollLogFileNow"); + + + if (currentLogFileHandle == nil) return; + + [currentLogFileHandle synchronizeFile]; + [currentLogFileHandle closeFile]; + currentLogFileHandle = nil; + + currentLogFileInfo.isArchived = YES; + + if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]) + { + [logFileManager didRollAndArchiveLogFile:(currentLogFileInfo.filePath)]; + } + + currentLogFileInfo = nil; + + if (rollingTimer) + { + dispatch_source_cancel(rollingTimer); + rollingTimer = NULL; + } +} + +- (void)maybeRollLogFileDueToAge +{ + if (rollingFrequency > 0.0 && currentLogFileInfo.age >= rollingFrequency) + { +// NSLogVerbose(@"DDFileLogger: Rolling log file due to age..."); + + [self rollLogFileNow]; + } + else + { + [self scheduleTimerToRollLogFileDueToAge]; + } +} + +- (void)maybeRollLogFileDueToSize +{ + // This method is called from logMessage. + // Keep it FAST. + + // Note: Use direct access to maximumFileSize variable. + // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons). + + if (maximumFileSize > 0) + { + unsigned long long fileSize = [currentLogFileHandle offsetInFile]; + + if (fileSize >= maximumFileSize) + { +// NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize); + + [self rollLogFileNow]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the log file that should be used. + * If there is an existing log file that is suitable, + * within the constraints of maximumFileSize and rollingFrequency, then it is returned. + * + * Otherwise a new file is created and returned. +**/ +- (DDLogFileInfo *)currentLogFileInfo +{ + if (currentLogFileInfo == nil) + { + NSArray *sortedLogFileInfos = [logFileManager sortedLogFileInfos]; + + if ([sortedLogFileInfos count] > 0) + { + DDLogFileInfo *mostRecentLogFileInfo = [sortedLogFileInfos objectAtIndex:0]; + + BOOL useExistingLogFile = YES; + BOOL shouldArchiveMostRecent = NO; + + if (mostRecentLogFileInfo.isArchived) + { + useExistingLogFile = NO; + shouldArchiveMostRecent = NO; + } + else if (maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= maximumFileSize) + { + useExistingLogFile = NO; + shouldArchiveMostRecent = YES; + } + else if (rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= rollingFrequency) + { + useExistingLogFile = NO; + shouldArchiveMostRecent = YES; + } + + if (useExistingLogFile) + { + //NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName); + + currentLogFileInfo = mostRecentLogFileInfo; + } + else + { + if (shouldArchiveMostRecent) + { + mostRecentLogFileInfo.isArchived = YES; + + if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) + { + [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)]; + } + } + } + } + + if (currentLogFileInfo == nil) + { + NSString *currentLogFilePath = [logFileManager createNewLogFile]; + + currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath]; + } + } + + return currentLogFileInfo; +} + +- (NSFileHandle *)currentLogFileHandle +{ + if (currentLogFileHandle == nil) + { + NSString *logFilePath = [[self currentLogFileInfo] filePath]; + + currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + [currentLogFileHandle seekToEndOfFile]; + + if (currentLogFileHandle) + { + [self scheduleTimerToRollLogFileDueToAge]; + } + } + + return currentLogFileHandle; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger Protocol +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)logMessage:(DDLogMessage *)logMessage +{ + NSString *logMsg = logMessage->logMsg; + + if (formatter) + { + logMsg = [formatter formatLogMessage:logMessage]; + } + + if (logMsg) + { + if (![logMsg hasSuffix:@"\n"]) + { + logMsg = [logMsg stringByAppendingString:@"\n"]; + } + + NSData *logData = [logMsg dataUsingEncoding:NSUTF8StringEncoding]; + + [[self currentLogFileHandle] writeData:logData]; + + [self maybeRollLogFileDueToSize]; + } +} + +- (void)willRemoveLogger +{ + // If you override me be sure to invoke [super willRemoveLogger]; + + [self rollLogFileNow]; +} + +- (NSString *)loggerName +{ + return @"cocoa.lumberjack.fileLogger"; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_IPHONE_SIMULATOR + #define XATTR_ARCHIVED_NAME @"archived" +#else + #define XATTR_ARCHIVED_NAME @"lumberjack.log.archived" +#endif + +@implementation DDLogFileInfo + +@synthesize filePath; + +@dynamic fileName; +@dynamic fileAttributes; +@dynamic creationDate; +@dynamic modificationDate; +@dynamic fileSize; +@dynamic age; + +@dynamic isArchived; + + +#pragma mark Lifecycle + ++ (id)logFileWithPath:(NSString *)aFilePath +{ + return [[DDLogFileInfo alloc] initWithFilePath:aFilePath]; +} + +- (id)initWithFilePath:(NSString *)aFilePath +{ + if ((self = [super init])) + { + filePath = [aFilePath copy]; + } + return self; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Standard Info +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSDictionary *)fileAttributes +{ + if (fileAttributes == nil) + { + fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + } + return fileAttributes; +} + +- (NSString *)fileName +{ + if (fileName == nil) + { + fileName = [filePath lastPathComponent]; + } + return fileName; +} + +- (NSDate *)modificationDate +{ + if (modificationDate == nil) + { + modificationDate = [[self fileAttributes] objectForKey:NSFileModificationDate]; + } + + return modificationDate; +} + +- (NSDate *)creationDate +{ + if (creationDate == nil) + { + + #if TARGET_OS_IPHONE + + const char *path = [filePath UTF8String]; + + struct attrlist attrList; + memset(&attrList, 0, sizeof(attrList)); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.commonattr = ATTR_CMN_CRTIME; + + struct { + u_int32_t attrBufferSizeInBytes; + struct timespec crtime; + } attrBuffer; + + int result = getattrlist(path, &attrList, &attrBuffer, sizeof(attrBuffer), 0); + if (result == 0) + { + double seconds = (double)(attrBuffer.crtime.tv_sec); + double nanos = (double)(attrBuffer.crtime.tv_nsec); + + NSTimeInterval ti = seconds + (nanos / 1000000000.0); + + creationDate = [NSDate dateWithTimeIntervalSince1970:ti]; + } + else + { +// NSLogError(@"DDLogFileInfo: creationDate(%@): getattrlist result = %i", self.fileName, result); + } + + #else + + creationDate = [[self fileAttributes] objectForKey:NSFileCreationDate]; + + #endif + + } + return creationDate; +} + +- (unsigned long long)fileSize +{ + if (fileSize == 0) + { + fileSize = [[[self fileAttributes] objectForKey:NSFileSize] unsignedLongLongValue]; + } + + return fileSize; +} + +- (NSTimeInterval)age +{ + return [[self creationDate] timeIntervalSinceNow] * -1.0; +} + +- (NSString *)description +{ + return [@{@"filePath": self.filePath, + @"fileName": self.fileName, + @"fileAttributes": self.fileAttributes, + @"creationDate": self.creationDate, + @"modificationDate": self.modificationDate, + @"fileSize": @(self.fileSize), + @"age": @(self.age), + @"isArchived": @(self.isArchived)} description]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Archiving +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isArchived +{ + +#if TARGET_IPHONE_SIMULATOR + + // Extended attributes don't work properly on the simulator. + // So we have to use a less attractive alternative. + // See full explanation in the header file. + + return [self hasExtensionAttributeWithName:XATTR_ARCHIVED_NAME]; + +#else + + return [self hasExtendedAttributeWithName:XATTR_ARCHIVED_NAME]; + +#endif +} + +- (void)setIsArchived:(BOOL)flag +{ + +#if TARGET_IPHONE_SIMULATOR + + // Extended attributes don't work properly on the simulator. + // So we have to use a less attractive alternative. + // See full explanation in the header file. + + if (flag) + [self addExtensionAttributeWithName:XATTR_ARCHIVED_NAME]; + else + [self removeExtensionAttributeWithName:XATTR_ARCHIVED_NAME]; + +#else + + if (flag) + [self addExtendedAttributeWithName:XATTR_ARCHIVED_NAME]; + else + [self removeExtendedAttributeWithName:XATTR_ARCHIVED_NAME]; + +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Changes +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)reset +{ + fileName = nil; + fileAttributes = nil; + creationDate = nil; + modificationDate = nil; +} + +- (void)renameFile:(NSString *)newFileName +{ + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + if (![newFileName isEqualToString:[self fileName]]) + { + NSString *fileDir = [filePath stringByDeletingLastPathComponent]; + + NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName]; + +// NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName); + + NSError *error = nil; + if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error]) + { + ; +// NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error); + } + + filePath = newFilePath; + [self reset]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Attribute Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_IPHONE_SIMULATOR + +// Extended attributes don't work properly on the simulator. +// So we have to use a less attractive alternative. +// See full explanation in the header file. + +- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName +{ + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + // Split the file name into components. + // + // log-ABC123.archived.uploaded.txt + // + // 0. log-ABC123 + // 1. archived + // 2. uploaded + // 3. txt + // + // So we want to search for the attrName in the components (ignoring the first and last array indexes). + + NSArray *components = [[self fileName] componentsSeparatedByString:@"."]; + + // Watch out for file names without an extension + + NSUInteger count = [components count]; + NSUInteger max = (count >= 2) ? count-1 : count; + + NSUInteger i; + for (i = 1; i < max; i++) + { + NSString *attr = [components objectAtIndex:i]; + + if ([attrName isEqualToString:attr]) + { + return YES; + } + } + + return NO; +} + +- (void)addExtensionAttributeWithName:(NSString *)attrName +{ + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + if ([attrName length] == 0) return; + + // Example: + // attrName = "archived" + // + // "log-ABC123.txt" -> "log-ABC123.archived.txt" + + NSArray *components = [[self fileName] componentsSeparatedByString:@"."]; + + NSUInteger count = [components count]; + + NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1; + NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength]; + + if (count > 0) + { + [newFileName appendString:[components objectAtIndex:0]]; + } + + NSString *lastExt = @""; + + NSUInteger i; + for (i = 1; i < count; i++) + { + NSString *attr = [components objectAtIndex:i]; + if ([attr length] == 0) + { + continue; + } + + if ([attrName isEqualToString:attr]) + { + // Extension attribute already exists in file name + return; + } + + if ([lastExt length] > 0) + { + [newFileName appendFormat:@".%@", lastExt]; + } + + lastExt = attr; + } + + [newFileName appendFormat:@".%@", attrName]; + + if ([lastExt length] > 0) + { + [newFileName appendFormat:@".%@", lastExt]; + } + + [self renameFile:newFileName]; +} + +- (void)removeExtensionAttributeWithName:(NSString *)attrName +{ + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + if ([attrName length] == 0) return; + + // Example: + // attrName = "archived" + // + // "log-ABC123.txt" -> "log-ABC123.archived.txt" + + NSArray *components = [[self fileName] componentsSeparatedByString:@"."]; + + NSUInteger count = [components count]; + + NSUInteger estimatedNewLength = [[self fileName] length]; + NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength]; + + if (count > 0) + { + [newFileName appendString:[components objectAtIndex:0]]; + } + + BOOL found = NO; + + NSUInteger i; + for (i = 1; i < count; i++) + { + NSString *attr = [components objectAtIndex:i]; + + if ([attrName isEqualToString:attr]) + { + found = YES; + } + else + { + [newFileName appendFormat:@".%@", attr]; + } + } + + if (found) + { + [self renameFile:newFileName]; + } +} + +#else + +- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName +{ + const char *path = [filePath UTF8String]; + const char *name = [attrName UTF8String]; + + ssize_t result = getxattr(path, name, NULL, 0, 0, 0); + + return (result >= 0); +} + +- (void)addExtendedAttributeWithName:(NSString *)attrName +{ + const char *path = [filePath UTF8String]; + const char *name = [attrName UTF8String]; + + int result = setxattr(path, name, NULL, 0, 0, 0); + + if (result < 0) + { + ; + //NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %i", attrName, self.fileName, result); + } +} + +- (void)removeExtendedAttributeWithName:(NSString *)attrName +{ + const char *path = [filePath UTF8String]; + const char *name = [attrName UTF8String]; + + int result = removexattr(path, name, 0); + + if (result < 0 && errno != ENOATTR) + { + ; + //NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %i", attrName, self.fileName, result); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Comparisons +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isEqual:(id)object +{ + if ([object isKindOfClass:[self class]]) + { + DDLogFileInfo *another = (DDLogFileInfo *)object; + + return [filePath isEqualToString:[another filePath]]; + } + + return NO; +} + +- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another +{ + NSDate *us = [self creationDate]; + NSDate *them = [another creationDate]; + + NSComparisonResult result = [us compare:them]; + + if (result == NSOrderedAscending) + return NSOrderedDescending; + + if (result == NSOrderedDescending) + return NSOrderedAscending; + + return NSOrderedSame; +} + +- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another +{ + NSDate *us = [self modificationDate]; + NSDate *them = [another modificationDate]; + + NSComparisonResult result = [us compare:them]; + + if (result == NSOrderedAscending) + return NSOrderedDescending; + + if (result == NSOrderedDescending) + return NSOrderedAscending; + + return NSOrderedSame; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.h new file mode 100755 index 00000000..487b7171 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.h @@ -0,0 +1,601 @@ +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * Otherwise, here is a quick refresher. + * There are three steps to using the macros: + * + * Step 1: + * Import the header in your implementation file: + * + * #import "DDLog.h" + * + * Step 2: + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const int ddLogLevel = LOG_LEVEL_VERBOSE; + * + * Step 3: + * Replace your NSLog statements with DDLog statements according to the severity of the message. + * + * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!"); + * + * DDLog works exactly the same as NSLog. + * This means you can pass it multiple variables just like NSLog. +**/ + + +@class DDLogMessage; + +@protocol DDLogger; +@protocol DDLogFormatter; + +/** + * This is the single macro that all other macros below compile into. + * This big multiline macro makes all the other macros easier to read. +**/ + +#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \ + [DDLog log:isAsynchronous \ + level:lvl \ + flag:flg \ + context:ctx \ + file:__FILE__ \ + function:fnct \ + line:__LINE__ \ + tag:atag \ + format:(frmt), ##__VA_ARGS__] + +/** + * Define the Objective-C and C versions of the macro. + * These automatically inject the proper function name for either an objective-c method or c function. + * + * We also define shorthand versions for asynchronous and synchronous logging. +**/ + +#define LOG_OBJC_MACRO(async, lvl, flg, ctx, frmt, ...) \ + LOG_MACRO(async, lvl, flg, ctx, nil, sel_getName(_cmd), frmt, ##__VA_ARGS__) + +#define LOG_C_MACRO(async, lvl, flg, ctx, frmt, ...) \ + LOG_MACRO(async, lvl, flg, ctx, nil, __FUNCTION__, frmt, ##__VA_ARGS__) + +#define SYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \ + LOG_OBJC_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define ASYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \ + LOG_OBJC_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define SYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \ + LOG_C_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define ASYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \ + LOG_C_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +/** + * Define version of the macro that only execute if the logLevel is above the threshold. + * The compiled versions essentially look like this: + * + * if (logFlagForThisLogMsg & ddLogLevel) { execute log message } + * + * As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels. + * This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques. + * + * Note that when compiler optimizations are enabled (as they are for your release builds), + * the log messages above your logging threshold will automatically be compiled out. + * + * (If the compiler sees ddLogLevel declared as a constant, the compiler simply checks to see if the 'if' statement + * would execute, and if not it strips it from the binary.) + * + * We also define shorthand versions for asynchronous and synchronous logging. +**/ + +#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \ + do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0) + +#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \ + LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__) + +#define LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \ + LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__) + +#define SYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \ + LOG_OBJC_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define ASYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \ + LOG_OBJC_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define SYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \ + LOG_C_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +#define ASYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \ + LOG_C_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__) + +/** + * Define versions of the macros that also accept tags. + * + * The DDLogMessage object includes a 'tag' ivar that may be used for a variety of purposes. + * It may be used to pass custom information to loggers or formatters. + * Or it may be used by 3rd party extensions to the framework. + * + * Thes macros just make it a little easier to extend logging functionality. +**/ + +#define LOG_OBJC_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \ + LOG_MACRO(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__) + +#define LOG_C_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \ + LOG_MACRO(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__) + +#define LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \ + do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0) + +#define LOG_OBJC_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \ + LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__) + +#define LOG_C_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \ + LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__) + +/** + * Define the standard options. + * + * We default to only 4 levels because it makes it easier for beginners + * to make the transition to a logging framework. + * + * More advanced users may choose to completely customize the levels (and level names) to suite their needs. + * For more information on this see the "Custom Log Levels" page: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels + * + * Advanced users may also notice that we're using a bitmask. + * This is to allow for custom fine grained logging: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/FineGrainedLogging + * + * -- Flags -- + * + * Typically you will use the LOG_LEVELS (see below), but the flags may be used directly in certain situations. + * For example, say you have a lot of warning log messages, and you wanted to disable them. + * However, you still needed to see your error and info log messages. + * You could accomplish that with the following: + * + * static const int ddLogLevel = LOG_FLAG_ERROR | LOG_FLAG_INFO; + * + * Flags may also be consulted when writing custom log formatters, + * as the DDLogMessage class captures the individual flag that caused the log message to fire. + * + * -- Levels -- + * + * Log levels are simply the proper bitmask of the flags. + * + * -- Booleans -- + * + * The booleans may be used when your logging code involves more than one line. + * For example: + * + * if (LOG_VERBOSE) { + * for (id sprocket in sprockets) + * DDLogVerbose(@"sprocket: %@", [sprocket description]) + * } + * + * -- Async -- + * + * Defines the default asynchronous options. + * The default philosophy for asynchronous logging is very simple: + * + * Log messages with errors should be executed synchronously. + * After all, an error just occurred. The application could be unstable. + * + * All other log messages, such as debug output, are executed asynchronously. + * After all, if it wasn't an error, then it was just informational output, + * or something the application was easily able to recover from. + * + * -- Changes -- + * + * You are strongly discouraged from modifying this file. + * If you do, you make it more difficult on yourself to merge future bug fixes and improvements from the project. + * Instead, create your own MyLogging.h or ApplicationNameLogging.h or CompanyLogging.h + * + * For an example of customizing your logging experience, see the "Custom Log Levels" page: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels +**/ + +#define LOG_FLAG_ERROR (1 << 0) // 0...0001 +#define LOG_FLAG_WARN (1 << 1) // 0...0010 +#define LOG_FLAG_INFO (1 << 2) // 0...0100 +#define LOG_FLAG_VERBOSE (1 << 3) // 0...1000 + +#define LOG_LEVEL_OFF 0 +#define LOG_LEVEL_ERROR (LOG_FLAG_ERROR) // 0...0001 +#define LOG_LEVEL_WARN (LOG_FLAG_ERROR | LOG_FLAG_WARN) // 0...0011 +#define LOG_LEVEL_INFO (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO) // 0...0111 +#define LOG_LEVEL_VERBOSE (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO | LOG_FLAG_VERBOSE) // 0...1111 + +#define LOG_ERROR (ddLogLevel & LOG_FLAG_ERROR) +#define LOG_WARN (ddLogLevel & LOG_FLAG_WARN) +#define LOG_INFO (ddLogLevel & LOG_FLAG_INFO) +#define LOG_VERBOSE (ddLogLevel & LOG_FLAG_VERBOSE) + +#define LOG_ASYNC_ENABLED YES + +#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED) + +#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__) +#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__) +#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__) +#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__) + +#define DDLogCError(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__) +#define DDLogCWarn(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__) +#define DDLogCInfo(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__) +#define DDLogCVerbose(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__) + +/** + * The THIS_FILE macro gives you an NSString of the file name. + * For simplicity and clarity, the file name does not include the full path or file extension. + * + * For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy" +**/ + +NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy); + +#define THIS_FILE (DDExtractFileNameWithoutExtension(__FILE__, NO)) + +/** + * The THIS_METHOD macro gives you the name of the current objective-c method. + * + * For example: DDLogWarn(@"%@ - Requires non-nil strings", THIS_METHOD) -> @"setMake:model: requires non-nil strings" + * + * Note: This does NOT work in straight C functions (non objective-c). + * Instead you should use the predefined __FUNCTION__ macro. +**/ + +#define THIS_METHOD NSStringFromSelector(_cmd) + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLog : NSObject + +/** + * Provides access to the underlying logging queue. + * This may be helpful to Logger classes for things like thread synchronization. +**/ + ++ (dispatch_queue_t)loggingQueue; + +/** + * Logging Primitive. + * + * This method is used by the macros above. + * It is suggested you stick with the macros as they're easier to use. +**/ + ++ (void)log:(BOOL)synchronous + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)file + function:(const char *)function + line:(int)line + tag:(id)tag + format:(NSString *)format, ... __attribute__ ((format (__NSString__, 9, 10))); + +/** + * Logging Primitive. + * + * This method can be used if you have a prepared va_list. +**/ + ++ (void)log:(BOOL)asynchronous + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)file + function:(const char *)function + line:(int)line + tag:(id)tag + format:(NSString *)format + args:(va_list)argList; + + +/** + * Since logging can be asynchronous, there may be times when you want to flush the logs. + * The framework invokes this automatically when the application quits. +**/ + ++ (void)flushLog; + +/** + * Loggers + * + * If you want your log statements to go somewhere, + * you should create and add a logger. +**/ + ++ (void)addLogger:(id )logger; ++ (void)removeLogger:(id )logger; + ++ (void)removeAllLoggers; + +/** + * Registered Dynamic Logging + * + * These methods allow you to obtain a list of classes that are using registered dynamic logging, + * and also provides methods to get and set their log level during run time. +**/ + ++ (NSArray *)registeredClasses; ++ (NSArray *)registeredClassNames; + ++ (int)logLevelForClass:(Class)aClass; ++ (int)logLevelForClassWithName:(NSString *)aClassName; + ++ (void)setLogLevel:(int)logLevel forClass:(Class)aClass; ++ (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol DDLogger +@required + +- (void)logMessage:(DDLogMessage *)logMessage; + +/** + * Formatters may optionally be added to any logger. + * + * If no formatter is set, the logger simply logs the message as it is given in logMessage, + * or it may use its own built in formatting style. +**/ +- (id )logFormatter; +- (void)setLogFormatter:(id )formatter; + +@optional + +/** + * Since logging is asynchronous, adding and removing loggers is also asynchronous. + * In other words, the loggers are added and removed at appropriate times with regards to log messages. + * + * - Loggers will not receive log messages that were executed prior to when they were added. + * - Loggers will not receive log messages that were executed after they were removed. + * + * These methods are executed in the logging thread/queue. + * This is the same thread/queue that will execute every logMessage: invocation. + * Loggers may use these methods for thread synchronization or other setup/teardown tasks. +**/ +- (void)didAddLogger; +- (void)willRemoveLogger; + +/** + * Some loggers may buffer IO for optimization purposes. + * For example, a database logger may only save occasionaly as the disk IO is slow. + * In such loggers, this method should be implemented to flush any pending IO. + * + * This allows invocations of DDLog's flushLog method to be propogated to loggers that need it. + * + * Note that DDLog's flushLog method is invoked automatically when the application quits, + * and it may be also invoked manually by the developer prior to application crashes, or other such reasons. +**/ +- (void)flush; + +/** + * Each logger is executed concurrently with respect to the other loggers. + * Thus, a dedicated dispatch queue is used for each logger. + * Logger implementations may optionally choose to provide their own dispatch queue. +**/ +- (dispatch_queue_t)loggerQueue; + +/** + * If the logger implementation does not choose to provide its own queue, + * one will automatically be created for it. + * The created queue will receive its name from this method. + * This may be helpful for debugging or profiling reasons. +**/ +- (NSString *)loggerName; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol DDLogFormatter +@required + +/** + * Formatters may optionally be added to any logger. + * This allows for increased flexibility in the logging environment. + * For example, log messages for log files may be formatted differently than log messages for the console. + * + * For more information about formatters, see the "Custom Formatters" page: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters + * + * The formatter may also optionally filter the log message by returning nil, + * in which case the logger will not log the message. +**/ +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage; + +@optional + +/** + * A single formatter instance can be added to multiple loggers. + * These methods provides hooks to notify the formatter of when it's added/removed. + * + * This is primarily for thread-safety. + * If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers. + * Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter), + * it could possibly use these hooks to switch to thread-safe versions of the code. +**/ +- (void)didAddToLogger:(id )logger; +- (void)willRemoveFromLogger:(id )logger; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol DDRegisteredDynamicLogging + +/** + * Implement these methods to allow a file's log level to be managed from a central location. + * + * This is useful if you'd like to be able to change log levels for various parts + * of your code from within the running application. + * + * Imagine pulling up the settings for your application, + * and being able to configure the logging level on a per file basis. + * + * The implementation can be very straight-forward: + * + * + (int)ddLogLevel + * { + * return ddLogLevel; + * } + * + * + (void)ddSetLogLevel:(int)logLevel + * { + * ddLogLevel = logLevel; + * } +**/ + ++ (int)ddLogLevel; ++ (void)ddSetLogLevel:(int)logLevel; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The DDLogMessage class encapsulates information about the log message. + * If you write custom loggers or formatters, you will be dealing with objects of this class. +**/ + +enum { + DDLogMessageCopyFile = 1 << 0, + DDLogMessageCopyFunction = 1 << 1 +}; +typedef int DDLogMessageOptions; + +@interface DDLogMessage : NSObject +{ + +// The public variables below can be accessed directly (for speed). +// For example: logMessage->logLevel + +@public + int logLevel; + int logFlag; + int logContext; + NSString *logMsg; + NSDate *timestamp; + char *file; + char *function; + int lineNumber; + mach_port_t machThreadID; + char *queueLabel; + NSString *threadName; + + // For 3rd party extensions to the framework, where flags and contexts aren't enough. + id tag; + + // For 3rd party extensions that manually create DDLogMessage instances. + DDLogMessageOptions options; +} + +/** + * Standard init method for a log message object. + * Used by the logging primitives. (And the macros use the logging primitives.) + * + * If you find need to manually create logMessage objects, there is one thing you should be aware of: + * + * If no flags are passed, the method expects the file and function parameters to be string literals. + * That is, it expects the given strings to exist for the duration of the object's lifetime, + * and it expects the given strings to be immutable. + * In other words, it does not copy these strings, it simply points to them. + * This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters, + * so it makes sense to optimize and skip the unnecessary allocations. + * However, if you need them to be copied you may use the options parameter to specify this. + * Options is a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction. +**/ +- (id)initWithLogMsg:(NSString *)logMsg + level:(int)logLevel + flag:(int)logFlag + context:(int)logContext + file:(const char *)file + function:(const char *)function + line:(int)line + tag:(id)tag + options:(DDLogMessageOptions)optionsMask; + +/** + * Returns the threadID as it appears in NSLog. + * That is, it is a hexadecimal value which is calculated from the machThreadID. +**/ +- (NSString *)threadID; + +/** + * Convenience property to get just the file name, as the file variable is generally the full file path. + * This method does not include the file extension, which is generally unwanted for logging purposes. +**/ +- (NSString *)fileName; + +/** + * Returns the function variable in NSString form. +**/ +- (NSString *)methodName; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The DDLogger protocol specifies that an optional formatter can be added to a logger. + * Most (but not all) loggers will want to support formatters. + * + * However, writting getters and setters in a thread safe manner, + * while still maintaining maximum speed for the logging process, is a difficult task. + * + * To do it right, the implementation of the getter/setter has strict requiremenets: + * - Must NOT require the logMessage method to acquire a lock. + * - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). + * + * To simplify things, an abstract logger is provided that implements the getter and setter. + * + * Logger implementations may simply extend this class, + * and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their logMessage method! +**/ + +@interface DDAbstractLogger : NSObject +{ + id formatter; + + dispatch_queue_t loggerQueue; +} + +- (id )logFormatter; +- (void)setLogFormatter:(id )formatter; + +// For thread-safety assertions +- (BOOL)isOnGlobalLoggingQueue; +- (BOOL)isOnInternalLoggerQueue; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.m new file mode 100755 index 00000000..bb4bfe8d --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.m @@ -0,0 +1,1083 @@ +#import "DDLog.h" + +#import +#import +#import +#import +#import + + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use a primitive logging macro around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#define DD_DEBUG NO + +#define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0) + +// Specifies the maximum queue size of the logging thread. +// +// Since most logging is asynchronous, its possible for rogue threads to flood the logging queue. +// That is, to issue an abundance of log statements faster than the logging thread can keepup. +// Typically such a scenario occurs when log statements are added haphazardly within large loops, +// but may also be possible if relatively slow loggers are being used. +// +// This property caps the queue size at a given number of outstanding log statements. +// If a thread attempts to issue a log statement when the queue is already maxed out, +// the issuing thread will block until the queue size drops below the max again. + +#define LOG_MAX_QUEUE_SIZE 1000 // Should not exceed INT32_MAX + +// The "global logging queue" refers to [DDLog loggingQueue]. +// It is the queue that all log statements go through. +// +// The logging queue sets a flag via dispatch_queue_set_specific using this key. +// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue". + +static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey; + + +@interface DDLoggerNode : NSObject { +@public + id logger; + dispatch_queue_t loggerQueue; +} + ++ (DDLoggerNode *)nodeWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue; + +@end + + +@interface DDLog (PrivateAPI) + ++ (void)lt_addLogger:(id )logger; ++ (void)lt_removeLogger:(id )logger; ++ (void)lt_removeAllLoggers; ++ (void)lt_log:(DDLogMessage *)logMessage; ++ (void)lt_flush; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLog + +// An array used to manage all the individual loggers. +// The array is only modified on the loggingQueue/loggingThread. +static NSMutableArray *loggers; + +// All logging statements are added to the same queue to ensure FIFO operation. +static dispatch_queue_t loggingQueue; + +// Individual loggers are executed concurrently per log statement. +// Each logger has it's own associated queue, and a dispatch group is used for synchrnoization. +static dispatch_group_t loggingGroup; + +// In order to prevent to queue from growing infinitely large, +// a maximum size is enforced (LOG_MAX_QUEUE_SIZE). +static dispatch_semaphore_t queueSemaphore; + +// Minor optimization for uniprocessor machines +static unsigned int numProcessors; + +/** + * The runtime sends initialize to each class in a program exactly one time just before the class, + * or any class that inherits from it, is sent its first message from within the program. (Thus the + * method may never be invoked if the class is not used.) The runtime sends the initialize message to + * classes in a thread-safe manner. Superclasses receive this message before their subclasses. + * + * This method may also be called directly (assumably by accident), hence the safety mechanism. +**/ ++ (void)initialize +{ + static BOOL initialized = NO; + if (!initialized) + { + initialized = YES; + + loggers = [[NSMutableArray alloc] initWithCapacity:4]; + + //NSLogDebug(@"DDLog: Using grand central dispatch"); + + loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); + loggingGroup = dispatch_group_create(); + + void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null + dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); + + queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE); + + // Figure out how many processors are available. + // This may be used later for an optimization on uniprocessor machines. + + host_basic_info_data_t hostInfo; + mach_msg_type_number_t infoCount; + + infoCount = HOST_BASIC_INFO_COUNT; + host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount); + + unsigned int result = (unsigned int)(hostInfo.max_cpus); + unsigned int one = (unsigned int)(1); + + numProcessors = MAX(result, one); + + //NSLogDebug(@"DDLog: numProcessors = %u", numProcessors); + + + #if TARGET_OS_IPHONE + NSString *notificationName = @"UIApplicationWillTerminateNotification"; + #else + NSString *notificationName = @"NSApplicationWillTerminateNotification"; + #endif + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:notificationName + object:nil]; + } +} + +/** + * Provides access to the logging queue. +**/ ++ (dispatch_queue_t)loggingQueue +{ + return loggingQueue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Notifications +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (void)applicationWillTerminate:(NSNotification *)notification +{ + [self flushLog]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logger Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (void)addLogger:(id )logger +{ + if (logger == nil) return; + + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_addLogger:logger]; + }}); +} + ++ (void)removeLogger:(id )logger +{ + if (logger == nil) return; + + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_removeLogger:logger]; + }}); +} + ++ (void)removeAllLoggers +{ + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_removeAllLoggers]; + }}); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Master Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag +{ + // We have a tricky situation here... + // + // In the common case, when the queueSize is below the maximumQueueSize, + // we want to simply enqueue the logMessage. And we want to do this as fast as possible, + // which means we don't want to block and we don't want to use any locks. + // + // However, if the queueSize gets too big, we want to block. + // But we have very strict requirements as to when we block, and how long we block. + // + // The following example should help illustrate our requirements: + // + // Imagine that the maximum queue size is configured to be 5, + // and that there are already 5 log messages queued. + // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed) + // + // Now if our thread issues a log statement (let us call the log message F), + // it should block before the message is added to the queue. + // Furthermore, it should be unblocked immediately after A has been unqueued. + // + // The requirements are strict in this manner so that we block only as long as necessary, + // and so that blocked threads are unblocked in the order in which they were blocked. + // + // Returning to our previous example, let us assume that log messages A through E are still queued. + // Our aforementioned thread is blocked attempting to queue log message F. + // Now assume we have another separate thread that attempts to issue log message G. + // It should block until log messages A and B have been unqueued. + + + // We are using a counting semaphore provided by GCD. + // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. + // Everytime we want to queue a log message we decrement this value. + // If the resulting value is less than zero, + // the semaphore function waits in FIFO order for a signal to occur before returning. + // + // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. + // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. + // If the calling semaphore does not need to block, no kernel call is made. + + dispatch_semaphore_wait(queueSemaphore, DISPATCH_TIME_FOREVER); + + // We've now sure we won't overflow the queue. + // It is time to queue our log message. + + dispatch_block_t logBlock = ^{ @autoreleasepool { + + [self lt_log:logMessage]; + }}; + + if (asyncFlag) + dispatch_async(loggingQueue, logBlock); + else + dispatch_sync(loggingQueue, logBlock); +} + ++ (void)log:(BOOL)asynchronous + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)file + function:(const char *)function + line:(int)line + tag:(id)tag + format:(NSString *)format, ... +{ + va_list args; + if (format) + { + va_start(args, format); + + NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; + DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:0]; + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + + va_end(args); + } +} + ++ (void)log:(BOOL)asynchronous + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)file + function:(const char *)function + line:(int)line + tag:(id)tag + format:(NSString *)format + args:(va_list)args +{ + if (format) + { + NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; + DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:0]; + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + } +} + ++ (void)flushLog +{ + dispatch_sync(loggingQueue, ^{ @autoreleasepool { + + [self lt_flush]; + }}); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Registered Dynamic Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (BOOL)isRegisteredClass:(Class)class +{ + SEL getterSel = @selector(ddLogLevel); + SEL setterSel = @selector(ddSetLogLevel:); + +#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR + + // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4 + // + // Crash caused by class_getClassMethod(2). + // + // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until + // users had VoiceOver enabled [...]. I was able to work around it by searching the + // result of class_copyMethodList() instead of calling class_getClassMethod()" + + BOOL result = NO; + + unsigned int methodCount, i; + Method *methodList = class_copyMethodList(object_getClass(class), &methodCount); + + if (methodList != NULL) + { + BOOL getterFound = NO; + BOOL setterFound = NO; + + for (i = 0; i < methodCount; ++i) + { + SEL currentSel = method_getName(methodList[i]); + + if (currentSel == getterSel) + { + getterFound = YES; + } + else if (currentSel == setterSel) + { + setterFound = YES; + } + + if (getterFound && setterFound) + { + result = YES; + break; + } + } + + free(methodList); + } + + return result; + +#else + + // Issue #24 (GitHub) - Crashing in in ARC+Simulator + // + // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator. + // For running in the Simulator, it needs to execute the non-iOS code. + + Method getter = class_getClassMethod(class, getterSel); + Method setter = class_getClassMethod(class, setterSel); + + if ((getter != NULL) && (setter != NULL)) + { + return YES; + } + + return NO; + +#endif +} + ++ (NSArray *)registeredClasses +{ + int numClasses, i; + + // We're going to get the list of all registered classes. + // The Objective-C runtime library automatically registers all the classes defined in your source code. + // + // To do this we use the following method (documented in the Objective-C Runtime Reference): + // + // int objc_getClassList(Class *buffer, int bufferLen) + // + // We can pass (NULL, 0) to obtain the total number of + // registered class definitions without actually retrieving any class definitions. + // This allows us to allocate the minimum amount of memory needed for the application. + + numClasses = objc_getClassList(NULL, 0); + + // The numClasses method now tells us how many classes we have. + // So we can allocate our buffer, and get pointers to all the class definitions. + + Class *classes = (Class *)malloc(sizeof(Class) * numClasses); + + numClasses = objc_getClassList(classes, numClasses); + + // We can now loop through the classes, and test each one to see if it is a DDLogging class. + + NSMutableArray *result = [NSMutableArray arrayWithCapacity:numClasses]; + + for (i = 0; i < numClasses; i++) + { + Class class = classes[i]; + + if ([self isRegisteredClass:class]) + { + [result addObject:class]; + } + } + + free(classes); + + return result; +} + ++ (NSArray *)registeredClassNames +{ + NSArray *registeredClasses = [self registeredClasses]; + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[registeredClasses count]]; + + for (Class class in registeredClasses) + { + [result addObject:NSStringFromClass(class)]; + } + + return result; +} + ++ (int)logLevelForClass:(Class)aClass +{ + if ([self isRegisteredClass:aClass]) + { + return [aClass ddLogLevel]; + } + + return -1; +} + ++ (int)logLevelForClassWithName:(NSString *)aClassName +{ + Class aClass = NSClassFromString(aClassName); + + return [self logLevelForClass:aClass]; +} + ++ (void)setLogLevel:(int)logLevel forClass:(Class)aClass +{ + if ([self isRegisteredClass:aClass]) + { + [aClass ddSetLogLevel:logLevel]; + } +} + ++ (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName +{ + Class aClass = NSClassFromString(aClassName); + + [self setLogLevel:logLevel forClass:aClass]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logging Thread +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method should only be run on the logging thread/queue. +**/ ++ (void)lt_addLogger:(id )logger +{ + // Add to loggers array. + // Need to create loggerQueue if loggerNode doesn't provide one. + + dispatch_queue_t loggerQueue = NULL; + + if ([logger respondsToSelector:@selector(loggerQueue)]) + { + // Logger may be providing its own queue + + loggerQueue = [logger loggerQueue]; + } + + if (loggerQueue == nil) + { + // Automatically create queue for the logger. + // Use the logger name as the queue name if possible. + + const char *loggerQueueName = NULL; + if ([logger respondsToSelector:@selector(loggerName)]) + { + loggerQueueName = [[logger loggerName] UTF8String]; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + } + + DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue]; + [loggers addObject:loggerNode]; + + if ([logger respondsToSelector:@selector(didAddLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [logger didAddLogger]; + }}); + } +} + +/** + * This method should only be run on the logging thread/queue. +**/ ++ (void)lt_removeLogger:(id )logger +{ + // Find associated loggerNode in list of added loggers + + DDLoggerNode *loggerNode = nil; + + for (DDLoggerNode *node in loggers) + { + if (node->logger == logger) + { + loggerNode = node; + break; + } + } + + if (loggerNode == nil) + { + //NSLogDebug(@"DDLog: Request to remove logger which wasn't added"); + return; + } + + // Notify logger + + if ([logger respondsToSelector:@selector(willRemoveLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [logger willRemoveLogger]; + }}); + } + + // Remove from loggers array + + [loggers removeObject:loggerNode]; +} + +/** + * This method should only be run on the logging thread/queue. +**/ ++ (void)lt_removeAllLoggers +{ + // Notify all loggers + + for (DDLoggerNode *loggerNode in loggers) + { + if ([loggerNode->logger respondsToSelector:@selector(willRemoveLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger willRemoveLogger]; + }}); + } + } + + // Remove all loggers from array + + [loggers removeAllObjects]; +} + +/** + * This method should only be run on the logging thread/queue. +**/ ++ (void)lt_log:(DDLogMessage *)logMessage +{ + // Execute the given log message on each of our loggers. + + if (numProcessors > 1) + { + // Execute each logger concurrently, each within its own queue. + // All blocks are added to same group. + // After each block has been queued, wait on group. + // + // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages. + // This would defeat the purpose of the efforts we made earlier to restrict the max queue size. + + for (DDLoggerNode *loggerNode in loggers) + { + dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger logMessage:logMessage]; + + }}); + } + + dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); + } + else + { + // Execute each logger serialy, each within its own queue. + + for (DDLoggerNode *loggerNode in loggers) + { + dispatch_sync(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger logMessage:logMessage]; + + }}); + } + } + + // If our queue got too big, there may be blocked threads waiting to add log messages to the queue. + // Since we've now dequeued an item from the log, we may need to unblock the next thread. + + // We are using a counting semaphore provided by GCD. + // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. + // When a log message is queued this value is decremented. + // When a log message is dequeued this value is incremented. + // If the value ever drops below zero, + // the queueing thread blocks and waits in FIFO order for us to signal it. + // + // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. + // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. + // If the calling semaphore does not need to block, no kernel call is made. + + dispatch_semaphore_signal(queueSemaphore); +} + +/** + * This method should only be run on the background logging thread. +**/ ++ (void)lt_flush +{ + // All log statements issued before the flush method was invoked have now been executed. + // + // Now we need to propogate the flush request to any loggers that implement the flush method. + // This is designed for loggers that buffer IO. + + for (DDLoggerNode *loggerNode in loggers) + { + if ([loggerNode->logger respondsToSelector:@selector(flush)]) + { + dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger flush]; + + }}); + } + } + + dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) +{ + if (filePath == NULL) return nil; + + char *lastSlash = NULL; + char *lastDot = NULL; + + char *p = (char *)filePath; + + while (*p != '\0') + { + if (*p == '/') + lastSlash = p; + else if (*p == '.') + lastDot = p; + + p++; + } + + char *subStr; + NSUInteger subLen; + + if (lastSlash) + { + if (lastDot) + { + // lastSlash -> lastDot + subStr = lastSlash + 1; + subLen = lastDot - subStr; + } + else + { + // lastSlash -> endOfString + subStr = lastSlash + 1; + subLen = p - subStr; + } + } + else + { + if (lastDot) + { + // startOfString -> lastDot + subStr = (char *)filePath; + subLen = lastDot - subStr; + } + else + { + // startOfString -> endOfString + subStr = (char *)filePath; + subLen = p - subStr; + } + } + + if (copy) + { + return [[NSString alloc] initWithBytes:subStr + length:subLen + encoding:NSUTF8StringEncoding]; + } + else + { + // We can take advantage of the fact that __FILE__ is a string literal. + // Specifically, we don't need to waste time copying the string. + // We can just tell NSString to point to a range within the string literal. + + return [[NSString alloc] initWithBytesNoCopy:subStr + length:subLen + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLoggerNode + +- (id)initWithLogger:(id )aLogger loggerQueue:(dispatch_queue_t)aLoggerQueue +{ + if ((self = [super init])) + { + logger = aLogger; + + if (aLoggerQueue) { + loggerQueue = aLoggerQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(loggerQueue); + #endif + } + } + return self; +} + ++ (DDLoggerNode *)nodeWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue +{ + return [[DDLoggerNode alloc] initWithLogger:logger loggerQueue:loggerQueue]; +} + +- (void)dealloc +{ + #if !OS_OBJECT_USE_OBJC + if (loggerQueue) dispatch_release(loggerQueue); + #endif +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogMessage + +static char *dd_str_copy(const char *str) +{ + if (str == NULL) return NULL; + + size_t length = strlen(str); + char * result = malloc(length + 1); + strncpy(result, str, length); + result[length] = 0; + + return result; +} + +- (id)initWithLogMsg:(NSString *)msg + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)aFile + function:(const char *)aFunction + line:(int)line + tag:(id)aTag + options:(DDLogMessageOptions)optionsMask +{ + if ((self = [super init])) + { + logMsg = msg; + logLevel = level; + logFlag = flag; + logContext = context; + lineNumber = line; + tag = aTag; + options = optionsMask; + + if (options & DDLogMessageCopyFile) + file = dd_str_copy(aFile); + else + file = (char *)aFile; + + if (options & DDLogMessageCopyFunction) + function = dd_str_copy(aFunction); + else + function = (char *)aFunction; + + timestamp = [[NSDate alloc] init]; + + machThreadID = pthread_mach_thread_np(pthread_self()); + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + // The documentation for dispatch_get_current_queue() states: + // + // > [This method is] "recommended for debugging and logging purposes only"... + // + // Well that's exactly how we're using it here. Literally for logging purposes only. + // However, Apple has decided to deprecate this method anyway. + // However they have not given us an alternate version of dispatch_queue_get_label() that + // automatically uses the current queue, thus dispatch_get_current_queue() is still required. + // + // If dispatch_get_current_queue() disappears, without a dispatch_queue_get_label() alternative, + // Apple will have effectively taken away our ability to properly log the name of executing dispatch queue. + + dispatch_queue_t currentQueue = dispatch_get_current_queue(); + #pragma clang diagnostic pop + + queueLabel = dd_str_copy(dispatch_queue_get_label(currentQueue)); + + threadName = [[NSThread currentThread] name]; + } + return self; +} + +- (NSString *)threadID +{ + return [[NSString alloc] initWithFormat:@"%x", machThreadID]; +} + +- (NSString *)fileName +{ + return DDExtractFileNameWithoutExtension(file, NO); +} + +- (NSString *)methodName +{ + if (function == NULL) + return nil; + else + return [[NSString alloc] initWithUTF8String:function]; +} + +- (void)dealloc +{ + if (file && (options & DDLogMessageCopyFile)) + free(file); + + if (function && (options & DDLogMessageCopyFunction)) + free(function); + + if (queueLabel) + free(queueLabel); +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDAbstractLogger + +- (id)init +{ + if ((self = [super init])) + { + const char *loggerQueueName = NULL; + if ([self respondsToSelector:@selector(loggerName)]) + { + loggerQueueName = [[self loggerName] UTF8String]; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + + // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. + // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. + // The documentation states: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // > Specifying a pointer to a string constant is not recommended. + // + // So we're going to use the very convenient key of "self", + // which also works when multiple logger classes extend this class, as each will have a different "self" key. + // + // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). + + void *key = (__bridge void *)self; + void *nonNullValue = (__bridge void *)self; + + dispatch_queue_set_specific(loggerQueue, key, nonNullValue, NULL); + } + return self; +} + +- (void)dealloc +{ + #if !OS_OBJECT_USE_OBJC + if (loggerQueue) dispatch_release(loggerQueue); + #endif +} + +- (void)logMessage:(DDLogMessage *)logMessage +{ + // Override me +} + +- (id )logFormatter +{ + // This method must be thread safe and intuitive. + // Therefore if somebody executes the following code: + // + // [logger setLogFormatter:myFormatter]; + // formatter = [logger logFormatter]; + // + // They would expect formatter to equal myFormatter. + // This functionality must be ensured by the getter and setter method. + // + // The thread safety must not come at a cost to the performance of the logMessage method. + // This method is likely called sporadically, while the logMessage method is called repeatedly. + // This means, the implementation of this method: + // - Must NOT require the logMessage method to acquire a lock. + // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). + // + // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. + // This is the same queue that the logMessage method operates on. + // + // Note: The last time I benchmarked the performance of direct access vs atomic property access, + // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. + // + // Furthermore, consider the following code: + // + // DDLogVerbose(@"log msg 1"); + // DDLogVerbose(@"log msg 2"); + // [logger setFormatter:myFormatter]; + // DDLogVerbose(@"log msg 3"); + // + // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. + // This must remain true even when using asynchronous logging. + // We must keep in mind the various queue's that are in play here: + // + // loggerQueue : Our own private internal queue that the logMessage method runs on. + // Operations are added to this queue from the global loggingQueue. + // + // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. + // + // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue. + // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. + + // IMPORTANT NOTE: + // + // Methods within the DDLogger implementation MUST access the formatter ivar directly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block id result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = formatter; + }); + }); + + return result; +} + +- (void)setLogFormatter:(id )logFormatter +{ + // The design of this method is documented extensively in the logFormatter message (above in code). + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_block_t block = ^{ @autoreleasepool { + + if (formatter != logFormatter) + { + if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) { + [formatter willRemoveFromLogger:self]; + } + + formatter = logFormatter; + + if ([formatter respondsToSelector:@selector(didAddToLogger:)]) { + [formatter didAddToLogger:self]; + } + } + }}; + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); +} + +- (dispatch_queue_t)loggerQueue +{ + return loggerQueue; +} + +- (NSString *)loggerName +{ + return NSStringFromClass([self class]); +} + +- (BOOL)isOnGlobalLoggingQueue +{ + return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); +} + +- (BOOL)isOnInternalLoggerQueue +{ + void *key = (__bridge void *)self; + return (dispatch_get_specific(key) != NULL); +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.h new file mode 100755 index 00000000..0acb0fdd --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.h @@ -0,0 +1,167 @@ +#import +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import "DDLog.h" + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides a logger for Terminal output or Xcode console output, + * depending on where you are running your code. + * + * As described in the "Getting Started" page, + * the traditional NSLog() function directs it's output to two places: + * + * - Apple System Log (so it shows up in Console.app) + * - StdErr (if stderr is a TTY, so log statements show up in Xcode console) + * + * To duplicate NSLog() functionality you can simply add this logger and an asl logger. + * However, if you instead choose to use file logging (for faster performance), + * you may choose to use only a file logger and a tty logger. +**/ + +@interface DDTTYLogger : DDAbstractLogger +{ + NSCalendar *calendar; + NSUInteger calendarUnitFlags; + + NSString *appName; + char *app; + size_t appLen; + + NSString *processID; + char *pid; + size_t pidLen; + + BOOL colorsEnabled; + NSMutableArray *colorProfilesArray; + NSMutableDictionary *colorProfilesDict; +} + ++ (DDTTYLogger *)sharedInstance; + +/* Inherited from the DDLogger protocol: + * + * Formatters may optionally be added to any logger. + * + * If no formatter is set, the logger simply logs the message as it is given in logMessage, + * or it may use its own built in formatting style. + * + * More information about formatters can be found here: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters + * + * The actual implementation of these methods is inherited from DDAbstractLogger. + +- (id )logFormatter; +- (void)setLogFormatter:(id )formatter; + +*/ + +/** + * Want to use different colors for different log levels? + * Enable this property. + * + * If you run the application via the Terminal (not Xcode), + * the logger will map colors to xterm-256color or xterm-color (if available). + * + * Xcode does NOT natively support colors in the Xcode debugging console. + * You'll need to install the XcodeColors plugin to see colors in the Xcode console. + * https://github.com/robbiehanson/XcodeColors + * + * The default value if NO. +**/ +@property (readwrite, assign) BOOL colorsEnabled; + +/** + * The default color set (foregroundColor, backgroundColor) is: + * + * - LOG_FLAG_ERROR = (red, nil) + * - LOG_FLAG_WARN = (orange, nil) + * + * You can customize the colors however you see fit. + * Please note that you are passing a flag, NOT a level. + * + * GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_FLAG_INFO]; // <- Good :) + * BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_LEVEL_INFO]; // <- BAD! :( + * + * LOG_FLAG_INFO = 0...00100 + * LOG_LEVEL_INFO = 0...00111 <- Would match LOG_FLAG_INFO and LOG_FLAG_WARN and LOG_FLAG_ERROR + * + * If you run the application within Xcode, then the XcodeColors plugin is required. + * + * If you run the application from a shell, then DDTTYLogger will automatically map the given color to + * the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.) + * + * This method invokes setForegroundColor:backgroundColor:forFlag:context: and passes the default context (0). +**/ +#if TARGET_OS_IPHONE +- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask; +#else +- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask; +#endif + +/** + * Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context. + * + * A logging context is often used to identify log messages coming from a 3rd party framework, + * although logging context's can be used for many different functions. + * + * Logging context's are explained in further detail here: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext +**/ +#if TARGET_OS_IPHONE +- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask context:(int)ctxt; +#else +- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask context:(int)ctxt; +#endif + +/** + * Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile. + * For example, you could do something like this: + * + * static NSString *const PurpleTag = @"PurpleTag"; + * + * #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__) + * + * And then in your applicationDidFinishLaunching, or wherever you configure Lumberjack: + * + * #if TARGET_OS_IPHONE + * UIColor *purple = [UIColor colorWithRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0]; + * #else + * NSColor *purple = [NSColor colorWithCalibratedRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0]; + * + * [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag]; + * [DDLog addLogger:[DDTTYLogger sharedInstance]]; + * + * This would essentially give you a straight NSLog replacement that prints in purple: + * + * DDLogPurple(@"I'm a purple log message!"); +**/ +#if TARGET_OS_IPHONE +- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forTag:(id )tag; +#else +- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forTag:(id )tag; +#endif + +/** + * Clearing color profiles. +**/ +- (void)clearColorsForFlag:(int)mask; +- (void)clearColorsForFlag:(int)mask context:(int)context; +- (void)clearColorsForTag:(id )tag; +- (void)clearColorsForAllFlags; +- (void)clearColorsForAllTags; +- (void)clearAllColors; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.m new file mode 100755 index 00000000..d219646c --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.m @@ -0,0 +1,1479 @@ +#import "DDTTYLogger.h" + +#import +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#define LOG_LEVEL 2 + +#define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) + +// Xcode does NOT natively support colors in the Xcode debugging console. +// You'll need to install the XcodeColors plugin to see colors in the Xcode console. +// https://github.com/robbiehanson/XcodeColors +// +// The following is documentation from the XcodeColors project: +// +// +// How to apply color formatting to your log statements: +// +// To set the foreground color: +// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255. +// +// To set the background color: +// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36. +// +// To reset the foreground color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "fg;" +// +// To reset the background color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "bg;" +// +// To reset the foreground and background color (to default values) in one operation: +// Insert the ESCAPE_SEQ into your string, followed by ";" + +#define XCODE_COLORS_ESCAPE_SEQ "\033[" + +#define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color +#define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color +#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE_SEQ ";" // Clear any foreground or background color + +// Some simple defines to make life easier on ourself + +#if TARGET_OS_IPHONE + #define MakeColor(r, g, b) [UIColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f] +#else + #define MakeColor(r, g, b) [NSColor colorWithCalibratedRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f] +#endif + +#if TARGET_OS_IPHONE + #define OSColor UIColor +#else + #define OSColor NSColor +#endif + +// If running in a shell, not all RGB colors will be supported. +// In this case we automatically map to the closest available color. +// In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell. +// However, not every shell is the same, and Apple likes to think different even when it comes to shell colors. +// +// Map to standard Terminal.app colors (1), or +// map to standard xterm colors (0). + +#define MAP_TO_TERMINAL_APP_COLORS 1 + + +@interface DDTTYLoggerColorProfile : NSObject { +@public + int mask; + int context; + + uint8_t fg_r; + uint8_t fg_g; + uint8_t fg_b; + + uint8_t bg_r; + uint8_t bg_g; + uint8_t bg_b; + + NSUInteger fgCodeIndex; + NSString *fgCodeRaw; + + NSUInteger bgCodeIndex; + NSString *bgCodeRaw; + + char fgCode[24]; + size_t fgCodeLen; + + char bgCode[24]; + size_t bgCodeLen; + + char resetCode[8]; + size_t resetCodeLen; +} + +- (id)initWithForegroundColor:(OSColor *)fgColor backgroundColor:(OSColor *)bgColor flag:(int)mask context:(int)ctxt; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDTTYLogger + +static BOOL isaColorTTY; +static BOOL isaColor256TTY; +static BOOL isaXcodeColorTTY; + +static NSArray *codes_fg = nil; +static NSArray *codes_bg = nil; +static NSArray *colors = nil; + +static DDTTYLogger *sharedInstance; + +/** + * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 16 color mode. + * + * This method is used when the application is running from within a shell that only supports 16 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. +**/ ++ (void)initialize_colors_16 +{ + if (codes_fg || codes_bg || colors) return; + + NSMutableArray *m_codes_fg = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *m_codes_bg = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:16]; + + // In a standard shell only 16 colors are supported. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + [m_codes_fg addObject:@"30m"]; // normal - black + [m_codes_fg addObject:@"31m"]; // normal - red + [m_codes_fg addObject:@"32m"]; // normal - green + [m_codes_fg addObject:@"33m"]; // normal - yellow + [m_codes_fg addObject:@"34m"]; // normal - blue + [m_codes_fg addObject:@"35m"]; // normal - magenta + [m_codes_fg addObject:@"36m"]; // normal - cyan + [m_codes_fg addObject:@"37m"]; // normal - gray + [m_codes_fg addObject:@"1;30m"]; // bright - darkgray + [m_codes_fg addObject:@"1;31m"]; // bright - red + [m_codes_fg addObject:@"1;32m"]; // bright - green + [m_codes_fg addObject:@"1;33m"]; // bright - yellow + [m_codes_fg addObject:@"1;34m"]; // bright - blue + [m_codes_fg addObject:@"1;35m"]; // bright - magenta + [m_codes_fg addObject:@"1;36m"]; // bright - cyan + [m_codes_fg addObject:@"1;37m"]; // bright - white + + [m_codes_bg addObject:@"40m"]; // normal - black + [m_codes_bg addObject:@"41m"]; // normal - red + [m_codes_bg addObject:@"42m"]; // normal - green + [m_codes_bg addObject:@"43m"]; // normal - yellow + [m_codes_bg addObject:@"44m"]; // normal - blue + [m_codes_bg addObject:@"45m"]; // normal - magenta + [m_codes_bg addObject:@"46m"]; // normal - cyan + [m_codes_bg addObject:@"47m"]; // normal - gray + [m_codes_bg addObject:@"1;40m"]; // bright - darkgray + [m_codes_bg addObject:@"1;41m"]; // bright - red + [m_codes_bg addObject:@"1;42m"]; // bright - green + [m_codes_bg addObject:@"1;43m"]; // bright - yellow + [m_codes_bg addObject:@"1;44m"]; // bright - blue + [m_codes_bg addObject:@"1;45m"]; // bright - magenta + [m_codes_bg addObject:@"1;46m"]; // bright - cyan + [m_codes_bg addObject:@"1;47m"]; // bright - white + +#if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the default colors used by Apple's Terminal.app. + + [m_colors addObject:MakeColor( 0, 0, 0)]; // normal - black + [m_colors addObject:MakeColor(194, 54, 33)]; // normal - red + [m_colors addObject:MakeColor( 37, 188, 36)]; // normal - green + [m_colors addObject:MakeColor(173, 173, 39)]; // normal - yellow + [m_colors addObject:MakeColor( 73, 46, 225)]; // normal - blue + [m_colors addObject:MakeColor(211, 56, 211)]; // normal - magenta + [m_colors addObject:MakeColor( 51, 187, 200)]; // normal - cyan + [m_colors addObject:MakeColor(203, 204, 205)]; // normal - gray + [m_colors addObject:MakeColor(129, 131, 131)]; // bright - darkgray + [m_colors addObject:MakeColor(252, 57, 31)]; // bright - red + [m_colors addObject:MakeColor( 49, 231, 34)]; // bright - green + [m_colors addObject:MakeColor(234, 236, 35)]; // bright - yellow + [m_colors addObject:MakeColor( 88, 51, 255)]; // bright - blue + [m_colors addObject:MakeColor(249, 53, 248)]; // bright - magenta + [m_colors addObject:MakeColor( 20, 240, 240)]; // bright - cyan + [m_colors addObject:MakeColor(233, 235, 235)]; // bright - white + +#else + + // Standard xterm colors: + // + // These are the default colors used by most xterm shells. + + [m_colors addObject:MakeColor( 0, 0, 0)]; // normal - black + [m_colors addObject:MakeColor(205, 0, 0)]; // normal - red + [m_colors addObject:MakeColor( 0, 205, 0)]; // normal - green + [m_colors addObject:MakeColor(205, 205, 0)]; // normal - yellow + [m_colors addObject:MakeColor( 0, 0, 238)]; // normal - blue + [m_colors addObject:MakeColor(205, 0, 205)]; // normal - magenta + [m_colors addObject:MakeColor( 0, 205, 205)]; // normal - cyan + [m_colors addObject:MakeColor(229, 229, 229)]; // normal - gray + [m_colors addObject:MakeColor(127, 127, 127)]; // bright - darkgray + [m_colors addObject:MakeColor(255, 0, 0)]; // bright - red + [m_colors addObject:MakeColor( 0, 255, 0)]; // bright - green + [m_colors addObject:MakeColor(255, 255, 0)]; // bright - yellow + [m_colors addObject:MakeColor( 92, 92, 255)]; // bright - blue + [m_colors addObject:MakeColor(255, 0, 255)]; // bright - magenta + [m_colors addObject:MakeColor( 0, 255, 255)]; // bright - cyan + [m_colors addObject:MakeColor(255, 255, 255)]; // bright - white + +#endif + + codes_fg = [m_codes_fg copy]; + codes_bg = [m_codes_bg copy]; + colors = [m_colors copy]; + + NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)"); + NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)"); +} + +/** + * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 256 color mode. + * + * This method is used when the application is running from within a shell that supports 256 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. +**/ ++ (void)initialize_colors_256 +{ + if (codes_fg || codes_bg || colors) return; + + NSMutableArray *m_codes_fg = [NSMutableArray arrayWithCapacity:(256-16)]; + NSMutableArray *m_codes_bg = [NSMutableArray arrayWithCapacity:(256-16)]; + NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:(256-16)]; + + #if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the colors the Terminal.app uses in xterm-256color mode. + // In this mode, the terminal supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are actually configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // While the color codes are standardized, the actual RGB values for each color code is not. + // Apple's Terminal.app uses different RGB values from that of a standard xterm. + // Apple's choices in colors are designed to be a little nicer on the eyes. + // + // The last 24 color codes represent a grayscale. + // + // Unfortunately, unlike the standard xterm color chart, + // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of). + // Also, I don't know of any ways to programmatically query the shell for the RGB values. + // So this big giant color chart had to be made by hand. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + // Colors + + [m_colors addObject:MakeColor( 47, 49, 49)]; + [m_colors addObject:MakeColor( 60, 42, 144)]; + [m_colors addObject:MakeColor( 66, 44, 183)]; + [m_colors addObject:MakeColor( 73, 46, 222)]; + [m_colors addObject:MakeColor( 81, 50, 253)]; + [m_colors addObject:MakeColor( 88, 51, 255)]; + + [m_colors addObject:MakeColor( 42, 128, 37)]; + [m_colors addObject:MakeColor( 42, 127, 128)]; + [m_colors addObject:MakeColor( 44, 126, 169)]; + [m_colors addObject:MakeColor( 56, 125, 209)]; + [m_colors addObject:MakeColor( 59, 124, 245)]; + [m_colors addObject:MakeColor( 66, 123, 255)]; + + [m_colors addObject:MakeColor( 51, 163, 41)]; + [m_colors addObject:MakeColor( 39, 162, 121)]; + [m_colors addObject:MakeColor( 42, 161, 162)]; + [m_colors addObject:MakeColor( 53, 160, 202)]; + [m_colors addObject:MakeColor( 45, 159, 240)]; + [m_colors addObject:MakeColor( 58, 158, 255)]; + + [m_colors addObject:MakeColor( 31, 196, 37)]; + [m_colors addObject:MakeColor( 48, 196, 115)]; + [m_colors addObject:MakeColor( 39, 195, 155)]; + [m_colors addObject:MakeColor( 49, 195, 195)]; + [m_colors addObject:MakeColor( 32, 194, 235)]; + [m_colors addObject:MakeColor( 53, 193, 255)]; + + [m_colors addObject:MakeColor( 50, 229, 35)]; + [m_colors addObject:MakeColor( 40, 229, 109)]; + [m_colors addObject:MakeColor( 27, 229, 149)]; + [m_colors addObject:MakeColor( 49, 228, 189)]; + [m_colors addObject:MakeColor( 33, 228, 228)]; + [m_colors addObject:MakeColor( 53, 227, 255)]; + + [m_colors addObject:MakeColor( 27, 254, 30)]; + [m_colors addObject:MakeColor( 30, 254, 103)]; + [m_colors addObject:MakeColor( 45, 254, 143)]; + [m_colors addObject:MakeColor( 38, 253, 182)]; + [m_colors addObject:MakeColor( 38, 253, 222)]; + [m_colors addObject:MakeColor( 42, 253, 252)]; + + [m_colors addObject:MakeColor(140, 48, 40)]; + [m_colors addObject:MakeColor(136, 51, 136)]; + [m_colors addObject:MakeColor(135, 52, 177)]; + [m_colors addObject:MakeColor(134, 52, 217)]; + [m_colors addObject:MakeColor(135, 56, 248)]; + [m_colors addObject:MakeColor(134, 53, 255)]; + + [m_colors addObject:MakeColor(125, 125, 38)]; + [m_colors addObject:MakeColor(124, 125, 125)]; + [m_colors addObject:MakeColor(122, 124, 166)]; + [m_colors addObject:MakeColor(123, 124, 207)]; + [m_colors addObject:MakeColor(123, 122, 247)]; + [m_colors addObject:MakeColor(124, 121, 255)]; + + [m_colors addObject:MakeColor(119, 160, 35)]; + [m_colors addObject:MakeColor(117, 160, 120)]; + [m_colors addObject:MakeColor(117, 160, 160)]; + [m_colors addObject:MakeColor(115, 159, 201)]; + [m_colors addObject:MakeColor(116, 158, 240)]; + [m_colors addObject:MakeColor(117, 157, 255)]; + + [m_colors addObject:MakeColor(113, 195, 39)]; + [m_colors addObject:MakeColor(110, 194, 114)]; + [m_colors addObject:MakeColor(111, 194, 154)]; + [m_colors addObject:MakeColor(108, 194, 194)]; + [m_colors addObject:MakeColor(109, 193, 234)]; + [m_colors addObject:MakeColor(108, 192, 255)]; + + [m_colors addObject:MakeColor(105, 228, 30)]; + [m_colors addObject:MakeColor(103, 228, 109)]; + [m_colors addObject:MakeColor(105, 228, 148)]; + [m_colors addObject:MakeColor(100, 227, 188)]; + [m_colors addObject:MakeColor( 99, 227, 227)]; + [m_colors addObject:MakeColor( 99, 226, 253)]; + + [m_colors addObject:MakeColor( 92, 253, 34)]; + [m_colors addObject:MakeColor( 96, 253, 103)]; + [m_colors addObject:MakeColor( 97, 253, 142)]; + [m_colors addObject:MakeColor( 88, 253, 182)]; + [m_colors addObject:MakeColor( 93, 253, 221)]; + [m_colors addObject:MakeColor( 88, 254, 251)]; + + [m_colors addObject:MakeColor(177, 53, 34)]; + [m_colors addObject:MakeColor(174, 54, 131)]; + [m_colors addObject:MakeColor(172, 55, 172)]; + [m_colors addObject:MakeColor(171, 57, 213)]; + [m_colors addObject:MakeColor(170, 55, 249)]; + [m_colors addObject:MakeColor(170, 57, 255)]; + + [m_colors addObject:MakeColor(165, 123, 37)]; + [m_colors addObject:MakeColor(163, 123, 123)]; + [m_colors addObject:MakeColor(162, 123, 164)]; + [m_colors addObject:MakeColor(161, 122, 205)]; + [m_colors addObject:MakeColor(161, 121, 241)]; + [m_colors addObject:MakeColor(161, 121, 255)]; + + [m_colors addObject:MakeColor(158, 159, 33)]; + [m_colors addObject:MakeColor(157, 158, 118)]; + [m_colors addObject:MakeColor(157, 158, 159)]; + [m_colors addObject:MakeColor(155, 157, 199)]; + [m_colors addObject:MakeColor(155, 157, 239)]; + [m_colors addObject:MakeColor(154, 156, 255)]; + + [m_colors addObject:MakeColor(152, 193, 40)]; + [m_colors addObject:MakeColor(151, 193, 113)]; + [m_colors addObject:MakeColor(150, 193, 153)]; + [m_colors addObject:MakeColor(150, 192, 193)]; + [m_colors addObject:MakeColor(148, 192, 232)]; + [m_colors addObject:MakeColor(149, 191, 253)]; + + [m_colors addObject:MakeColor(146, 227, 28)]; + [m_colors addObject:MakeColor(144, 227, 108)]; + [m_colors addObject:MakeColor(144, 227, 147)]; + [m_colors addObject:MakeColor(144, 227, 187)]; + [m_colors addObject:MakeColor(142, 226, 227)]; + [m_colors addObject:MakeColor(142, 225, 252)]; + + [m_colors addObject:MakeColor(138, 253, 36)]; + [m_colors addObject:MakeColor(137, 253, 102)]; + [m_colors addObject:MakeColor(136, 253, 141)]; + [m_colors addObject:MakeColor(138, 254, 181)]; + [m_colors addObject:MakeColor(135, 255, 220)]; + [m_colors addObject:MakeColor(133, 255, 250)]; + + [m_colors addObject:MakeColor(214, 57, 30)]; + [m_colors addObject:MakeColor(211, 59, 126)]; + [m_colors addObject:MakeColor(209, 57, 168)]; + [m_colors addObject:MakeColor(208, 55, 208)]; + [m_colors addObject:MakeColor(207, 58, 247)]; + [m_colors addObject:MakeColor(206, 61, 255)]; + + [m_colors addObject:MakeColor(204, 121, 32)]; + [m_colors addObject:MakeColor(202, 121, 121)]; + [m_colors addObject:MakeColor(201, 121, 161)]; + [m_colors addObject:MakeColor(200, 120, 202)]; + [m_colors addObject:MakeColor(200, 120, 241)]; + [m_colors addObject:MakeColor(198, 119, 255)]; + + [m_colors addObject:MakeColor(198, 157, 37)]; + [m_colors addObject:MakeColor(196, 157, 116)]; + [m_colors addObject:MakeColor(195, 156, 157)]; + [m_colors addObject:MakeColor(195, 156, 197)]; + [m_colors addObject:MakeColor(194, 155, 236)]; + [m_colors addObject:MakeColor(193, 155, 255)]; + + [m_colors addObject:MakeColor(191, 192, 36)]; + [m_colors addObject:MakeColor(190, 191, 112)]; + [m_colors addObject:MakeColor(189, 191, 152)]; + [m_colors addObject:MakeColor(189, 191, 191)]; + [m_colors addObject:MakeColor(188, 190, 230)]; + [m_colors addObject:MakeColor(187, 190, 253)]; + + [m_colors addObject:MakeColor(185, 226, 28)]; + [m_colors addObject:MakeColor(184, 226, 106)]; + [m_colors addObject:MakeColor(183, 225, 146)]; + [m_colors addObject:MakeColor(183, 225, 186)]; + [m_colors addObject:MakeColor(182, 225, 225)]; + [m_colors addObject:MakeColor(181, 224, 252)]; + + [m_colors addObject:MakeColor(178, 255, 35)]; + [m_colors addObject:MakeColor(178, 255, 101)]; + [m_colors addObject:MakeColor(177, 254, 141)]; + [m_colors addObject:MakeColor(176, 254, 180)]; + [m_colors addObject:MakeColor(176, 254, 220)]; + [m_colors addObject:MakeColor(175, 253, 249)]; + + [m_colors addObject:MakeColor(247, 56, 30)]; + [m_colors addObject:MakeColor(245, 57, 122)]; + [m_colors addObject:MakeColor(243, 59, 163)]; + [m_colors addObject:MakeColor(244, 60, 204)]; + [m_colors addObject:MakeColor(242, 59, 241)]; + [m_colors addObject:MakeColor(240, 55, 255)]; + + [m_colors addObject:MakeColor(241, 119, 36)]; + [m_colors addObject:MakeColor(240, 120, 118)]; + [m_colors addObject:MakeColor(238, 119, 158)]; + [m_colors addObject:MakeColor(237, 119, 199)]; + [m_colors addObject:MakeColor(237, 118, 238)]; + [m_colors addObject:MakeColor(236, 118, 255)]; + + [m_colors addObject:MakeColor(235, 154, 36)]; + [m_colors addObject:MakeColor(235, 154, 114)]; + [m_colors addObject:MakeColor(234, 154, 154)]; + [m_colors addObject:MakeColor(232, 154, 194)]; + [m_colors addObject:MakeColor(232, 153, 234)]; + [m_colors addObject:MakeColor(232, 153, 255)]; + + [m_colors addObject:MakeColor(230, 190, 30)]; + [m_colors addObject:MakeColor(229, 189, 110)]; + [m_colors addObject:MakeColor(228, 189, 150)]; + [m_colors addObject:MakeColor(227, 189, 190)]; + [m_colors addObject:MakeColor(227, 189, 229)]; + [m_colors addObject:MakeColor(226, 188, 255)]; + + [m_colors addObject:MakeColor(224, 224, 35)]; + [m_colors addObject:MakeColor(223, 224, 105)]; + [m_colors addObject:MakeColor(222, 224, 144)]; + [m_colors addObject:MakeColor(222, 223, 184)]; + [m_colors addObject:MakeColor(222, 223, 224)]; + [m_colors addObject:MakeColor(220, 223, 253)]; + + [m_colors addObject:MakeColor(217, 253, 28)]; + [m_colors addObject:MakeColor(217, 253, 99)]; + [m_colors addObject:MakeColor(216, 252, 139)]; + [m_colors addObject:MakeColor(216, 252, 179)]; + [m_colors addObject:MakeColor(215, 252, 218)]; + [m_colors addObject:MakeColor(215, 251, 250)]; + + [m_colors addObject:MakeColor(255, 61, 30)]; + [m_colors addObject:MakeColor(255, 60, 118)]; + [m_colors addObject:MakeColor(255, 58, 159)]; + [m_colors addObject:MakeColor(255, 56, 199)]; + [m_colors addObject:MakeColor(255, 55, 238)]; + [m_colors addObject:MakeColor(255, 59, 255)]; + + [m_colors addObject:MakeColor(255, 117, 29)]; + [m_colors addObject:MakeColor(255, 117, 115)]; + [m_colors addObject:MakeColor(255, 117, 155)]; + [m_colors addObject:MakeColor(255, 117, 195)]; + [m_colors addObject:MakeColor(255, 116, 235)]; + [m_colors addObject:MakeColor(254, 116, 255)]; + + [m_colors addObject:MakeColor(255, 152, 27)]; + [m_colors addObject:MakeColor(255, 152, 111)]; + [m_colors addObject:MakeColor(254, 152, 152)]; + [m_colors addObject:MakeColor(255, 152, 192)]; + [m_colors addObject:MakeColor(254, 151, 231)]; + [m_colors addObject:MakeColor(253, 151, 253)]; + + [m_colors addObject:MakeColor(255, 187, 33)]; + [m_colors addObject:MakeColor(253, 187, 107)]; + [m_colors addObject:MakeColor(252, 187, 148)]; + [m_colors addObject:MakeColor(253, 187, 187)]; + [m_colors addObject:MakeColor(254, 187, 227)]; + [m_colors addObject:MakeColor(252, 186, 252)]; + + [m_colors addObject:MakeColor(252, 222, 34)]; + [m_colors addObject:MakeColor(251, 222, 103)]; + [m_colors addObject:MakeColor(251, 222, 143)]; + [m_colors addObject:MakeColor(250, 222, 182)]; + [m_colors addObject:MakeColor(251, 221, 222)]; + [m_colors addObject:MakeColor(252, 221, 252)]; + + [m_colors addObject:MakeColor(251, 252, 15)]; + [m_colors addObject:MakeColor(251, 252, 97)]; + [m_colors addObject:MakeColor(249, 252, 137)]; + [m_colors addObject:MakeColor(247, 252, 177)]; + [m_colors addObject:MakeColor(247, 253, 217)]; + [m_colors addObject:MakeColor(254, 255, 255)]; + + // Grayscale + + [m_colors addObject:MakeColor( 52, 53, 53)]; + [m_colors addObject:MakeColor( 57, 58, 59)]; + [m_colors addObject:MakeColor( 66, 67, 67)]; + [m_colors addObject:MakeColor( 75, 76, 76)]; + [m_colors addObject:MakeColor( 83, 85, 85)]; + [m_colors addObject:MakeColor( 92, 93, 94)]; + + [m_colors addObject:MakeColor(101, 102, 102)]; + [m_colors addObject:MakeColor(109, 111, 111)]; + [m_colors addObject:MakeColor(118, 119, 119)]; + [m_colors addObject:MakeColor(126, 127, 128)]; + [m_colors addObject:MakeColor(134, 136, 136)]; + [m_colors addObject:MakeColor(143, 144, 145)]; + + [m_colors addObject:MakeColor(151, 152, 153)]; + [m_colors addObject:MakeColor(159, 161, 161)]; + [m_colors addObject:MakeColor(167, 169, 169)]; + [m_colors addObject:MakeColor(176, 177, 177)]; + [m_colors addObject:MakeColor(184, 185, 186)]; + [m_colors addObject:MakeColor(192, 193, 194)]; + + [m_colors addObject:MakeColor(200, 201, 202)]; + [m_colors addObject:MakeColor(208, 209, 210)]; + [m_colors addObject:MakeColor(216, 218, 218)]; + [m_colors addObject:MakeColor(224, 226, 226)]; + [m_colors addObject:MakeColor(232, 234, 234)]; + [m_colors addObject:MakeColor(240, 242, 242)]; + + // Color codes + + int index = 16; + + while (index < 256) + { + [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + + index++; + } + + #else + + // Standard xterm colors: + // + // These are the colors xterm shells use in xterm-256color mode. + // In this mode, the shell supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are generally configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // The last 24 color codes represent a grayscale. + // + // While the color codes are standardized, the actual RGB values for each color code is not. + // However most standard xterms follow a well known color chart, + // which can easily be calculated using the simple formula below. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + int index = 16; + + int r; // red + int g; // green + int b; // blue + + int ri; // r increment + int gi; // g increment + int bi; // b increment + + // Calculate xterm colors (using standard algorithm) + + int r = 0; + int g = 0; + int b = 0; + + for (ri = 0; ri < 6; ri++) + { + r = (ri == 0) ? 0 : 95 + (40 * (ri - 1)); + + for (gi = 0; gi < 6; gi++) + { + g = (gi == 0) ? 0 : 95 + (40 * (gi - 1)); + + for (bi = 0; bi < 6; bi++) + { + b = (bi == 0) ? 0 : 95 + (40 * (bi - 1)); + + [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [m_colors addObject:MakeColor(r, g, b)]; + + index++; + } + } + } + + // Calculate xterm grayscale (using standard algorithm) + + r = 8; + g = 8; + b = 8; + + while (index < 256) + { + [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [m_colors addObject:MakeColor(r, g, b)]; + + r += 10; + g += 10; + b += 10; + + index++; + } + + #endif + + codes_fg = [m_codes_fg copy]; + codes_bg = [m_codes_bg copy]; + colors = [m_colors copy]; + + NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)"); + NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)"); +} + ++ (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(OSColor *)color +{ + #if TARGET_OS_IPHONE + + // iOS + + if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) + { + [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + } + else + { + // The method getRed:green:blue:alpha: was only available starting iOS 5. + // So in iOS 4 and earlier, we have to jump through hoops. + + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + + unsigned char pixel[4]; + CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, kCGImageAlphaNoneSkipLast); + + CGContextSetFillColorWithColor(context, [color CGColor]); + CGContextFillRect(context, CGRectMake(0, 0, 1, 1)); + + if (rPtr) { *rPtr = pixel[0] / 255.0f; } + if (gPtr) { *gPtr = pixel[1] / 255.0f; } + if (bPtr) { *bPtr = pixel[2] / 255.0f; } + + CGContextRelease(context); + CGColorSpaceRelease(rgbColorSpace); + } + + #else + + // Mac OS X + + [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + + #endif +} + +/** + * Maps the given color to the closest available color supported by the shell. + * The shell may support 256 colors, or only 16. + * + * This method loops through the known supported color set, and calculates the closest color. + * The array index of that color, within the colors array, is then returned. + * This array index may also be used as the index within the codes_fg and codes_bg arrays. +**/ ++ (NSUInteger)codeIndexForColor:(OSColor *)inColor +{ + CGFloat inR, inG, inB; + [self getRed:&inR green:&inG blue:&inB fromColor:inColor]; + + NSUInteger bestIndex = 0; + CGFloat lowestDistance = 100.0f; + + NSUInteger i = 0; + for (OSColor *color in colors) + { + // Calculate Euclidean distance (lower value means closer to given color) + + CGFloat r, g, b; + [self getRed:&r green:&g blue:&b fromColor:color]; + + #if CGFLOAT_IS_DOUBLE + CGFloat distance = sqrt(pow(r-inR, 2.0) + pow(g-inG, 2.0) + pow(b-inB, 2.0)); + #else + CGFloat distance = sqrtf(powf(r-inR, 2.0f) + powf(g-inG, 2.0f) + powf(b-inB, 2.0f)); + #endif + + //NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f", + ((unsigned long)i, inR, inG, inB, r, g, b, distance); + + if (distance < lowestDistance) + { + bestIndex = i; + lowestDistance = distance; + + //NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex); + } + + i++; + } + + return bestIndex; +} + +/** + * The runtime sends initialize to each class in a program exactly one time just before the class, + * or any class that inherits from it, is sent its first message from within the program. (Thus the + * method may never be invoked if the class is not used.) The runtime sends the initialize message to + * classes in a thread-safe manner. Superclasses receive this message before their subclasses. + * + * This method may also be called directly (assumably by accident), hence the safety mechanism. +**/ ++ (void)initialize +{ + static BOOL initialized = NO; + if (!initialized) + { + initialized = YES; + + char *term = getenv("TERM"); + if (term) + { + if (strcasestr(term, "color") != NULL) + { + isaColorTTY = YES; + isaColor256TTY = (strcasestr(term, "256") != NULL); + + if (isaColor256TTY) + [self initialize_colors_256]; + else + [self initialize_colors_16]; + } + } + else + { + // Xcode does NOT natively support colors in the Xcode debugging console. + // You'll need to install the XcodeColors plugin to see colors in the Xcode console. + // + // PS - Please read the header file before diving into the source code. + + char *xcode_colors = getenv("XcodeColors"); + if (xcode_colors && (strcmp(xcode_colors, "YES") == 0)) + { + isaXcodeColorTTY = YES; + } + } + +// NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO")); +// NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO")); +// NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO")); + + sharedInstance = [[DDTTYLogger alloc] init]; + } +} + ++ (DDTTYLogger *)sharedInstance +{ + return sharedInstance; +} + +- (id)init +{ + if (sharedInstance != nil) + { + return nil; + } + + if ((self = [super init])) + { + calendar = [NSCalendar autoupdatingCurrentCalendar]; + + calendarUnitFlags = 0; + calendarUnitFlags |= NSYearCalendarUnit; + calendarUnitFlags |= NSMonthCalendarUnit; + calendarUnitFlags |= NSDayCalendarUnit; + calendarUnitFlags |= NSHourCalendarUnit; + calendarUnitFlags |= NSMinuteCalendarUnit; + calendarUnitFlags |= NSSecondCalendarUnit; + + // Initialze 'app' variable (char *) + + appName = [[NSProcessInfo processInfo] processName]; + + appLen = [appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + app = (char *)malloc(appLen + 1); + + [appName getCString:app maxLength:(appLen+1) encoding:NSUTF8StringEncoding]; + + // Initialize 'pid' variable (char *) + + processID = [NSString stringWithFormat:@"%i", (int)getpid()]; + + pidLen = [processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + pid = (char *)malloc(pidLen + 1); + + [processID getCString:pid maxLength:(pidLen+1) encoding:NSUTF8StringEncoding]; + + // Initialize color stuff + + colorsEnabled = NO; + colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8]; + colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8]; + } + return self; +} + +- (void)loadDefaultColorProfiles +{ + [self setForegroundColor:MakeColor(214, 57, 30) backgroundColor:nil forFlag:LOG_FLAG_ERROR]; + [self setForegroundColor:MakeColor(204, 121, 32) backgroundColor:nil forFlag:LOG_FLAG_WARN]; +} + +- (BOOL)colorsEnabled +{ + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block BOOL result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = colorsEnabled; + }); + }); + + return result; +} + +- (void)setColorsEnabled:(BOOL)newColorsEnabled +{ + dispatch_block_t block = ^{ @autoreleasepool { + + colorsEnabled = newColorsEnabled; + + if ([colorProfilesArray count] == 0) { + [self loadDefaultColorProfiles]; + } + }}; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); +} + +- (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColor forFlag:(int)mask +{ + [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:0]; +} + +- (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColor forFlag:(int)mask context:(int)ctxt +{ + dispatch_block_t block = ^{ @autoreleasepool { + + DDTTYLoggerColorProfile *newColorProfile = + [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:mask + context:ctxt]; + + //NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + NSUInteger i = 0; + for (DDTTYLoggerColorProfile *colorProfile in colorProfilesArray) + { + if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) + { + break; + } + + i++; + } + + if (i < [colorProfilesArray count]) + [colorProfilesArray replaceObjectAtIndex:i withObject:newColorProfile]; + else + [colorProfilesArray addObject:newColorProfile]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColor forTag:(id )tag +{ + NSAssert([(id )tag conformsToProtocol:@protocol(NSCopying)], @"Invalid tag"); + + dispatch_block_t block = ^{ @autoreleasepool { + + DDTTYLoggerColorProfile *newColorProfile = + [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:0 + context:0]; + + //NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + [colorProfilesDict setObject:newColorProfile forKey:tag]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)clearColorsForFlag:(int)mask +{ + [self clearColorsForFlag:mask context:0]; +} + +- (void)clearColorsForFlag:(int)mask context:(int)context +{ + dispatch_block_t block = ^{ @autoreleasepool { + + NSUInteger i = 0; + for (DDTTYLoggerColorProfile *colorProfile in colorProfilesArray) + { + if ((colorProfile->mask == mask) && (colorProfile->context == context)) + { + break; + } + + i++; + } + + if (i < [colorProfilesArray count]) + { + [colorProfilesArray removeObjectAtIndex:i]; + } + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)clearColorsForTag:(id )tag +{ + NSAssert([(id )tag conformsToProtocol:@protocol(NSCopying)], @"Invalid tag"); + + dispatch_block_t block = ^{ @autoreleasepool { + + [colorProfilesDict removeObjectForKey:tag]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllFlags +{ + dispatch_block_t block = ^{ @autoreleasepool { + + [colorProfilesArray removeAllObjects]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllTags +{ + dispatch_block_t block = ^{ @autoreleasepool { + + [colorProfilesDict removeAllObjects]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)clearAllColors +{ + dispatch_block_t block = ^{ @autoreleasepool { + + [colorProfilesArray removeAllObjects]; + [colorProfilesDict removeAllObjects]; + }}; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { + block(); + } + else + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } +} + +- (void)logMessage:(DDLogMessage *)logMessage +{ + NSString *logMsg = logMessage->logMsg; + BOOL isFormatted = NO; + + if (formatter) + { + logMsg = [formatter formatLogMessage:logMessage]; + isFormatted = logMsg != logMessage->logMsg; + } + + if (logMsg) + { + // Search for a color profile associated with the log message + + DDTTYLoggerColorProfile *colorProfile = nil; + + if (colorsEnabled) + { + if (logMessage->tag) + { + colorProfile = [colorProfilesDict objectForKey:logMessage->tag]; + } + if (colorProfile == nil) + { + for (DDTTYLoggerColorProfile *cp in colorProfilesArray) + { + if ((logMessage->logFlag & cp->mask) && (logMessage->logContext == cp->context)) + { + colorProfile = cp; + break; + } + } + } + } + + // Convert log message to C string. + // + // We use the stack instead of the heap for speed if possible. + // But we're extra cautious to avoid a stack overflow. + + NSUInteger msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + const BOOL useStack = msgLen < (1024 * 4); + + char msgStack[useStack ? (msgLen + 1) : 1]; // Analyzer doesn't like zero-size array, hence the 1 + char *msg = useStack ? msgStack : (char *)malloc(msgLen + 1); + + [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding]; + + // Write the log message to STDERR + + if (isFormatted) + { + // The log message has already been formatted. + + struct iovec v[5]; + + if (colorProfile) + { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[4].iov_base = colorProfile->resetCode; + v[4].iov_len = colorProfile->resetCodeLen; + } + else + { + v[0].iov_base = ""; + v[0].iov_len = 0; + + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[4].iov_base = ""; + v[4].iov_len = 0; + } + + v[2].iov_base = (char *)msg; + v[2].iov_len = msgLen; + + v[3].iov_base = "\n"; + v[3].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + + writev(STDERR_FILENO, v, 5); + } + else + { + // The log message is unformatted, so apply standard NSLog style formatting. + + int len; + + // Calculate timestamp. + // The technique below is faster than using NSDateFormatter. + + NSDateComponents *components = [calendar components:calendarUnitFlags fromDate:logMessage->timestamp]; + + NSTimeInterval epoch = [logMessage->timestamp timeIntervalSinceReferenceDate]; + int milliseconds = (int)((epoch - floor(epoch)) * 1000); + + char ts[24]; + len = snprintf(ts, 24, "%04ld-%02ld-%02ld %02ld:%02ld:%02ld:%03d", // yyyy-MM-dd HH:mm:ss:SSS + (long)components.year, + (long)components.month, + (long)components.day, + (long)components.hour, + (long)components.minute, + (long)components.second, milliseconds); + + size_t tsLen = MIN(24-1, len); + + // Calculate thread ID + // + // How many characters do we need for the thread id? + // logMessage->machThreadID is of type mach_port_t, which is an unsigned int. + // + // 1 hex char = 4 bits + // 8 hex chars for 32 bit, plus ending '\0' = 9 + + char tid[9]; + len = snprintf(tid, 9, "%x", logMessage->machThreadID); + + size_t tidLen = MIN(9-1, len); + + // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg + + struct iovec v[13]; + + if (colorProfile) + { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[12].iov_base = colorProfile->resetCode; + v[12].iov_len = colorProfile->resetCodeLen; + } + else + { + v[0].iov_base = ""; + v[0].iov_len = 0; + + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[12].iov_base = ""; + v[12].iov_len = 0; + } + + v[2].iov_base = ts; + v[2].iov_len = tsLen; + + v[3].iov_base = " "; + v[3].iov_len = 1; + + v[4].iov_base = app; + v[4].iov_len = appLen; + + v[5].iov_base = "["; + v[5].iov_len = 1; + + v[6].iov_base = pid; + v[6].iov_len = pidLen; + + v[7].iov_base = ":"; + v[7].iov_len = 1; + + v[8].iov_base = tid; + v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think + + v[9].iov_base = "] "; + v[9].iov_len = 2; + + v[10].iov_base = (char *)msg; + v[10].iov_len = msgLen; + + v[11].iov_base = "\n"; + v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + + writev(STDERR_FILENO, v, 13); + } + + if (!useStack) { + free(msg); + } + } +} + +- (NSString *)loggerName +{ + return @"cocoa.lumberjack.ttyLogger"; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDTTYLoggerColorProfile + +- (id)initWithForegroundColor:(OSColor *)fgColor backgroundColor:(OSColor *)bgColor flag:(int)aMask context:(int)ctxt +{ + if ((self = [super init])) + { + mask = aMask; + context = ctxt; + + CGFloat r, g, b; + + if (fgColor) + { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor]; + + fg_r = (uint8_t)(r * 255.0f); + fg_g = (uint8_t)(g * 255.0f); + fg_b = (uint8_t)(b * 255.0f); + } + if (bgColor) + { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor]; + + bg_r = (uint8_t)(r * 255.0f); + bg_g = (uint8_t)(g * 255.0f); + bg_b = (uint8_t)(b * 255.0f); + } + + if (fgColor && isaColorTTY) + { + // Map foreground color to closest available shell color + + fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor]; + fgCodeRaw = [codes_fg objectAtIndex:fgCodeIndex]; + + NSString *escapeSeq = @"\033["; + + NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSUInteger len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + [escapeSeq getCString:(fgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; + [fgCodeRaw getCString:(fgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + + fgCodeLen = len1+len2; + } + else if (fgColor && isaXcodeColorTTY) + { + // Convert foreground color to color code sequence + + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + + int result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg_r, fg_g, fg_b); + fgCodeLen = MIN(result, (24-1)); + } + else + { + // No foreground color or no color support + + fgCode[0] = '\0'; + fgCodeLen = 0; + } + + if (bgColor && isaColorTTY) + { + // Map background color to closest available shell color + + bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor]; + bgCodeRaw = [codes_bg objectAtIndex:bgCodeIndex]; + + NSString *escapeSeq = @"\033["; + + NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSUInteger len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + [escapeSeq getCString:(bgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; + [bgCodeRaw getCString:(bgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + + bgCodeLen = len1+len2; + } + else if (bgColor && isaXcodeColorTTY) + { + // Convert background color to color code sequence + + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + + int result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg_r, bg_g, bg_b); + bgCodeLen = MIN(result, (24-1)); + } + else + { + // No background color or no color support + + bgCode[0] = '\0'; + bgCodeLen = 0; + } + + if (isaColorTTY) + { + resetCodeLen = snprintf(resetCode, 8, "\033[0m"); + } + else if (isaXcodeColorTTY) + { + resetCodeLen = snprintf(resetCode, 8, XCODE_COLORS_RESET); + } + else + { + resetCode[0] = '\0'; + resetCodeLen = 0; + } + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat: + @"", + self, mask, context, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, fgCodeRaw, bgCodeRaw]; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h new file mode 100755 index 00000000..794ca0b6 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h @@ -0,0 +1,65 @@ +#import +#import "DDLog.h" + +@class ContextFilterLogFormatter; + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" page. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides a log formatter that filters log statements from a logging context not on the whitelist. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters + * + * You can learn more about logging context's here: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. +**/ +@interface ContextWhitelistFilterLogFormatter : NSObject + +- (id)init; + +- (void)addToWhitelist:(int)loggingContext; +- (void)removeFromWhitelist:(int)loggingContext; + +- (NSArray *)whitelist; + +- (BOOL)isOnWhitelist:(int)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This class provides a log formatter that filters log statements from a logging context on the blacklist. +**/ +@interface ContextBlacklistFilterLogFormatter : NSObject + +- (id)init; + +- (void)addToBlacklist:(int)loggingContext; +- (void)removeFromBlacklist:(int)loggingContext; + +- (NSArray *)blacklist; + +- (BOOL)isOnBlacklist:(int)loggingContext; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m new file mode 100755 index 00000000..0af541f3 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m @@ -0,0 +1,191 @@ +#import "ContextFilterLogFormatter.h" +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface LoggingContextSet : NSObject + +- (void)addToSet:(int)loggingContext; +- (void)removeFromSet:(int)loggingContext; + +- (NSArray *)currentSet; + +- (BOOL)isInSet:(int)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation ContextWhitelistFilterLogFormatter +{ + LoggingContextSet *contextSet; +} + +- (id)init +{ + if ((self = [super init])) + { + contextSet = [[LoggingContextSet alloc] init]; + } + return self; +} + + +- (void)addToWhitelist:(int)loggingContext +{ + [contextSet addToSet:loggingContext]; +} + +- (void)removeFromWhitelist:(int)loggingContext +{ + [contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)whitelist +{ + return [contextSet currentSet]; +} + +- (BOOL)isOnWhitelist:(int)loggingContext +{ + return [contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage +{ + if ([self isOnWhitelist:logMessage->logContext]) + return logMessage->logMsg; + else + return nil; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation ContextBlacklistFilterLogFormatter +{ + LoggingContextSet *contextSet; +} + +- (id)init +{ + if ((self = [super init])) + { + contextSet = [[LoggingContextSet alloc] init]; + } + return self; +} + + +- (void)addToBlacklist:(int)loggingContext +{ + [contextSet addToSet:loggingContext]; +} + +- (void)removeFromBlacklist:(int)loggingContext +{ + [contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)blacklist +{ + return [contextSet currentSet]; +} + +- (BOOL)isOnBlacklist:(int)loggingContext +{ + return [contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage +{ + if ([self isOnBlacklist:logMessage->logContext]) + return nil; + else + return logMessage->logMsg; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation LoggingContextSet +{ + OSSpinLock lock; + NSMutableSet *set; +} + +- (id)init +{ + if ((self = [super init])) + { + set = [[NSMutableSet alloc] init]; + } + return self; +} + + +- (void)addToSet:(int)loggingContext +{ + OSSpinLockLock(&lock); + { + [set addObject:@(loggingContext)]; + } + OSSpinLockUnlock(&lock); +} + +- (void)removeFromSet:(int)loggingContext +{ + OSSpinLockLock(&lock); + { + [set removeObject:@(loggingContext)]; + } + OSSpinLockUnlock(&lock); +} + +- (NSArray *)currentSet +{ + NSArray *result = nil; + + OSSpinLockLock(&lock); + { + result = [set allObjects]; + } + OSSpinLockUnlock(&lock); + + return result; +} + +- (BOOL)isInSet:(int)loggingContext +{ + BOOL result = NO; + + OSSpinLockLock(&lock); + { + result = [set containsObject:@(loggingContext)]; + } + OSSpinLockUnlock(&lock); + + return result; +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h new file mode 100755 index 00000000..e4a6656f --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h @@ -0,0 +1,116 @@ +#import +#import +#import "DDLog.h" + + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" page. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * + * + * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters + * + * A typical NSLog (or DDTTYLogger) prints detailed info as [:]. + * For example: + * + * 2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here + * + * Where: + * - 19928 = process id + * - 5207 = thread id (mach_thread_id printed in hex) + * + * When using grand central dispatch (GCD), this information is less useful. + * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool. + * For example: + * + * 2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue + * + * This formatter allows you to replace the standard [box:info] with the dispatch_queue name. + * For example: + * + * 2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue + * + * If the dispatch_queue doesn't have a set name, then it falls back to the thread name. + * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal). + * + * Note: If manually creating your own background threads (via NSThread/alloc/init or NSThread/detachNeThread), + * you can use [[NSThread currentThread] setName:(NSString *)]. +**/ +@interface DispatchQueueLogFormatter : NSObject { +@protected + + NSString *dateFormatString; +} + +/** + * Standard init method. + * Configure using properties as desired. +**/ +- (id)init; + +/** + * The minQueueLength restricts the minimum size of the [detail box]. + * If the minQueueLength is set to 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the minQueueLength is 0: [diskIO] + * If the minQueueLength is 4: [diskIO] + * If the minQueueLength is 5: [diskIO] + * If the minQueueLength is 6: [diskIO] + * If the minQueueLength is 7: [diskIO ] + * If the minQueueLength is 8: [diskIO ] + * + * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. +**/ +@property (assign) NSUInteger minQueueLength; + +/** + * The maxQueueLength restricts the number of characters that will be inside the [detail box]. + * If the maxQueueLength is 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the maxQueueLength is 0: [diskIO] + * If the maxQueueLength is 4: [disk] + * If the maxQueueLength is 5: [diskI] + * If the maxQueueLength is 6: [diskIO] + * If the maxQueueLength is 7: [diskIO] + * If the maxQueueLength is 8: [diskIO] + * + * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. +**/ +@property (assign) NSUInteger maxQueueLength; + +/** + * Sometimes queue labels have long names like "com.apple.main-queue", + * but you'd prefer something shorter like simply "main". + * + * This method allows you to set such preferred replacements. + * The above example is set by default. + * + * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter. +**/ +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel; +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel; + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m new file mode 100755 index 00000000..76da64c9 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m @@ -0,0 +1,251 @@ +#import "DispatchQueueLogFormatter.h" +#import + +/** + * Welcome to Cocoa Lumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/robbiehanson/CocoaLumberjack + * + * If you're new to the project you may wish to read the "Getting Started" wiki. + * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted +**/ + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + + +@implementation DispatchQueueLogFormatter +{ + int32_t atomicLoggerCount; + NSDateFormatter *threadUnsafeDateFormatter; // Use [self stringFromDate] + + OSSpinLock lock; + + NSUInteger _minQueueLength; // _prefix == Only access via atomic property + NSUInteger _maxQueueLength; // _prefix == Only access via atomic property + NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock +} + +- (id)init +{ + if ((self = [super init])) + { + dateFormatString = @"yyyy-MM-dd HH:mm:ss:SSS"; + + atomicLoggerCount = 0; + threadUnsafeDateFormatter = nil; + + _minQueueLength = 0; + _maxQueueLength = 0; + _replacements = [[NSMutableDictionary alloc] init]; + + // Set default replacements: + + [_replacements setObject:@"main" forKey:@"com.apple.main-thread"]; + } + return self; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize minQueueLength = _minQueueLength; +@synthesize maxQueueLength = _maxQueueLength; + +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel +{ + NSString *result = nil; + + OSSpinLockLock(&lock); + { + result = [_replacements objectForKey:longLabel]; + } + OSSpinLockUnlock(&lock); + + return result; +} + +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel +{ + OSSpinLockLock(&lock); + { + if (shortLabel) + [_replacements setObject:shortLabel forKey:longLabel]; + else + [_replacements removeObjectForKey:longLabel]; + } + OSSpinLockUnlock(&lock); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogFormatter +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSString *)stringFromDate:(NSDate *)date +{ + int32_t loggerCount = OSAtomicAdd32(0, &atomicLoggerCount); + + if (loggerCount <= 1) + { + // Single-threaded mode. + + if (threadUnsafeDateFormatter == nil) + { + threadUnsafeDateFormatter = [[NSDateFormatter alloc] init]; + [threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [threadUnsafeDateFormatter setDateFormat:dateFormatString]; + } + + return [threadUnsafeDateFormatter stringFromDate:date]; + } + else + { + // Multi-threaded mode. + // NSDateFormatter is NOT thread-safe. + + NSString *key = @"DispatchQueueLogFormatter_NSDateFormatter"; + + NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; + NSDateFormatter *dateFormatter = [threadDictionary objectForKey:key]; + + if (dateFormatter == nil) + { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [dateFormatter setDateFormat:dateFormatString]; + + [threadDictionary setObject:dateFormatter forKey:key]; + } + + return [dateFormatter stringFromDate:date]; + } +} + +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage +{ + // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue + + NSUInteger minQueueLength = self.minQueueLength; + NSUInteger maxQueueLength = self.maxQueueLength; + + // Get the name of the queue, thread, or machID (whichever we are to use). + + NSString *queueThreadLabel = nil; + + BOOL useQueueLabel = YES; + BOOL useThreadName = NO; + + if (logMessage->queueLabel) + { + // If you manually create a thread, it's dispatch_queue will have one of the thread names below. + // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID. + + char *names[] = { "com.apple.root.low-priority", + "com.apple.root.default-priority", + "com.apple.root.high-priority", + "com.apple.root.low-overcommit-priority", + "com.apple.root.default-overcommit-priority", + "com.apple.root.high-overcommit-priority" }; + + int length = sizeof(names) / sizeof(char *); + + int i; + for (i = 0; i < length; i++) + { + if (strcmp(logMessage->queueLabel, names[i]) == 0) + { + useQueueLabel = NO; + useThreadName = [logMessage->threadName length] > 0; + break; + } + } + } + else + { + useQueueLabel = NO; + useThreadName = [logMessage->threadName length] > 0; + } + + if (useQueueLabel || useThreadName) + { + NSString *fullLabel; + NSString *abrvLabel; + + if (useQueueLabel) + fullLabel = @(logMessage->queueLabel); + else + fullLabel = logMessage->threadName; + + OSSpinLockLock(&lock); + { + abrvLabel = [_replacements objectForKey:fullLabel]; + } + OSSpinLockUnlock(&lock); + + if (abrvLabel) + queueThreadLabel = abrvLabel; + else + queueThreadLabel = fullLabel; + } + else + { + queueThreadLabel = [NSString stringWithFormat:@"%x", logMessage->machThreadID]; + } + + // Now use the thread label in the output + + NSUInteger labelLength = [queueThreadLabel length]; + + // labelLength > maxQueueLength : truncate + // labelLength < minQueueLength : padding + // : exact + + if ((maxQueueLength > 0) && (labelLength > maxQueueLength)) + { + // Truncate + + return [queueThreadLabel substringToIndex:maxQueueLength]; + } + else if (labelLength < minQueueLength) + { + // Padding + + NSUInteger numSpaces = minQueueLength - labelLength; + + char spaces[numSpaces + 1]; + memset(spaces, ' ', numSpaces); + spaces[numSpaces] = '\0'; + + return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces]; + } + else + { + // Exact + + return queueThreadLabel; + } +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage +{ + NSString *timestamp = [self stringFromDate:(logMessage->timestamp)]; + NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage]; + + return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->logMsg]; +} + +- (void)didAddToLogger:(id )logger +{ + OSAtomicIncrement32(&atomicLoggerCount); +} + +- (void)willRemoveFromLogger:(id )logger +{ + OSAtomicDecrement32(&atomicLoggerCount); +} + +@end diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/index.css b/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/index.css new file mode 100755 index 00000000..60ce0620 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/index.css @@ -0,0 +1,101 @@ +img { + width: 100%; + vertical-align: top; +} +html, +body { + width: 100%; + height: 100%; +} +.container { + margin: 0 auto; + height: 100%; + background: #f5f5f5; +} +.header { + height: 80px; + text-align: center; + background: #2f2f2f; +} +.header img { + margin: 0 auto; + height: 100%; + width: 900px; +} +.section { + padding: 20px 24px; + width: 800px; + margin: 0 auto; +} +.section .uploadBtn { + margin: 0 auto 20px; + width: 40%; + cursor: pointer; +} +.section #upload, +.section #submit { + display: none; +} +.section .songList { + height: 600px; + background: #fff; +} +.section .songList .title { + padding: 16px 15px; + background: #e7e5e5; +} +.section .songList .title span { + border-left: 5px solid #FF894F; + padding-left: 10px; + line-height: 16px; +} +.section .songList ul li { + height: 60px; + display: flex; + border-bottom: 1px solid #f5f5f5; + align-items: center; + padding: 0 10px; +} +.section .songList ul li .attention { + flex: 0 0 20px; + height: 20px; + width: 20px; + background: url('../images/attention.png') no-repeat; + background-size: 100% 100%; + margin-right: 8px; +} +.section .songList ul li .progress { + flex: 0 0 180px; + width: 180px; + height: 14px; + background: #e7e5e5; + border-radius: 7px; + overflow: hidden; +} +.section .songList ul li .progress .bar { + width: 0; + background: #ffb400; + height: 100%; + transition: all 0.2s; +} +.section .songList ul li .download { + flex: 0 0 20px; + height: 20px; + width: 20px; + background: url('../images/download.png') no-repeat; + background-size: 100% 100%; + margin-right: 8px; +} +.section .songList ul li .delete { + flex: 0 0 20px; + height: 20px; + width: 20px; + background: url('../images/delete.png') no-repeat; + background-size: 100% 100%; +} +.section .songList ul li .songName { + flex: 4; +} +.section .songList ul li .songSize { + flex: 1; +} diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/reset.css b/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/reset.css new file mode 100755 index 00000000..98f9bed6 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Web/css/reset.css @@ -0,0 +1,90 @@ +/** + * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) + * http://cssreset.com + */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, +menu, nav, output, ruby, section, summary, +time, mark, audio, video, input { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font-weight: normal; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* custom */ +a { + color: #7e8c8d; + text-decoration: none; + -webkit-backface-visibility: hidden; +} + +li { + list-style: none; +} + +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-track-piece { + background-color: rgba(0, 0, 0, 0.2); + -webkit-border-radius: 6px; +} + +::-webkit-scrollbar-thumb:vertical { + height: 5px; + background-color: rgba(125, 125, 125, 0.7); + -webkit-border-radius: 6px; +} + +::-webkit-scrollbar-thumb:horizontal { + width: 5px; + background-color: rgba(125, 125, 125, 0.7); + -webkit-border-radius: 6px; +} + +html, body { + width: 100%; +} + +body { + -webkit-text-size-adjust: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} \ No newline at end of file diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/attention.png b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/attention.png new file mode 100755 index 00000000..f4286442 Binary files /dev/null and b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/attention.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/button.png b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/button.png new file mode 100755 index 00000000..db656a95 Binary files /dev/null and b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/button.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/delete.png b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/delete.png new file mode 100755 index 00000000..4b39adda Binary files /dev/null and b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/delete.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/download.png b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/download.png new file mode 100755 index 00000000..a7719717 Binary files /dev/null and b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/download.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/header.png b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/header.png new file mode 100755 index 00000000..7978b136 Binary files /dev/null and b/xplan-ios/Base/Tool/CocoaHttpServer/Web/images/header.png differ diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/index.html b/xplan-ios/Base/Tool/CocoaHttpServer/Web/index.html new file mode 100755 index 00000000..747207df --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Web/index.html @@ -0,0 +1,54 @@ + + + + + + + 一键上传歌曲 + + + + +
    +
    + +
    +
    +
    +
    + +
    + + +
    +
    +
    歌曲列表
    +
      + +
    +
      + +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/index.js b/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/index.js new file mode 100755 index 00000000..8e090f1c --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/index.js @@ -0,0 +1,49 @@ +$(function(){ + $('.uploadBtn').on('click',function(){ + $('#upload').click(); + }) + + $('#upload').fileupload({ + dropZone: $(document), + pasteZone: null, + autoUpload: true, + sequentialUploads: true, + + dataType: 'json', + url:'./upload.html', + type: 'POST', + add: function(e,data){ + console.log(data,'添加文件时执行的操作'); + var $li = '
  • '+data.files[0].name+'

  • '; + data.context = $($li).appendTo('#uploadingList'); + console.log(data.context); + var jqXHR = data.submit(); + + }, + progress: function (e, data) {//上传进度 + var progress = parseInt(data.loaded / data.total * 100, 10); + console.log(progress,data); + // $(".progress .bar").css("width", progress + "%"); + data.context.find(".bar").css("width", progress + "%"); + }, + done:function(e,data){ + console.log('上传完毕'); + }, + always: function(e, data) { + // 每次传输后(包括成功,失败,被拒执行的回调) + data.context.remove(); + var $li = $('
  • '); + $li.html('

    '+data.files[0].name+'

    '+formatFileSize(data.files[0].size)+'

    '); + $('#uploadingDone').append($li); + } + }) +}) +function formatFileSize(bytes) { + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; +} \ No newline at end of file diff --git a/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery-3.2.1.min.js b/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery-3.2.1.min.js new file mode 100755 index 00000000..764485c0 --- /dev/null +++ b/xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery-3.2.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), +a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), +null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("