Browse Source

update project

Marino Faggiana 6 years ago
parent
commit
6e2561ed81
100 changed files with 11332 additions and 0 deletions
  1. 1 0
      Cartfile
  2. 1 0
      Cartfile.resolved
  3. 17 0
      Carthage/Checkouts/KTVHTTPCache/.gitignore
  4. 26 0
      Carthage/Checkouts/KTVHTTPCache/Framework/Info.plist
  5. 0 0
      Carthage/Checkouts/KTVHTTPCache/Framework/KTVHTTPCache.h
  6. 0 0
      Carthage/Checkouts/KTVHTTPCache/Framework/module.modulemap
  7. 16 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache.podspec
  8. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCCommon.h
  9. 36 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.h
  10. 80 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.m
  11. 159 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCLog.h
  12. 130 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCLog.m
  13. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.h
  14. 170 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.m
  15. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.h
  16. 44 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.m
  17. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.h
  18. 29 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.m
  19. 16 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.h
  20. 41 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.m
  21. 46 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.h
  22. 187 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.m
  23. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.h
  24. 140 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.m
  25. 50 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataNetworkSource.h
  26. 344 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataNetworkSource.m
  27. 27 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataPrivate.h
  28. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.h
  29. 301 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.m
  30. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.h
  31. 53 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.m
  32. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.h
  33. 74 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.m
  34. 47 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceManager.h
  35. 267 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceManager.m
  36. 25 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceProtocol.h
  37. 39 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceQueue.h
  38. 152 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceQueue.m
  39. 56 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.h
  40. 89 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.m
  41. 70 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnit.h
  42. 410 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnit.m
  43. 28 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.h
  44. 110 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.m
  45. 31 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitPool.h
  46. 306 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitPool.m
  47. 27 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.h
  48. 108 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.m
  49. 53 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDownload/KTVHCDownload.h
  50. 306 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDownload/KTVHCDownload.m
  51. 16 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.h
  52. 63 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.m
  53. 14 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPHeader.h
  54. 24 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPPingResponse.h
  55. 98 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPPingResponse.m
  56. 23 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPRequest.h
  57. 31 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPRequest.m
  58. 22 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.h
  59. 132 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.m
  60. 25 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.h
  61. 166 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.m
  62. 32 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPURL.h
  63. 116 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPURL.m
  64. 34 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTools.h
  65. 184 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTools.m
  66. 27 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTools.h
  67. 145 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTools.m
  68. 0 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHTTPCacheImp.h
  69. 224 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHTTPCacheImp.m
  70. 27 0
      Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/KTVHTTPCache.h
  71. 21 0
      Carthage/Checkouts/KTVHTTPCache/LICENSE
  72. 24 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/Info.plist
  73. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/KTVCocoaHTTPServer.h
  74. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/module.modulemap
  75. 16 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer.podspec
  76. 14 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDData.h
  77. 158 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDData.m
  78. 12 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDNumber.h
  79. 88 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDNumber.m
  80. 56 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDRange.h
  81. 104 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDRange.m
  82. 45 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPAuthenticationRequest.h
  83. 195 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPAuthenticationRequest.m
  84. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPConnection.h
  85. 2708 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPConnection.m
  86. 46 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPLogging.h
  87. 18 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPLogging.m
  88. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPMessage.h
  89. 113 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPMessage.m
  90. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPResponse.h
  91. 0 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPServer.h
  92. 772 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPServer.m
  93. 65 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartFormDataParser.h
  94. 529 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartFormDataParser.m
  95. 33 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartMessageHeader.h
  96. 86 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartMessageHeader.m
  97. 23 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartMessageHeaderField.h
  98. 211 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartMessageHeaderField.m
  99. 75 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Responses/HTTPAsyncFileResponse.h
  100. 405 0
      Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Responses/HTTPAsyncFileResponse.m

+ 1 - 0
Cartfile

@@ -6,3 +6,4 @@ github "MortimerGoro/MGSwipeTableCell"
 github "dzenbot/DZNEmptyDataSet"
 github "ealeksandrov/EAIntroView"
 github "calimarkus/JDStatusBarNotification"
+github "ChangbaDevs/KTVHTTPCache" ~> 1.1.7

+ 1 - 0
Cartfile.resolved

@@ -1,3 +1,4 @@
+github "ChangbaDevs/KTVHTTPCache" "1.1.7"
 github "MortimerGoro/MGSwipeTableCell" "1.6.7"
 github "calimarkus/JDStatusBarNotification" "1.6.0"
 github "danielsaidi/Sheeeeeeeeet" "1.0.1"

+ 17 - 0
Carthage/Checkouts/KTVHTTPCache/.gitignore

@@ -0,0 +1,17 @@
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+.DS_Store

+ 26 - 0
Carthage/Checkouts/KTVHTTPCache/Framework/Info.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.1.7</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHTTPCache.h → Carthage/Checkouts/KTVHTTPCache/Framework/KTVHTTPCache.h


+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Modules/module.modulemap → Carthage/Checkouts/KTVHTTPCache/Framework/module.modulemap


+ 16 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache.podspec

@@ -0,0 +1,16 @@
+Pod::Spec.new do |s|
+  s.name                = "KTVHTTPCache"
+  s.version             = "1.1.7"
+  s.summary             = "A media cache framework from Changba iOS Team."
+  s.homepage            = "https://github.com/ChangbaDevs/KTVHTTPCache"
+  s.license             = { :type => "MIT", :file => "LICENSE" }
+  s.author              = { "Single" => "libobjc@gmail.com" }
+  s.social_media_url    = "https://weibo.com/3118550737"
+  s.platform            = :ios, "8.0"
+  s.source              = { :git => "https://github.com/ChangbaDevs/KTVHTTPCache.git", :tag => "#{s.version}" }
+  s.source_files        = "KTVHTTPCache", "KTVHTTPCache/**/*.{h,m}"
+  s.public_header_files = "KTVHTTPCache/**/*.h"
+  s.frameworks          = "UIKit", "Foundation"
+  s.requires_arc        = true
+  s.dependency 'KTVCocoaHTTPServer'
+end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCCommon.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCCommon.h


+ 36 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.h

@@ -0,0 +1,36 @@
+//
+//  KTVHCError.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/17.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NS_ENUM(NSInteger, KTVHCErrorCode)
+{
+    KTVHCErrorCodeResponseUnavailable  = -192700,
+    KTVHCErrorCodeUnsupportContentType = -192701,
+    KTVHCErrorCodeNotEnoughDiskSpace   = -192702,
+    KTVHCErrorCodeException            = -192703,
+};
+
+@interface KTVHCError : NSObject
+
++ (NSError *)errorForResponseUnavailable:(NSURL *)URL
+                                 request:(NSURLRequest *)request
+                                response:(NSURLResponse *)response;
+
++ (NSError *)errorForUnsupportContentType:(NSURL *)URL
+                                  request:(NSURLRequest *)request
+                                 response:(NSURLResponse *)response;
+
++ (NSError *)errorForNotEnoughDiskSpace:(long long)totlaContentLength
+                                request:(long long)currentContentLength
+                       totalCacheLength:(long long)totalCacheLength
+                         maxCacheLength:(long long)maxCacheLength;
+
++ (NSError *)errorForException:(NSException *)exception;
+
+@end

+ 80 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.m

@@ -0,0 +1,80 @@
+//
+//  KTVHCError.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/17.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCError.h"
+
+NSString * const KTVHCErrorUserInfoKeyURL      = @"KTVHCErrorUserInfoKeyURL";
+NSString * const KTVHCErrorUserInfoKeyRequest  = @"KTVHCErrorUserInfoKeyRequest";
+NSString * const KTVHCErrorUserInfoKeyResponse = @"KTVHCErrorUserInfoKeyResponse";
+
+@implementation KTVHCError
+
++ (NSError *)errorForResponseUnavailable:(NSURL *)URL
+                                 request:(NSURLRequest *)request
+                                response:(NSURLResponse *)response
+{
+    NSMutableDictionary * userInfo = [NSMutableDictionary dictionary];
+    if (URL) {
+        [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL];
+    }
+    if (request) {
+        [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest];
+    }
+    if (response) {
+        [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse];
+    }
+    NSError * error = [NSError errorWithDomain:@"KTVHTTPCache error"
+                                          code:KTVHCErrorCodeResponseUnavailable
+                                      userInfo:userInfo];
+    return error;
+}
+
++ (NSError *)errorForUnsupportContentType:(NSURL *)URL
+                                  request:(NSURLRequest *)request
+                                 response:(NSURLResponse *)response
+{
+    NSMutableDictionary * userInfo = [NSMutableDictionary dictionary];
+    if (URL) {
+        [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL];
+    }
+    if (request) {
+        [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest];
+    }
+    if (response) {
+        [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse];
+    }
+    NSError * error = [NSError errorWithDomain:@"KTVHTTPCache error"
+                                          code:KTVHCErrorCodeUnsupportContentType
+                                      userInfo:userInfo];
+    return error;
+}
+
++ (NSError *)errorForNotEnoughDiskSpace:(long long)totlaContentLength
+                                request:(long long)currentContentLength
+                       totalCacheLength:(long long)totalCacheLength
+                         maxCacheLength:(long long)maxCacheLength
+{
+    NSError * error = [NSError errorWithDomain:@"KTVHTTPCache error"
+                                          code:KTVHCErrorCodeNotEnoughDiskSpace
+                                      userInfo:@{@"totlaContentLength" : @(totlaContentLength),
+                                                 @"currentContentLength" : @(currentContentLength),
+                                                 @"totalCacheLength" : @(totalCacheLength),
+                                                 @"maxCacheLength" : @(maxCacheLength)}];
+    return error;
+}
+
++ (NSError *)errorForException:(NSException *)exception
+{
+    NSError * error = [NSError errorWithDomain:@"KTVHTTPCache error"
+                                          code:KTVHCErrorCodeException
+                                      userInfo:exception.userInfo];
+    return error;
+}
+
+
+@end

+ 159 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCLog.h

@@ -0,0 +1,159 @@
+//
+//  KTVHCLog.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/17.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+/**
+ *  Log Enable Config
+ */
+#define KTVHCLogEnable(target, console_log_enable, record_log_enable)               \
+static BOOL const KTVHCLog_##target##_ConsoleLogEnable = console_log_enable;        \
+static BOOL const KTVHCLog_##target##_RecordLogEnable = record_log_enable;
+
+#define KTVHCLogEnableValueConsoleLog(target)       KTVHCLog_##target##_ConsoleLogEnable
+#define KTVHCLogEnableValueRecordLog(target)        KTVHCLog_##target##_RecordLogEnable
+
+/**
+ *  Common
+ */
+KTVHCLogEnable(Common,            YES, YES)
+
+/**
+ *  HTTP Server
+ */
+KTVHCLogEnable(HTTPServer,        YES, YES)
+KTVHCLogEnable(HTTPConnection,    YES, YES)
+KTVHCLogEnable(HTTPRequest,       YES, YES)
+KTVHCLogEnable(HTTPResponse,      YES, YES)
+KTVHCLogEnable(HTTPResponsePing,  YES, YES)
+KTVHCLogEnable(HTTPURL,           YES, YES)
+
+/**
+ *  Data Storage
+ */
+KTVHCLogEnable(DataStorage,       YES, YES)
+KTVHCLogEnable(DataRequest,       YES, YES)
+KTVHCLogEnable(DataResponse,      YES, YES)
+KTVHCLogEnable(DataReader,        YES, YES)
+KTVHCLogEnable(DataLoader,        YES, YES)
+
+KTVHCLogEnable(DataUnit,          YES, YES)
+KTVHCLogEnable(DataUnitItem,      YES, YES)
+KTVHCLogEnable(DataUnitPool,      YES, YES)
+KTVHCLogEnable(DataUnitQueue,     YES, YES)
+
+KTVHCLogEnable(DataSourceManager, YES, YES)
+KTVHCLogEnable(DataSourceQueue,   YES, YES)
+KTVHCLogEnable(DataFileSource,    YES, YES)
+KTVHCLogEnable(DataNetworkSource, YES, YES)
+
+/**
+ *  Download
+ */
+KTVHCLogEnable(Download,          YES, YES)
+
+/**
+ *  Alloc & Dealloc
+ */
+KTVHCLogEnable(Alloc,             YES, YES)
+KTVHCLogEnable(Dealloc,           YES, YES)
+
+/**
+ *  Log
+ */
+#define KTVHCLogging(target, console_log_enable, record_log_enable, ...)            \
+if (([KTVHCLog log].consoleLogEnable && console_log_enable) || ([KTVHCLog log].recordLogEnable && record_log_enable))       \
+{                                                                                   \
+    NSString * va_args = [NSString stringWithFormat:__VA_ARGS__];                   \
+    NSString * log = [NSString stringWithFormat:@"%@  :   %@", target, va_args];    \
+    if ([KTVHCLog log].recordLogEnable && record_log_enable) {                      \
+        [[KTVHCLog log] addRecordLog:log];                                          \
+    }                                                                               \
+    if ([KTVHCLog log].consoleLogEnable && console_log_enable) {                    \
+        NSLog(@"%@", log);                                                          \
+    }                                                                               \
+}
+
+
+/**
+ *  Common
+ */
+#define KTVHCLogCommon(...)                 KTVHCLogging(@"KTVHCCommon           ", KTVHCLogEnableValueConsoleLog(Common),            KTVHCLogEnableValueRecordLog(Common),            ##__VA_ARGS__)
+
+/**
+ *  HTTP Server
+ */
+#define KTVHCLogHTTPServer(...)             KTVHCLogging(@"KTVHCHTTPServer       ", KTVHCLogEnableValueConsoleLog(HTTPServer),        KTVHCLogEnableValueRecordLog(HTTPServer),        ##__VA_ARGS__)
+#define KTVHCLogHTTPConnection(...)         KTVHCLogging(@"KTVHCHTTPConnection   ", KTVHCLogEnableValueConsoleLog(HTTPConnection),    KTVHCLogEnableValueRecordLog(HTTPConnection),    ##__VA_ARGS__)
+#define KTVHCLogHTTPRequest(...)            KTVHCLogging(@"KTVHCHTTPRequest      ", KTVHCLogEnableValueConsoleLog(HTTPRequest),       KTVHCLogEnableValueRecordLog(HTTPRequest),       ##__VA_ARGS__)
+#define KTVHCLogHTTPResponse(...)           KTVHCLogging(@"KTVHCHTTPResponse     ", KTVHCLogEnableValueConsoleLog(HTTPResponse),      KTVHCLogEnableValueRecordLog(HTTPResponse),      ##__VA_ARGS__)
+#define KTVHCLogHTTPResponsePing(...)       KTVHCLogging(@"KTVHCHTTPPingResponse ", KTVHCLogEnableValueConsoleLog(HTTPResponsePing),  KTVHCLogEnableValueRecordLog(HTTPResponsePing),  ##__VA_ARGS__)
+#define KTVHCLogHTTPURL(...)                KTVHCLogging(@"KTVHCHTTPURL          ", KTVHCLogEnableValueConsoleLog(HTTPURL),           KTVHCLogEnableValueRecordLog(HTTPURL),           ##__VA_ARGS__)
+
+/**
+ *  Data Storage
+ */
+#define KTVHCLogDataStorage(...)            KTVHCLogging(@"KTVHCDataStorage      ", KTVHCLogEnableValueConsoleLog(DataStorage),       KTVHCLogEnableValueRecordLog(DataStorage),       ##__VA_ARGS__)
+#define KTVHCLogDataRequest(...)            KTVHCLogging(@"KTVHCDataRequest      ", KTVHCLogEnableValueConsoleLog(DataRequest),       KTVHCLogEnableValueRecordLog(DataRequest),       ##__VA_ARGS__)
+#define KTVHCLogDataResponse(...)           KTVHCLogging(@"KTVHCDataResponse     ", KTVHCLogEnableValueConsoleLog(DataResponse),      KTVHCLogEnableValueRecordLog(DataResponse),      ##__VA_ARGS__)
+#define KTVHCLogDataReader(...)             KTVHCLogging(@"KTVHCDataReader       ", KTVHCLogEnableValueConsoleLog(DataReader),        KTVHCLogEnableValueRecordLog(DataReader),        ##__VA_ARGS__)
+#define KTVHCLogDataLoader(...)             KTVHCLogging(@"KTVHCDataLoader       ", KTVHCLogEnableValueConsoleLog(DataLoader),        KTVHCLogEnableValueRecordLog(DataLoader),        ##__VA_ARGS__)
+
+#define KTVHCLogDataUnit(...)               KTVHCLogging(@"KTVHCDataUnit         ", KTVHCLogEnableValueConsoleLog(DataUnit),          KTVHCLogEnableValueRecordLog(DataUnit),          ##__VA_ARGS__)
+#define KTVHCLogDataUnitItem(...)           KTVHCLogging(@"KTVHCDataUnitItem     ", KTVHCLogEnableValueConsoleLog(DataUnitItem),      KTVHCLogEnableValueRecordLog(DataUnitItem),      ##__VA_ARGS__)
+#define KTVHCLogDataUnitPool(...)           KTVHCLogging(@"KTVHCDataUnitPool     ", KTVHCLogEnableValueConsoleLog(DataUnitPool),      KTVHCLogEnableValueRecordLog(DataUnitPool),      ##__VA_ARGS__)
+#define KTVHCLogDataUnitQueue(...)          KTVHCLogging(@"KTVHCDataUnitQueue    ", KTVHCLogEnableValueConsoleLog(DataUnitQueue),     KTVHCLogEnableValueRecordLog(DataUnitQueue),     ##__VA_ARGS__)
+
+#define KTVHCLogDataSourceManager(...)      KTVHCLogging(@"KTVHCDataSourceManager", KTVHCLogEnableValueConsoleLog(DataSourceManager), KTVHCLogEnableValueRecordLog(DataSourceManager), ##__VA_ARGS__)
+#define KTVHCLogDataSourceQueue(...)        KTVHCLogging(@"KTVHCDataSourceQueue  ", KTVHCLogEnableValueConsoleLog(DataSourceQueue),   KTVHCLogEnableValueRecordLog(DataSourceQueue),   ##__VA_ARGS__)
+#define KTVHCLogDataFileSource(...)         KTVHCLogging(@"KTVHCDataFileSource   ", KTVHCLogEnableValueConsoleLog(DataFileSource),    KTVHCLogEnableValueRecordLog(DataFileSource),    ##__VA_ARGS__)
+#define KTVHCLogDataNetworkSource(...)      KTVHCLogging(@"KTVHCDataNetworkSource", KTVHCLogEnableValueConsoleLog(DataNetworkSource), KTVHCLogEnableValueRecordLog(DataNetworkSource), ##__VA_ARGS__)
+
+/**
+ *  Download
+ */
+#define KTVHCLogDownload(...)               KTVHCLogging(@"KTVHCDownload         ", KTVHCLogEnableValueConsoleLog(Download),          KTVHCLogEnableValueRecordLog(Download),          ##__VA_ARGS__)
+
+/**
+ *  Alloc & Dealloc
+ */
+#define KTVHCLogAlloc(obj)                  KTVHCLogging(obj, KTVHCLogEnableValueConsoleLog(Alloc),   KTVHCLogEnableValueRecordLog(Alloc),   @"alloc")
+#define KTVHCLogDealloc(obj)                KTVHCLogging(obj, KTVHCLogEnableValueConsoleLog(Dealloc), KTVHCLogEnableValueRecordLog(Dealloc), @"dealloc")
+
+@interface KTVHCLog : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)log;
+
+/**
+ *  DEBUG   : default is NO.
+ *  RELEASE : default is NO.
+ */
+@property (nonatomic, assign) BOOL consoleLogEnable;
+
+/**
+ *  DEBUG   : default is NO.
+ *  RELEASE : default is NO.
+ */
+@property (nonatomic, assign) BOOL recordLogEnable;
+
+- (void)addRecordLog:(NSString *)log;
+
+- (NSString *)recordLogFilePath;
+- (void)deleteRecordLog;
+
+/**
+ *  Error
+ */
+- (NSError *)lastError;
+- (NSArray <NSError *> *)allErrors;
+- (void)addError:(NSError *)error;
+
+@end

+ 130 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCLog.m

@@ -0,0 +1,130 @@
+//
+//  KTVHCLog.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/17.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCLog.h"
+#import "KTVHCPathTools.h"
+
+#import <UIKit/UIKit.h>
+
+@interface KTVHCLog ()
+
+@property (nonatomic, strong) NSLock * lock;
+@property (nonatomic, strong) NSFileHandle * writingHandle;
+@property (nonatomic, strong) NSMutableArray <NSError *> * internalErrors;
+
+@end
+
+@implementation KTVHCLog
+
++ (instancetype)log
+{
+    static KTVHCLog * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        self.consoleLogEnable = NO;
+        self.recordLogEnable = NO;
+        self.lock = [[NSLock alloc] init];
+        self.internalErrors = [NSMutableArray array];
+    }
+    return self;
+}
+
+- (void)addRecordLog:(NSString *)log
+{
+    if (!self.recordLogEnable)
+    {
+        return;
+    }
+    if (log.length <= 0)
+    {
+        return;
+    }
+    [self.lock lock];
+    NSString * string = [NSString stringWithFormat:@"%@  %@\n", [NSDate date], log];
+    NSData * data = [string dataUsingEncoding:NSUTF8StringEncoding];
+    if (!self.writingHandle)
+    {
+        [KTVHCPathTools deleteFileAtPath:[KTVHCPathTools logPath]];
+        [KTVHCPathTools createFileAtPath:[KTVHCPathTools logPath]];
+        self.writingHandle = [NSFileHandle fileHandleForWritingAtPath:[KTVHCPathTools logPath]];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
+    }
+    [self.writingHandle writeData:data];
+    [self.lock unlock];
+}
+
+- (NSString *)recordLogFilePath
+{
+    NSString * path = nil;
+    [self.lock lock];
+    long long size = [KTVHCPathTools sizeOfItemAtPath:[KTVHCPathTools logPath]];
+    if (size > 0)
+    {
+        path = [KTVHCPathTools logPath];
+    }
+    [self.lock unlock];
+    return path;
+}
+
+- (void)deleteRecordLog
+{
+    [self.lock lock];
+    [self.writingHandle synchronizeFile];
+    [self.writingHandle closeFile];
+    self.writingHandle = nil;
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
+    [self.lock unlock];
+}
+
+- (NSError *)lastError
+{
+    if (self.internalErrors.count > 0)
+    {
+        return self.internalErrors.lastObject;
+    }
+    return nil;
+}
+
+- (NSArray<NSError *> *)allErrors
+{
+    if (self.internalErrors.count > 0)
+    {
+        return [self.internalErrors copy];
+    }
+    return nil;
+}
+
+- (void)addError:(NSError *)error
+{
+    if (error && [error isKindOfClass:[NSError class]])
+    {
+        if (self.internalErrors.count >= 20)
+        {
+            [self.internalErrors removeObjectAtIndex:0];
+        }
+        [self.internalErrors addObject:error];
+    }
+}
+
+#pragma mark - UIApplicationWillTerminateNotification
+
+- (void)applicationWillTerminate:(NSNotification *)notification
+{
+    [self deleteRecordLog];
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCRange.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.h


+ 170 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.m

@@ -0,0 +1,170 @@
+//
+//  KTVHCRange.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2018/5/20.
+//  Copyright © 2018年 Single. All rights reserved.
+//
+
+#import "KTVHCRange.h"
+
+BOOL KTVHCRangeIsFull(KTVHCRange range)
+{
+    return KTVHCEqualRanges(range, KTVHCRangeFull());
+}
+
+BOOL KTVHCRangeIsVaild(KTVHCRange range)
+{
+    return !KTVHCRangeIsInvaild(range);
+}
+
+BOOL KTVHCRangeIsInvaild(KTVHCRange range)
+{
+    return KTVHCEqualRanges(range, KTVHCRangeInvaild());
+}
+
+BOOL KTVHCEqualRanges(KTVHCRange range1, KTVHCRange range2)
+{
+    return range1.start == range2.start && range1.end == range2.end;
+}
+
+long long KTVHCRangeGetLength(KTVHCRange range)
+{
+    if (range.start == KTVHCNotFound || range.end == KTVHCNotFound)
+    {
+        return KTVHCNotFound;
+    }
+    return range.end - range.start + 1;
+}
+
+NSString * KTVHCStringFromRange(KTVHCRange range)
+{
+    return [NSString stringWithFormat:@"Range : {%lld, %lld}", range.start, range.end];
+}
+
+NSString * KTVHCRangeGetHeaderString(KTVHCRange range)
+{
+    NSMutableString * string = [NSMutableString stringWithFormat:@"bytes="];
+    if (range.start != KTVHCNotFound)
+    {
+        [string appendFormat:@"%lld", range.start];
+    }
+    [string appendFormat:@"-"];
+    if (range.end != KTVHCNotFound)
+    {
+        [string appendFormat:@"%lld", range.end];
+    }
+    return [string copy];
+}
+
+NSDictionary * KTVHCRangeFillToRequestHeaders(KTVHCRange range, NSDictionary * headers)
+{
+    NSMutableDictionary * ret = [NSMutableDictionary dictionaryWithDictionary:headers];
+    [ret setObject:KTVHCRangeGetHeaderString(range) forKey:@"Range"];
+    return ret;
+}
+
+NSDictionary * KTVHCRangeFillToResponseHeaders(KTVHCRange range, NSDictionary * headers, long long totalLength)
+{
+    NSMutableDictionary * ret = [NSMutableDictionary dictionaryWithDictionary:headers];
+    long long currentLength = KTVHCRangeGetLength(range);
+    [ret setObject:[NSString stringWithFormat:@"%lld", currentLength] forKey:@"Content-Length"];
+    [ret setObject:[NSString stringWithFormat:@"bytes %lld-%lld/%lld", range.start, range.end, totalLength] forKey:@"Content-Range"];
+    return ret;
+}
+
+KTVHCRange KTVHCMakeRange(long long start, long long end)
+{
+    KTVHCRange range = {start, end};
+    return range;
+}
+
+KTVHCRange KTVHCRangeZero(void)
+{
+    return KTVHCMakeRange(0, 0);
+}
+
+KTVHCRange KTVHCRangeFull(void)
+{
+    return KTVHCMakeRange(0, KTVHCNotFound);
+}
+
+KTVHCRange KTVHCRangeInvaild()
+{
+    return KTVHCMakeRange(KTVHCNotFound, KTVHCNotFound);
+}
+
+KTVHCRange KTVHCRangeWithSeparateValue(NSString * value)
+{
+    KTVHCRange range = KTVHCRangeInvaild();
+    if (value.length > 0)
+    {
+        NSArray * components = [value componentsSeparatedByString:@","];
+        if (components.count == 1)
+        {
+            components = [components.firstObject componentsSeparatedByString:@"-"];
+            if (components.count == 2)
+            {
+                NSString * startString = [components objectAtIndex:0];
+                NSInteger startValue = [startString integerValue];
+                NSString * endString = [components objectAtIndex:1];
+                NSInteger endValue = [endString integerValue];
+                if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue))
+                {
+                    // The second 500 bytes: "500-999"
+                    range.start = startValue;
+                    range.end = endValue;
+                }
+                else if (startString.length && (startValue >= 0))
+                {
+                    // The bytes after 9500 bytes: "9500-"
+                    range.start = startValue;
+                    range.end = KTVHCNotFound;
+                }
+                else if (endString.length && (endValue > 0))
+                {
+                    // The final 500 bytes: "-500"
+                    range.start = KTVHCNotFound;
+                    range.end = endValue;
+                }
+            }
+        }
+    }
+    return range;
+}
+
+KTVHCRange KTVHCRangeWithRequestHeaderValue(NSString * value)
+{
+    if ([value hasPrefix:@"bytes="])
+    {
+        NSString * rangeString = [value substringFromIndex:6];
+        return KTVHCRangeWithSeparateValue(rangeString);
+    }
+    return KTVHCRangeInvaild();
+}
+
+KTVHCRange KTVHCRangeWithResponseHeaderValue(NSString * value, long long * totalLength)
+{
+    if ([value hasPrefix:@"bytes "])
+    {
+        value = [value stringByReplacingOccurrencesOfString:@"bytes " withString:@""];
+        NSRange range = [value rangeOfString:@"/"];
+        if (range.location != NSNotFound)
+        {
+            NSString * rangeString = [value substringToIndex:range.location];
+            NSString * totalLengthString = [value substringFromIndex:range.location + range.length];
+            * totalLength = totalLengthString.longLongValue;
+            return KTVHCRangeWithSeparateValue(rangeString);
+        }
+    }
+    return KTVHCRangeInvaild();
+}
+
+KTVHCRange KTVHCRangeWithEnsureLength(KTVHCRange range, long long ensureLength)
+{
+    if (range.end == KTVHCNotFound && ensureLength > 0)
+    {
+        return KTVHCMakeRange(range.start, ensureLength - 1);
+    }
+    return range;
+}

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataCacheItem.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.h


+ 44 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.m

@@ -0,0 +1,44 @@
+//
+//  KTVHCDataCacheItem.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/13.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataCacheItem.h"
+#import "KTVHCDataPrivate.h"
+
+@implementation KTVHCDataCacheItem
+
++ (instancetype)itemWithURL:(NSURL *)URL
+                totalLength:(long long)totalLength
+                cacheLength:(long long)cacheLength
+                vaildLength:(long long)vaildLength
+                      zones:(NSArray <KTVHCDataCacheItemZone *> *)zones
+{
+    return [[self alloc] initWithURL:URL
+                         totalLength:totalLength
+                         cacheLength:cacheLength
+                         vaildLength:vaildLength
+                               zones:zones];
+}
+
+- (instancetype)initWithURL:(NSURL *)URL
+                totalLength:(long long)totalLength
+                cacheLength:(long long)cacheLength
+                vaildLength:(long long)vaildLength
+                      zones:(NSArray <KTVHCDataCacheItemZone *> *)zones
+{
+    if (self = [super init])
+    {
+        _URL = URL;
+        _totalLength = totalLength;
+        _cacheLength = cacheLength;
+        _vaildLength = vaildLength;
+        _zones = zones;
+    }
+    return self;
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataCacheItemZone.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.h


+ 29 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.m

@@ -0,0 +1,29 @@
+//
+//  KTVHCDataCacheItemZone.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/13.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataCacheItemZone.h"
+#import "KTVHCDataPrivate.h"
+
+@implementation KTVHCDataCacheItemZone
+
++ (instancetype)itemZoneWithOffset:(long long)offset length:(long long)length
+{
+    return [[self alloc] initWithOffset:offset length:length];
+}
+
+- (instancetype)initWithOffset:(long long)offset length:(long long)length
+{
+    if (self = [super init])
+    {
+        _offset = offset;
+        _length = length;
+    }
+    return self;
+}
+
+@end

+ 16 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.h

@@ -0,0 +1,16 @@
+//
+//  KTVHCDataCallback.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface KTVHCDataCallback : NSObject
+
++ (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block;      // Default is async.
++ (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block async:(BOOL)async;
+
+@end

+ 41 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.m

@@ -0,0 +1,41 @@
+//
+//  KTVHCDataCallback.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataCallback.h"
+
+@implementation KTVHCDataCallback
+
++ (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block
+{
+    [self callbackWithQueue:queue block:block async:YES];
+}
+
++ (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block async:(BOOL)async
+{
+    if (!queue) {
+        return;
+    }
+    if (!block) {
+        return;
+    }
+    if (async) {
+        dispatch_async(queue, ^{
+            if (block) {
+                block();
+            }
+        });
+    } else {
+        dispatch_sync(queue, ^{
+            if (block) {
+                block();
+            }
+        });
+    }
+}
+
+@end

+ 46 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.h

@@ -0,0 +1,46 @@
+//
+//  KTVHCDataFileSource.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataSourceProtocol.h"
+
+@class KTVHCDataFileSource;
+
+@protocol KTVHCDataFileSourceDelegate <NSObject>
+
+- (void)fileSourceDidPrepared:(KTVHCDataFileSource *)fileSource;
+- (void)fileSource:(KTVHCDataFileSource *)fileSource didFailed:(NSError *)error;
+
+@end
+
+@interface KTVHCDataFileSource : NSObject <KTVHCDataSourceProtocol>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithPath:(NSString *)path range:(KTVHCRange)range readRange:(KTVHCRange)readRange;
+
+@property (nonatomic, copy, readonly) NSString * path;
+@property (nonatomic, assign, readonly) KTVHCRange range;
+@property (nonatomic, assign, readonly) KTVHCRange readRange;
+
+@property (nonatomic, assign, readonly) BOOL didPrepared;
+@property (nonatomic, assign, readonly) BOOL didFinished;
+@property (nonatomic, assign, readonly) BOOL didClosed;
+
+- (void)prepare;
+- (void)close;
+
+- (NSData *)readDataOfLength:(NSUInteger)length;
+
+@property (nonatomic, weak, readonly) id<KTVHCDataFileSourceDelegate> delegate;
+@property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue;
+
+- (void)setDelegate:(id<KTVHCDataFileSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+
+@end

+ 187 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.m

@@ -0,0 +1,187 @@
+//
+//  KTVHCDataFileSource.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataFileSource.h"
+#import "KTVHCDataCallback.h"
+#import "KTVHCError.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataFileSource () <NSLocking>
+
+@property (nonatomic, strong) NSError * error;
+@property (nonatomic, strong) NSLock * coreLock;
+@property (nonatomic, strong) NSFileHandle * readingHandle;
+@property (nonatomic, assign) long long readedLength;
+
+@end
+
+@implementation KTVHCDataFileSource
+
+- (instancetype)initWithPath:(NSString *)path range:(KTVHCRange)range readRange:(KTVHCRange)readRange
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _path = path;
+        _range = range;
+        _readRange = readRange;
+        KTVHCLogDataFileSource(@"%p, Create file source\npath : %@\nrange : %@\nreadRange : %@", self, path, KTVHCStringFromRange(range), KTVHCStringFromRange(readRange));
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (void)prepare
+{
+    [self lock];
+    if (self.didPrepared)
+    {
+        [self unlock];
+        return;
+    }
+    KTVHCLogDataFileSource(@"%p, Call prepare", self);
+    self.readingHandle = [NSFileHandle fileHandleForReadingAtPath:self.path];
+    @try
+    {
+        [self.readingHandle seekToFileOffset:self.readRange.start];
+        _didPrepared = YES;
+        if ([self.delegate respondsToSelector:@selector(fileSourceDidPrepared:)])
+        {
+            KTVHCLogDataFileSource(@"%p, Callback for prepared - Begin", self);
+            [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+                KTVHCLogDataFileSource(@"%p, Callback for prepared - End", self);
+                [self.delegate fileSourceDidPrepared:self];
+            }];
+        }
+    }
+    @catch (NSException * exception)
+    {
+        KTVHCLogDataFileSource(@"%p, Seek file exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        NSError * error = [KTVHCError errorForException:exception];
+        [self callbackForFailed:error];
+    }
+    [self unlock];
+}
+
+- (void)close
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    _didClosed = YES;
+    KTVHCLogDataFileSource(@"%p, Call close", self);
+    [self destoryReadingHandle];
+    [self unlock];
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.didFinished)
+    {
+        [self unlock];
+        return nil;
+    }
+    NSData * data = nil;
+    @try
+    {
+        long long readLength = KTVHCRangeGetLength(self.readRange);
+        length = (NSUInteger)MIN(readLength - self.readedLength, length);
+        data = [self.readingHandle readDataOfLength:length];
+        if (data.length > 0)
+        {
+            self.readedLength += data.length;
+            KTVHCLogDataFileSource(@"%p, Read data : %lld, %lld, %lld", self, (long long)data.length, self.readedLength, readLength);
+        }
+        if (self.readedLength >= readLength)
+        {
+            KTVHCLogDataFileSource(@"%p, Read data did finished", self);
+            [self destoryReadingHandle];
+            _didFinished = YES;
+        }
+    }
+    @catch (NSException * exception)
+    {
+        KTVHCLogDataFileSource(@"%p, Read exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        NSError * error = [KTVHCError errorForException:exception];
+        [self callbackForFailed:error];
+    }
+    [self unlock];
+    return data;
+}
+
+- (void)destoryReadingHandle
+{
+    if (self.readingHandle)
+    {
+        @try
+        {
+            [self.readingHandle closeFile];
+        }
+        @catch (NSException * exception)
+        {
+            KTVHCLogDataFileSource(@"%p, Close exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        }
+        self.readingHandle = nil;
+    }
+}
+
+- (void)callbackForFailed:(NSError *)error
+{
+    if (!error)
+    {
+        return;
+    }
+    if (self.error)
+    {
+        return;
+    }
+    self.error = error;
+    if ([self.delegate respondsToSelector:@selector(fileSource:didFailed:)])
+    {
+        KTVHCLogDataFileSource(@"%p, Callback for prepared - Begin", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataFileSource(@"%p, Callback for prepared - End", self);
+            [self.delegate fileSource:self didFailed:self.error];
+        }];
+    }
+}
+
+- (void)setDelegate:(id <KTVHCDataFileSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue
+{
+    _delegate = delegate;
+    _delegateQueue = delegateQueue;
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataLoader.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.h


+ 140 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.m

@@ -0,0 +1,140 @@
+//
+//  KTVHCDataLoader.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2018/6/7.
+//  Copyright © 2018 Single. All rights reserved.
+//
+
+#import "KTVHCDataLoader.h"
+#import "KTVHCDataReader.h"
+#import "KTVHCDataResponse.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataLoader () <KTVHCDataReaderDelegate>
+
+@property (nonatomic, strong) KTVHCDataReader * reader;
+
+@end
+
+@implementation KTVHCDataLoader
+
++ (instancetype)loaderWithRequest:(KTVHCDataRequest *)request
+{
+    return [[self alloc] initWithRequest:request];
+}
+
+- (instancetype)initWithRequest:(KTVHCDataRequest *)request
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        self.reader = [KTVHCDataReader readerWithRequest:request];
+        self.reader.delegate = self;
+        KTVHCLogDataLoader(@"%p, Create loader\norignalRequest : %@\nreader : %@", self, request, self.reader);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+    [self close];
+    KTVHCLogDataLoader(@"%p, Destory reader\nError : %@\nprogress : %f", self, self.error, self.progress);
+}
+
+- (void)prepare
+{
+    KTVHCLogDataLoader(@"%p, Call prepare", self);
+    [self.reader prepare];
+}
+
+- (void)close
+{
+    KTVHCLogDataLoader(@"%p, Call close", self);
+    [self.reader close];
+}
+
+- (KTVHCDataRequest *)request
+{
+    return self.reader.request;
+}
+
+- (KTVHCDataResponse *)response
+{
+    return self.reader.response;
+}
+
+- (NSError *)error
+{
+    return self.reader.error;
+}
+
+- (BOOL)didClosed
+{
+    return self.reader.didClosed;
+}
+
+- (BOOL)didFinished
+{
+    return self.reader.didFinished;
+}
+
+#pragma mark - KTVHCDataReaderDelegate
+
+- (void)readerDidPrepared:(KTVHCDataReader *)reader
+{
+    [self readData];
+}
+
+- (void)readerHasAvailableData:(KTVHCDataReader *)reader
+{
+    [self readData];
+}
+
+- (void)reader:(KTVHCDataReader *)reader didFailed:(NSError *)error
+{
+    KTVHCLogDataLoader(@"%p, Callback for failed", self);
+    if ([self.delegate respondsToSelector:@selector(loader:didFailed:)])
+    {
+        [self.delegate loader:self didFailed:error];
+    }
+}
+
+- (void)readData
+{
+    while (YES)
+    {
+        @autoreleasepool
+        {
+            NSData * data = [self.reader readDataOfLength:1024 * 1024 * 1];
+            if (self.reader.didFinished)
+            {
+                _progress = 1.0f;
+                if ([self.delegate respondsToSelector:@selector(loader:didChangeProgress:)])
+                {
+                    [self.delegate loader:self didChangeProgress:_progress];
+                }
+                KTVHCLogDataLoader(@"%p, Callback finished", self);
+                if ([self.delegate respondsToSelector:@selector(loaderDidFinished:)])
+                {
+                    [self.delegate loaderDidFinished:self];
+                }
+            }
+            else if (data)
+            {
+                _progress = (double)self.reader.readOffset / (double)self.response.currentLength;
+                if ([self.delegate respondsToSelector:@selector(loader:didChangeProgress:)])
+                {
+                    [self.delegate loader:self didChangeProgress:_progress];
+                }
+                KTVHCLogDataLoader(@"%p, read data progress %f", self, _progress);
+                continue;
+            }
+            KTVHCLogDataLoader(@"%p, read data break", self);
+            break;
+        }
+    }
+}
+
+@end

+ 50 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataNetworkSource.h

@@ -0,0 +1,50 @@
+//
+//  KTVHCDataNetworkSource.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataSourceProtocol.h"
+#import "KTVHCDataRequest.h"
+#import "KTVHCDataResponse.h"
+
+@class KTVHCDataNetworkSource;
+
+@protocol KTVHCDataNetworkSourceDelegate <NSObject>
+
+- (void)networkSourceDidPrepared:(KTVHCDataNetworkSource *)networkSource;
+- (void)networkSourceHasAvailableData:(KTVHCDataNetworkSource *)networkSource;
+- (void)networkSourceDidFinishedDownload:(KTVHCDataNetworkSource *)networkSource;
+- (void)networkSource:(KTVHCDataNetworkSource *)networkSource didFailed:(NSError *)error;
+
+@end
+
+@interface KTVHCDataNetworkSource : NSObject <KTVHCDataSourceProtocol>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithRequest:(KTVHCDataRequest *)reqeust;
+
+@property (nonatomic, strong, readonly) KTVHCDataRequest * request;
+@property (nonatomic, strong, readonly) KTVHCDataResponse * response;
+@property (nonatomic, assign, readonly) KTVHCRange range;
+
+@property (nonatomic, assign, readonly) BOOL didPrepared;
+@property (nonatomic, assign, readonly) BOOL didFinished;
+@property (nonatomic, assign, readonly) BOOL didClosed;
+
+- (void)prepare;
+- (void)close;
+
+- (NSData *)readDataOfLength:(NSUInteger)length;
+
+@property (nonatomic, weak, readonly) id<KTVHCDataNetworkSourceDelegate> delegate;
+@property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue;
+
+- (void)setDelegate:(id<KTVHCDataNetworkSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+
+@end

+ 344 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataNetworkSource.m

@@ -0,0 +1,344 @@
+//
+//  KTVHCDataNetworkSource.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataNetworkSource.h"
+#import "KTVHCDataUnitPool.h"
+#import "KTVHCDataCallback.h"
+#import "KTVHCPathTools.h"
+#import "KTVHCDownload.h"
+#import "KTVHCError.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataNetworkSource () <NSLocking, KTVHCDownloadDelegate>
+
+@property (nonatomic, strong) NSError * error;
+@property (nonatomic, strong) NSLock * coreLock;
+@property (nonatomic, assign) long long downloadLength;
+@property (nonatomic, assign) long long downloadReadedLength;
+@property (nonatomic, assign) BOOL downloadDidCallComplete;
+@property (nonatomic, assign) BOOL needCallHasAvailableData;
+@property (nonatomic, assign) BOOL didCalledPrepare;
+
+@property (nonatomic, strong) KTVHCDataUnitItem * unitItem;
+@property (nonatomic, strong) NSFileHandle * readingHandle;
+@property (nonatomic, strong) NSFileHandle * writingHandle;
+@property (nonatomic, assign) NSURLSessionTask * downlaodTask;
+
+@end
+
+@implementation KTVHCDataNetworkSource
+
+- (instancetype)initWithRequest:(KTVHCDataRequest *)reqeust
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _request = reqeust;
+        _range = reqeust.range;
+        KTVHCLogDataNetworkSource(@"%p, Create network source\nrequest : %@\nrange : %@", self, self.request, KTVHCStringFromRange(self.range));
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+    KTVHCLogDataNetworkSource(@"%p, Destory network source\nError : %@\ndownloadLength : %lld\nreadedLength : %lld", self, self.error, self.downloadLength, self.downloadReadedLength);
+}
+
+- (void)prepare
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if (self.didCalledPrepare)
+    {
+        [self unlock];
+        return;
+    }
+    _didCalledPrepare = YES;
+    KTVHCLogDataNetworkSource(@"%p, Call prepare", self);
+    self.downlaodTask = [[KTVHCDownload download] downloadWithRequest:self.request delegate:self];
+    [self unlock];
+}
+
+- (void)close
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    _didClosed = YES;
+    KTVHCLogDataNetworkSource(@"%p, Call close", self);
+    if (!self.downloadDidCallComplete)
+    {
+        KTVHCLogDataNetworkSource(@"%p, Cancel download task", self);
+        [self.downlaodTask cancel];
+        self.downlaodTask = nil;
+    }
+    [self destoryReadingHandle];
+    [self destoryWritingHandle];
+    [self unlock];
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    [self lock];
+    if (self.didClosed || self.didFinished || self.error)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.downloadReadedLength >= self.downloadLength)
+    {
+        if (self.downloadDidCallComplete)
+        {
+            KTVHCLogDataNetworkSource(@"%p, Read data failed\ndownloadLength : %lld\nreadedLength : %lld", self, self.downloadReadedLength, self.downloadLength);
+            [self destoryReadingHandle];
+        }
+        else
+        {
+            KTVHCLogDataNetworkSource(@"%p, Read data wait callback", self);
+            self.needCallHasAvailableData = YES;
+        }
+        [self unlock];
+        return nil;
+    }
+    NSData * data = nil;
+    @try
+    {
+        data = [self.readingHandle readDataOfLength:(NSUInteger)MIN(self.downloadLength - self.downloadReadedLength, length)];
+        self.downloadReadedLength += data.length;
+        KTVHCLogDataNetworkSource(@"%p, Read data\nLength : %lld\ndownloadLength : %lld\nreadedLength : %lld", self, (long long)data.length, self.downloadReadedLength, self.downloadLength);
+        if (self.downloadReadedLength >= KTVHCRangeGetLength(self.response.range))
+        {
+            _didFinished = YES;
+            KTVHCLogDataNetworkSource(@"%p, Read data did finished", self);
+            [self destoryReadingHandle];
+        }
+    }
+    @catch (NSException * exception)
+    {
+        KTVHCLogDataFileSource(@"%p, Read exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        NSError * error = [KTVHCError errorForException:exception];
+        [self callbackForFailed:error];
+    }
+    [self unlock];
+    return data;
+}
+
+- (void)setDelegate:(id <KTVHCDataNetworkSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue
+{
+    _delegate = delegate;
+    _delegateQueue = delegateQueue;
+}
+
+- (void)download:(KTVHCDownload *)download didCompleteWithError:(NSError *)error
+{
+    [self lock];
+    self.downloadDidCallComplete = YES;
+    [self destoryWritingHandle];
+    if (self.didClosed)
+    {
+        KTVHCLogDataNetworkSource(@"%p, Complete but did closed\nError : %@", self, error);
+    }
+    else if (self.error)
+    {
+        KTVHCLogDataNetworkSource(@"%p, Complete but did failed\nself.error : %@\nerror : %@", self, self.error, error);
+    }
+    else if (error)
+    {
+        if (error.code != NSURLErrorCancelled)
+        {
+            [self callbackForFailed:error];
+        }
+        else
+        {
+            KTVHCLogDataNetworkSource(@"%p, Complete with cancel\nError : %@", self, error);
+        }
+    }
+    else if (self.downloadLength >= KTVHCRangeGetLength(self.response.range))
+    {
+        KTVHCLogDataNetworkSource(@"%p, Complete and finisehed", self);
+        if ([self.delegate respondsToSelector:@selector(networkSourceDidFinishedDownload:)])
+        {
+            [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+                [self.delegate networkSourceDidFinishedDownload:self];
+            }];
+        }
+    }
+    else
+    {
+        KTVHCLogDataNetworkSource(@"%p, Complete but not finisehed\ndownloadLength : %lld", self, self.downloadLength);
+    }
+    [self unlock];
+}
+
+- (void)download:(KTVHCDownload *)download didReceiveResponse:(KTVHCDataResponse *)response
+{
+    [self lock];
+    if (self.didClosed || self.error)
+    {
+        [self unlock];
+        return;
+    }
+    _response = response;
+    NSString * path = [KTVHCPathTools unitItemPathWithURL:self.request.URL offset:self.request.range.start];
+    self.unitItem = [[KTVHCDataUnitItem alloc] initWithPath:path offset:self.request.range.start];
+    KTVHCDataUnit * unit = [[KTVHCDataUnitPool pool] unitWithURL:self.request.URL];
+    [unit insertUnitItem:self.unitItem];
+    KTVHCLogDataNetworkSource(@"%p, Receive response\nResponse : %@\nUnit : %@\nUnitItem : %@", self, response, unit, self.unitItem);
+    [unit workingRelease];
+    self.writingHandle = [NSFileHandle fileHandleForWritingAtPath:self.unitItem.absolutePath];
+    self.readingHandle = [NSFileHandle fileHandleForReadingAtPath:self.unitItem.absolutePath];
+    [self callbackForPrepared];
+    [self unlock];
+}
+
+- (void)download:(KTVHCDownload *)download didReceiveData:(NSData *)data
+{
+    [self lock];
+    if (self.didClosed || self.error)
+    {
+        [self unlock];
+        return;
+    }
+    @try
+    {
+        [self.writingHandle writeData:data];
+        self.downloadLength += data.length;
+        [self.unitItem setLength:self.downloadLength];
+        KTVHCLogDataNetworkSource(@"%p, Receive data : %lld, %lld, %lld", self, (long long)data.length, self.downloadLength, self.unitItem.length);
+        [self callbackForHasAvailableData];
+    }
+    @catch (NSException * exception)
+    {
+        NSError * error = [KTVHCError errorForException:exception];
+        KTVHCLogDataNetworkSource(@"%p, write exception\nError : %@", self, error);
+        [self callbackForFailed:error];
+        if (!self.downloadDidCallComplete)
+        {
+            KTVHCLogDataNetworkSource(@"%p, Cancel download task when write exception", self);
+            [self.downlaodTask cancel];
+            self.downlaodTask = nil;
+        }
+    }
+    [self unlock];
+}
+
+- (void)destoryReadingHandle
+{
+    if (self.readingHandle)
+    {
+        @try
+        {
+            [self.readingHandle closeFile];
+        }
+        @catch (NSException * exception)
+        {
+            KTVHCLogDataFileSource(@"%p, Close reading handle exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        }
+        self.readingHandle = nil;
+    }
+}
+
+- (void)destoryWritingHandle
+{
+    if (self.writingHandle)
+    {
+        @try
+        {
+            [self.writingHandle synchronizeFile];
+            [self.writingHandle closeFile];
+        }
+        @catch (NSException * exception)
+        {
+            KTVHCLogDataFileSource(@"%p, Close writing handle exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        }
+        self.writingHandle = nil;
+    }
+}
+
+- (void)callbackForPrepared
+{
+    if (self.didClosed)
+    {
+        return;
+    }
+    if (self.didPrepared)
+    {
+        return;
+    }
+    _didPrepared = YES;
+    if ([self.delegate respondsToSelector:@selector(networkSourceDidPrepared:)])
+    {
+        KTVHCLogDataNetworkSource(@"%p, Callback for prepared - Begin", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataNetworkSource(@"%p, Callback for prepared - End", self);
+            [self.delegate networkSourceDidPrepared:self];
+        }];
+    }
+}
+
+- (void)callbackForHasAvailableData
+{
+    if (self.didClosed)
+    {
+        return;
+    }
+    if (!self.needCallHasAvailableData)
+    {
+        return;
+    }
+    self.needCallHasAvailableData = NO;
+    if ([self.delegate respondsToSelector:@selector(networkSourceHasAvailableData:)])
+    {
+        KTVHCLogDataNetworkSource(@"%p, Callback for has available data - Begin", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataNetworkSource(@"%p, Callback for has available data - End", self);
+            [self.delegate networkSourceHasAvailableData:self];
+        }];
+    }
+}
+
+- (void)callbackForFailed:(NSError *)error
+{
+    if (self.didClosed || !error || self.error)
+    {
+        return;
+    }
+    self.error = error;
+    KTVHCLogDataNetworkSource(@"%p, Callback for failed\nError : %@", self, self.error);
+    if ([self.delegate respondsToSelector:@selector(networkSource:didFailed:)])
+    {
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            [self.delegate networkSource:self didFailed:self.error];
+        }];
+    }
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 27 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataPrivate.h

@@ -0,0 +1,27 @@
+//
+//  KTVHCDataReaderPrivate.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataCacheItem.h"
+#import "KTVHCDataCacheItemZone.h"
+
+@interface KTVHCDataCacheItem (Private)
+
++ (instancetype)itemWithURL:(NSURL *)URL
+                totalLength:(long long)totalLength
+                cacheLength:(long long)cacheLength
+                vaildLength:(long long)vaildLength
+                      zones:(NSArray <KTVHCDataCacheItemZone *> *)zones;
+
+@end
+
+@interface KTVHCDataCacheItemZone (Private)
+
++ (instancetype)itemZoneWithOffset:(long long)offset length:(long long)length;
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataReader.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.h


+ 301 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.m

@@ -0,0 +1,301 @@
+//
+//  KTVHCDataReader.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataReader.h"
+#import "KTVHCDataSourceManager.h"
+#import "KTVHCDataUnitPool.h"
+#import "KTVHCDataCallback.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataReader () <KTVHCDataSourceManagerDelegate>
+
+@property (nonatomic, strong) NSRecursiveLock * coreLock;
+@property (nonatomic, strong) dispatch_queue_t delegateQueue;
+@property (nonatomic, strong) dispatch_queue_t internalDelegateQueue;
+@property (nonatomic, assign) BOOL didCalledPrepare;
+
+@property (nonatomic, strong) KTVHCDataUnit * unit;
+@property (nonatomic, strong) KTVHCDataSourceManager * sourceManager;
+
+@end
+
+@implementation KTVHCDataReader
+
++ (instancetype)readerWithRequest:(KTVHCDataRequest *)request
+{
+    return [[self alloc] initWithRequest:request];
+}
+
+- (instancetype)initWithRequest:(KTVHCDataRequest *)request
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        self.unit = [[KTVHCDataUnitPool pool] unitWithURL:request.URL];
+        _request = [request requestWithTotalLength:self.unit.totalLength];
+        [self.unit updateRequestHeaders:self.request.headers];
+        self.delegateQueue = dispatch_queue_create("KTVHCDataReader_delegateQueue", DISPATCH_QUEUE_SERIAL);
+        self.internalDelegateQueue = dispatch_queue_create("KTVHCDataReader_internalDelegateQueue", DISPATCH_QUEUE_SERIAL);
+        KTVHCLogDataReader(@"%p, Create reader\norignalRequest : %@\nfinalRequest : %@\nUnit : %@", self, request, self.request, self.unit);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+    [self close];
+    KTVHCLogDataReader(@"%p, Destory reader\nError : %@\nreadOffset : %lld", self, self.error, self.readOffset);
+}
+
+- (void)prepare
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if (self.didCalledPrepare)
+    {
+        [self unlock];
+        return;
+    }
+    _didCalledPrepare = YES;
+    KTVHCLogDataReader(@"%p, Call prepare", self);
+    [self prepareSourceManager];
+    [self unlock];
+}
+
+- (void)close
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    _didClosed = YES;
+    KTVHCLogDataReader(@"%p, Call close", self);
+    [self.sourceManager close];
+    [self.unit workingRelease];
+    self.unit = nil;
+    [self unlock];
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.didFinished)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.error)
+    {
+        [self unlock];
+        return nil;
+    }
+    NSData * data = [self.sourceManager readDataOfLength:length];;
+    _readOffset += data.length;
+    KTVHCLogDataReader(@"%p, Read data : %lld", self, (long long)data.length);
+    if (self.sourceManager.didFinished)
+    {
+        KTVHCLogDataReader(@"%p, Read data did finished", self);
+        _didFinished = YES;
+        [self close];
+    }
+    [self unlock];
+    return data;
+}
+
+- (void)prepareSourceManager
+{
+    self.sourceManager = [[KTVHCDataSourceManager alloc] initWithDelegate:self delegateQueue:self.internalDelegateQueue];
+    NSMutableArray <KTVHCDataFileSource *> * fileSources = [NSMutableArray array];
+    NSMutableArray <KTVHCDataNetworkSource *> * networkSources = [NSMutableArray array];
+    long long min = self.request.range.start;
+    long long max = self.request.range.end;
+    NSArray * unitItems = self.unit.unitItems;
+    for (KTVHCDataUnitItem * item in unitItems)
+    {
+        long long itemMin = item.offset;
+        long long itemMax = item.offset + item.length - 1;
+        if (itemMax < min || itemMin > max)
+        {
+            continue;
+        }
+        if (min > itemMin)
+        {
+            itemMin = min;
+        }
+        if (max < itemMax)
+        {
+            itemMax = max;
+        }
+        min = itemMax + 1;
+        KTVHCRange range = KTVHCMakeRange(item.offset, item.offset + item.length - 1);
+        KTVHCRange readRange = KTVHCMakeRange(itemMin - item.offset, itemMax - item.offset);
+        KTVHCDataFileSource * source = [[KTVHCDataFileSource alloc] initWithPath:item.absolutePath range:range readRange:readRange];
+        [fileSources addObject:source];
+    }
+    [fileSources sortUsingComparator:^NSComparisonResult(KTVHCDataFileSource * obj1, KTVHCDataFileSource * obj2) {
+        if (obj1.range.start < obj2.range.start) {
+            return NSOrderedAscending;
+        }
+        return NSOrderedDescending;
+    }];
+    long long offset = self.request.range.start;
+    long long length = KTVHCRangeIsFull(self.request.range) ? KTVHCRangeGetLength(self.request.range) : (self.request.range.end - offset + 1);
+    for (KTVHCDataFileSource * obj in fileSources)
+    {
+        long long delta = obj.range.start + obj.readRange.start - offset;
+        if (delta > 0)
+        {
+            KTVHCRange range = KTVHCMakeRange(offset, offset + delta - 1);
+            KTVHCDataRequest * request = [self.request requestWithRange:range];
+            KTVHCDataNetworkSource * source = [[KTVHCDataNetworkSource alloc] initWithRequest:request];
+            [networkSources addObject:source];
+            offset += delta;
+            length -= delta;
+        }
+        offset += KTVHCRangeGetLength(obj.readRange);
+        length -= KTVHCRangeGetLength(obj.readRange);
+    }
+    if (length > 0)
+    {
+        KTVHCRange range = KTVHCMakeRange(offset, self.request.range.end);
+        KTVHCDataRequest * request = [self.request requestWithRange:range];
+        KTVHCDataNetworkSource * source = [[KTVHCDataNetworkSource alloc] initWithRequest:request];
+        [networkSources addObject:source];
+    }
+    for (KTVHCDataFileSource * obj in fileSources)
+    {
+        [self.sourceManager putSource:obj];
+    }
+    for (KTVHCDataNetworkSource * obj in networkSources)
+    {
+        [self.sourceManager putSource:obj];
+    }
+    [self.sourceManager prepare];
+}
+
+- (void)sourceManagerDidPrepared:(KTVHCDataSourceManager *)sourceManager
+{
+    [self lock];
+    [self callbackForPrepared];
+    [self unlock];
+}
+
+- (void)sourceManager:(KTVHCDataSourceManager *)sourceManager didReceiveResponse:(KTVHCDataResponse *)response
+{
+    [self lock];
+    [self.unit updateResponseHeaders:response.headers totalLength:response.totalLength];
+    [self callbackForPrepared];
+    [self unlock];
+}
+
+- (void)sourceManagerHasAvailableData:(KTVHCDataSourceManager *)sourceManager
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if ([self.delegate respondsToSelector:@selector(readerHasAvailableData:)])
+    {
+        KTVHCLogDataReader(@"%p, Callback for has available data - Begin", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataReader(@"%p, Callback for has available data - End", self);
+            [self.delegate readerHasAvailableData:self];
+        }];
+    }
+    [self unlock];
+}
+
+- (void)sourceManager:(KTVHCDataSourceManager *)sourceManager didFailed:(NSError *)error
+{
+    if (!error)
+    {
+        return;
+    }
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if (self.error)
+    {
+        [self unlock];
+        return;
+    }
+    _error = error;
+    [self close];
+    [[KTVHCLog log] addError:self.error];
+    if ([self.delegate respondsToSelector:@selector(reader:didFailed:)])
+    {
+        KTVHCLogDataReader(@"%p, Callback for failed - Begin\nError : %@", self, self.error);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataReader(@"%p, Callback for failed - End", self);
+            [self.delegate reader:self didFailed:self.error];
+        }];
+    }
+    [self unlock];
+}
+
+- (void)callbackForPrepared
+{
+    if (self.didClosed)
+    {
+        return;
+    }
+    if (self.didPrepared)
+    {
+        return;
+    }
+    if (self.sourceManager.didPrepared && self.unit.totalLength > 0)
+    {
+        long long totalLength = self.unit.totalLength;
+        KTVHCRange range = KTVHCRangeWithEnsureLength(self.request.range, totalLength);
+        NSDictionary * headers = KTVHCRangeFillToResponseHeaders(range, self.unit.responseHeaders, totalLength);
+        _response = [[KTVHCDataResponse alloc] initWithURL:self.request.URL headers:headers];
+        _didPrepared = YES;
+        KTVHCLogDataReader(@"%p, Reader did prepared\nResponse : %@", self, self.response);
+        if ([self.delegate respondsToSelector:@selector(readerDidPrepared:)])
+        {
+            KTVHCLogDataReader(@"%p, Callback for prepared - Begin", self);
+            [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+                KTVHCLogDataReader(@"%p, Callback for prepared - End", self);
+                [self.delegate readerDidPrepared:self];
+            }];
+        }
+    }
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSRecursiveLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataRequest.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.h


+ 53 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.m

@@ -0,0 +1,53 @@
+//
+//  KTVHCDataRequest.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataRequest.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHCDataRequest
+
+- (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _URL = URL;
+        if (![headers objectForKey:@"Range"]) {
+            _headers = KTVHCRangeFillToRequestHeaders(KTVHCRangeFull(), headers);
+        } else {
+            _headers = headers;
+        }
+        _range = KTVHCRangeWithRequestHeaderValue([_headers objectForKey:@"Range"]);
+        KTVHCLogDataRequest(@"%p Create data request\nURL : %@\nHeaders : %@\nRange : %@", self, self.URL, self.headers, KTVHCStringFromRange(self.range));
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (KTVHCDataRequest *)requestWithRange:(KTVHCRange)range
+{
+    if (!KTVHCEqualRanges(self.range, range))
+    {
+        NSDictionary * headers = KTVHCRangeFillToRequestHeaders(range, self.headers);
+        KTVHCDataRequest * obj = [[KTVHCDataRequest alloc] initWithURL:self.URL headers:headers];
+        return obj;
+    }
+    return self;
+}
+
+- (KTVHCDataRequest *)requestWithTotalLength:(long long)totalLength
+{
+    KTVHCRange range = KTVHCRangeWithEnsureLength(self.range, totalLength);
+    return [self requestWithRange:range];
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHCDataResponse.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.h


+ 74 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.m

@@ -0,0 +1,74 @@
+//
+//  KTVHCDataResponse.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/24.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataResponse.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHCDataResponse
+
+- (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _URL = URL;
+        _headers = headers;
+        NSMutableDictionary * headersWithoutRangeAndLength = [headers mutableCopy];
+        for (NSString * key in [self withoutHeaderKeys])
+        {
+            [headersWithoutRangeAndLength removeObjectForKey:key];
+        }
+        _headersWithoutRangeAndLength = [headersWithoutRangeAndLength copy];
+        _contentType = [self headerValueWithKey:@"Content-Type"];
+        _currentLength = [self headerValueWithKey:@"Content-Length"].longLongValue;
+        _range = KTVHCRangeWithResponseHeaderValue([self headerValueWithKey:@"Content-Range"], &_totalLength);
+        KTVHCLogDataResponse(@"%p Create data response\nURL : %@\nHeaders : %@\nheadersWithoutRangeAndLength : %@\ncontentType : %@\ntotalLength : %lld\ncurrentLength : %lld", self, self.URL, self.headers, self.headersWithoutRangeAndLength, self.contentType, self.totalLength, self.currentLength);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (NSString *)headerValueWithKey:(NSString *)key
+{
+    NSString * value = [self.headers objectForKey:key];
+    if (!value)
+    {
+        value = [self.headers objectForKey:[key lowercaseString]];
+    }
+    return value;
+}
+
+- (NSArray <NSString *> *)withoutHeaderKeys
+{
+    static NSArray * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = @[@"Content-Length",
+                @"content-length",
+                @"Content-Range",
+                @"content-range"];
+    });
+    return obj;
+}
+
+- (KTVHCDataResponse *)responseWithRange:(KTVHCRange)range
+{
+    if (!KTVHCEqualRanges(self.range, range))
+    {
+        NSDictionary * headers = KTVHCRangeFillToResponseHeaders(range, self.headers, self.totalLength);
+        KTVHCDataResponse * obj = [[KTVHCDataResponse alloc] initWithURL:self.URL headers:headers];
+        return obj;
+    }
+    return self;
+}
+
+@end

+ 47 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceManager.h

@@ -0,0 +1,47 @@
+//
+//  KTVHCDataSourceManager.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataFileSource.h"
+#import "KTVHCDataNetworkSource.h"
+
+@class KTVHCDataSourceManager;
+
+@protocol KTVHCDataSourceManagerDelegate <NSObject>
+
+- (void)sourceManagerDidPrepared:(KTVHCDataSourceManager *)sourceManager;
+- (void)sourceManagerHasAvailableData:(KTVHCDataSourceManager *)sourceManager;
+- (void)sourceManager:(KTVHCDataSourceManager *)sourceManager didFailed:(NSError *)error;
+- (void)sourceManager:(KTVHCDataSourceManager *)sourceManager didReceiveResponse:(KTVHCDataResponse *)response;
+
+@end
+
+@interface KTVHCDataSourceManager : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithDelegate:(id <KTVHCDataSourceManagerDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+
+@property (nonatomic, weak, readonly) id <KTVHCDataSourceManagerDelegate> delegate;
+@property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue;
+
+@property (nonatomic, strong, readonly) NSError * error;
+
+@property (nonatomic, assign, readonly) BOOL didClosed;
+@property (nonatomic, assign, readonly) BOOL didPrepared;
+@property (nonatomic, assign, readonly) BOOL didFinished;
+
+- (void)putSource:(id<KTVHCDataSourceProtocol>)source;
+
+- (void)prepare;
+- (void)close;
+
+- (NSData *)readDataOfLength:(NSUInteger)length;
+
+@end

+ 267 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceManager.m

@@ -0,0 +1,267 @@
+//
+//  KTVHCDataSourceManager.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataSourceManager.h"
+#import "KTVHCDataSourceQueue.h"
+#import "KTVHCDataCallback.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataSourceManager () <NSLocking, KTVHCDataFileSourceDelegate, KTVHCDataNetworkSourceDelegate>
+
+@property (nonatomic, strong) NSLock * coreLock;
+@property (nonatomic, assign) BOOL didCalledPrepare;
+@property (nonatomic, assign) BOOL didCalledReceiveResponse;
+
+@property (nonatomic, strong) KTVHCDataSourceQueue * sourceQueue;
+@property (nonatomic, strong) id <KTVHCDataSourceProtocol> currentSource;
+@property (nonatomic, strong) KTVHCDataNetworkSource * currentNetworkSource;
+
+@end
+
+@implementation KTVHCDataSourceManager
+
+- (instancetype)initWithDelegate:(id <KTVHCDataSourceManagerDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _delegate = delegate;
+        _delegateQueue = delegateQueue;
+        self.sourceQueue = [KTVHCDataSourceQueue sourceQueue];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+    KTVHCLogDataReader(@"%p, Destory reader\nError : %@\ncurrentSource : %@\ncurrentNetworkSource : %@", self, self.error, self.currentSource, self.currentNetworkSource);
+}
+
+- (void)putSource:(id<KTVHCDataSourceProtocol>)source
+{
+    KTVHCLogDataSourceManager(@"%p, Put source : %@", self, source);
+    [self.sourceQueue putSource:source];
+}
+
+- (void)prepare
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if (self.didCalledPrepare)
+    {
+        [self unlock];
+        return;
+    }
+    _didCalledPrepare = YES;
+    KTVHCLogDataSourceManager(@"%p, Call prepare", self);
+    [self.sourceQueue sortSources];
+    [self.sourceQueue setAllSourceDelegate:self delegateQueue:self.delegateQueue];
+    self.currentSource = [self.sourceQueue firstSource];
+    self.currentNetworkSource = [self.sourceQueue firstNetworkSource];
+    KTVHCLogDataSourceManager(@"%p, Sort source\ncurrentSource : %@\ncurrentNetworkSource : %@", self, self.currentSource, self.currentNetworkSource);
+    [self.currentSource prepare];
+    if (self.currentSource != self.currentNetworkSource)
+    {
+        [self.currentNetworkSource prepare];
+    }
+    [self unlock];
+}
+
+- (void)close
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    _didClosed = YES;
+    KTVHCLogDataSourceManager(@"%p, Call close", self);
+    [self.sourceQueue closeAllSource];
+    [self unlock];
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.didFinished)
+    {
+        [self unlock];
+        return nil;
+    }
+    if (self.error)
+    {
+        [self unlock];
+        return nil;
+    }
+    NSData * data = [self.currentSource readDataOfLength:length];
+    KTVHCLogDataSourceManager(@"%p, Read data : %lld", self, (long long)data.length);
+    if (self.currentSource.didFinished)
+    {
+        self.currentSource = [self.sourceQueue nextSource:self.currentSource];
+        if (self.currentSource)
+        {
+            KTVHCLogDataSourceManager(@"%p, Switch to next source, %@", self, self.currentSource);
+            if ([self.currentSource isKindOfClass:[KTVHCDataFileSource class]])
+            {
+                [self.currentSource prepare];
+            }
+        }
+        else
+        {
+            KTVHCLogDataSourceManager(@"%p, Read data did finished", self);
+            _didFinished = YES;
+        }
+    }
+    [self unlock];
+    return data;
+}
+
+- (void)fileSourceDidPrepared:(KTVHCDataFileSource *)fileSource
+{
+    [self lock];
+    [self callbackForPrepared];
+    [self unlock];
+}
+
+- (void)fileSource:(KTVHCDataFileSource *)fileSource didFailed:(NSError *)error
+{
+    [self callbackForFailed:error];
+}
+
+- (void)networkSourceDidPrepared:(KTVHCDataNetworkSource *)networkSource
+{
+    [self lock];
+    [self callbackForPrepared];
+    [self callbackForReceiveResponse:networkSource.response];
+    [self unlock];
+}
+
+- (void)networkSourceHasAvailableData:(KTVHCDataNetworkSource *)networkSource
+{
+    [self lock];
+    if ([self.delegate respondsToSelector:@selector(sourceManagerHasAvailableData:)])
+    {
+        KTVHCLogDataSourceManager(@"%p, Callback for has available data - Begin\nSource : %@", self, networkSource);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataSourceManager(@"%p, Callback for has available data - End", self);
+            [self.delegate sourceManagerHasAvailableData:self];
+        }];
+    }
+    [self unlock];
+}
+
+- (void)networkSourceDidFinishedDownload:(KTVHCDataNetworkSource *)networkSource
+{
+    [self lock];
+    self.currentNetworkSource = [self.sourceQueue nextNetworkSource:self.currentNetworkSource];
+    [self.currentNetworkSource prepare];
+    [self unlock];
+}
+
+- (void)networkSource:(KTVHCDataNetworkSource *)networkSource didFailed:(NSError *)error
+{
+    [self callbackForFailed:error];
+}
+
+- (void)callbackForPrepared
+{
+    if (self.didClosed)
+    {
+        return;
+    }
+    if (self.didPrepared)
+    {
+        return;
+    }
+    _didPrepared = YES;
+    if ([self.delegate respondsToSelector:@selector(sourceManagerDidPrepared:)])
+    {
+        KTVHCLogDataSourceManager(@"%p, Callback for prepared - Begin", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataSourceManager(@"%p, Callback for prepared - End", self);
+            [self.delegate sourceManagerDidPrepared:self];
+        }];
+    }
+}
+
+- (void)callbackForReceiveResponse:(KTVHCDataResponse *)response
+{
+    if (self.didClosed)
+    {
+        return;
+    }
+    if (self.didCalledReceiveResponse)
+    {
+        return;
+    }
+    _didCalledReceiveResponse = YES;
+    if ([self.delegate respondsToSelector:@selector(sourceManager:didReceiveResponse:)])
+    {
+        KTVHCLogDataSourceManager(@"%p, Callback for did receive response - End", self);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataSourceManager(@"%p, Callback for did receive response - End", self);
+            [self.delegate sourceManager:self didReceiveResponse:response];
+        }];
+    }
+}
+
+- (void)callbackForFailed:(NSError *)error
+{
+    if (!error)
+    {
+        return;
+    }
+    [self lock];
+    if (self.didClosed)
+    {
+        [self unlock];
+        return;
+    }
+    if (self.error)
+    {
+        [self unlock];
+        return;
+    }
+    _error = error;
+    KTVHCLogDataSourceManager(@"failure, %d", (int)self.error.code);
+    if (self.error && [self.delegate respondsToSelector:@selector(sourceManager:didFailed:)])
+    {
+        KTVHCLogDataSourceManager(@"%p, Callback for network source failed - Begin\nError : %@", self, self.error);
+        [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{
+            KTVHCLogDataSourceManager(@"%p, Callback for network source failed - End", self);
+            [self.delegate sourceManager:self didFailed:self.error];
+        }];
+    }
+    [self unlock];
+}
+
+- (void)lock
+{
+    if (!self.coreLock) {
+        self.coreLock = [[NSLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 25 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceProtocol.h

@@ -0,0 +1,25 @@
+//
+//  KTVHCDataSourceProtocol.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCRange.h"
+
+@protocol KTVHCDataSourceProtocol <NSObject>
+
+- (KTVHCRange)range;
+
+- (BOOL)didPrepared;
+- (BOOL)didFinished;
+- (BOOL)didClosed;
+
+- (void)prepare;
+- (void)close;
+
+- (NSData *)readDataOfLength:(NSUInteger)length;
+
+@end

+ 39 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceQueue.h

@@ -0,0 +1,39 @@
+//
+//  KTVHCDataSourceQueue.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataSourceProtocol.h"
+
+@class KTVHCDataFileSource;
+@class KTVHCDataNetworkSource;
+
+@protocol KTVHCDataFileSourceDelegate;
+@protocol KTVHCDataNetworkSourceDelegate;
+
+@interface KTVHCDataSourceQueue : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)sourceQueue;
+
+- (void)putSource:(id<KTVHCDataSourceProtocol>)source;
+- (void)popSource:(id<KTVHCDataSourceProtocol>)source;
+
+- (void)setAllSourceDelegate:(id<KTVHCDataFileSourceDelegate, KTVHCDataNetworkSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+
+- (void)sortSources;
+- (void)closeAllSource;
+
+- (id<KTVHCDataSourceProtocol>)firstSource;
+- (id<KTVHCDataSourceProtocol>)nextSource:(id<KTVHCDataSourceProtocol>)currentSource;
+
+- (KTVHCDataNetworkSource *)firstNetworkSource;
+- (KTVHCDataNetworkSource *)nextNetworkSource:(KTVHCDataNetworkSource *)currentSource;
+
+@end

+ 152 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceQueue.m

@@ -0,0 +1,152 @@
+//
+//  KTVHCDataSourceQueue.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataSourceQueue.h"
+#import "KTVHCDataFileSource.h"
+#import "KTVHCDataNetworkSource.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataSourceQueue ()
+
+@property (nonatomic, strong) NSMutableArray <id<KTVHCDataSourceProtocol>> * sources;
+
+@end
+
+@implementation KTVHCDataSourceQueue
+
++ (instancetype)sourceQueue
+{
+    return [[self alloc] init];
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        self.sources = [NSMutableArray array];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (void)putSource:(id<KTVHCDataSourceProtocol>)source
+{
+    if (!source) {
+        return;
+    } {
+        [self.sources addObject:source];
+    }
+}
+
+- (void)popSource:(id<KTVHCDataSourceProtocol>)source
+{
+    if (!source)
+    {
+        return;
+    }
+    if ([self.sources containsObject:source])
+    {
+        [self.sources removeObject:source];
+    }
+}
+
+- (void)setAllSourceDelegate:(id<KTVHCDataFileSourceDelegate, KTVHCDataNetworkSourceDelegate>)delegate delegateQueue:(dispatch_queue_t)delegateQueue
+{
+    for (id <KTVHCDataSourceProtocol> obj in self.sources)
+    {
+        if ([obj isKindOfClass:[KTVHCDataFileSource class]])
+        {
+            KTVHCDataFileSource * source = (KTVHCDataFileSource *)obj;
+            [source setDelegate:delegate delegateQueue:delegateQueue];
+        }
+        else if ([obj isKindOfClass:[KTVHCDataNetworkSource class]])
+        {
+            KTVHCDataNetworkSource * source = (KTVHCDataNetworkSource *)obj;
+            [source setDelegate:delegate delegateQueue:delegateQueue];
+        }
+    }
+}
+
+- (void)sortSources
+{
+    KTVHCLogDataSourceQueue(@"%p, Sort sources - Begin\nSources : %@", self, self.sources);
+    [self.sources sortUsingComparator:^NSComparisonResult(id <KTVHCDataSourceProtocol> obj1, id <KTVHCDataSourceProtocol> obj2) {
+        if (obj1.range.start < obj2.range.start)
+        {
+            return NSOrderedAscending;
+        }
+        return NSOrderedDescending;
+    }];
+    KTVHCLogDataSourceQueue(@"%p, Sort sources - End  \nSources : %@", self, self.sources);
+}
+
+- (void)closeAllSource
+{
+    for (id <KTVHCDataSourceProtocol> obj in self.sources)
+    {
+        [obj close];
+    }
+}
+
+- (id<KTVHCDataSourceProtocol>)firstSource
+{
+    return self.sources.firstObject;
+}
+
+- (id<KTVHCDataSourceProtocol>)nextSource:(id<KTVHCDataSourceProtocol>)currentSource
+{
+    if ([self.sources containsObject:currentSource])
+    {
+        NSUInteger index = [self.sources indexOfObject:currentSource] + 1;
+        if (index < self.sources.count)
+        {
+            KTVHCLogDataSourceQueue(@"%p, Fetch next source : %@", self, [self.sources objectAtIndex:index]);
+            return [self.sources objectAtIndex:index];
+        }
+    }
+    KTVHCLogDataSourceQueue(@"%p, Fetch netxt source failed", self);
+    return nil;
+}
+
+- (KTVHCDataNetworkSource *)firstNetworkSource
+{
+    for (id<KTVHCDataSourceProtocol> obj in self.sources)
+    {
+        if ([obj isKindOfClass:[KTVHCDataNetworkSource class]])
+        {
+            return obj;
+        }
+    }
+    return nil;
+}
+
+- (KTVHCDataNetworkSource *)nextNetworkSource:(KTVHCDataNetworkSource *)currentSource
+{
+    if ([self.sources containsObject:currentSource])
+    {
+        NSUInteger index = [self.sources indexOfObject:currentSource] + 1;
+        for (; index < self.sources.count; index++)
+        {
+            id <KTVHCDataSourceProtocol> obj = [self.sources objectAtIndex:index];
+            if ([obj isKindOfClass:[KTVHCDataNetworkSource class]])
+            {
+                KTVHCLogDataSourceQueue(@"%p, Fetch next network source : %@", self, obj);
+                return obj;
+            }
+        }
+    }
+    KTVHCLogDataSourceQueue(@"%p, Fetch netxt network source failed", self);
+    return nil;
+}
+
+@end

+ 56 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.h

@@ -0,0 +1,56 @@
+//
+//  KTVHCDataManager.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataReader.h"
+#import "KTVHCDataLoader.h"
+#import "KTVHCDataRequest.h"
+#import "KTVHCDataResponse.h"
+#import "KTVHCDataCacheItem.h"
+
+@interface KTVHCDataStorage : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)storage;
+
+/**
+ *  Return file path if the content did finished cache.
+ */
+- (NSURL *)completeFileURLIfExistedWithURL:(NSURL *)URL;
+
+/**
+ *  Reader for certain request.
+ */
+- (KTVHCDataReader *)readerWithRequest:(KTVHCDataRequest *)request;
+
+/**
+ *  Loader for certain request.
+ */
+- (KTVHCDataLoader *)loaderWithRequest:(KTVHCDataRequest *)request;
+
+/**
+ *  Get cache item.
+ */
+- (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL;
+- (NSArray <KTVHCDataCacheItem *> *)allCacheItems;
+
+/**
+ *  Get cache length.
+ */
+@property (nonatomic, assign) long long maxCacheLength;     // Default is 500M.
+- (long long)totalCacheLength;
+
+/**
+ *  Delete cache.
+ */
+- (void)deleteCacheWithURL:(NSURL *)URL;
+- (void)deleteAllCaches;
+
+@end

+ 89 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.m

@@ -0,0 +1,89 @@
+//
+//  KTVHCDataManager.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataStorage.h"
+#import "KTVHCDataUnitPool.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHCDataStorage
+
++ (instancetype)storage
+{
+    static KTVHCDataStorage * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        self.maxCacheLength = 500 * 1024 * 1024;
+    }
+    return self;
+}
+
+- (NSURL *)completeFileURLIfExistedWithURL:(NSURL *)URL
+{
+    KTVHCDataUnit * unit = [[KTVHCDataUnitPool pool] unitWithURL:URL];
+    NSURL * fileURL = unit.fileURL;
+    [unit workingRelease];
+    return fileURL;
+}
+
+- (KTVHCDataReader *)readerWithRequest:(KTVHCDataRequest *)request
+{
+    if (!request || request.URL.absoluteString.length <= 0)
+    {
+        KTVHCLogDataStorage(@"Invaild reader request, %@", request.URL);
+        return nil;
+    }
+    KTVHCDataReader * reader = [KTVHCDataReader readerWithRequest:request];
+    return reader;
+}
+
+- (KTVHCDataLoader *)loaderWithRequest:(KTVHCDataRequest *)request
+{
+    if (!request || request.URL.absoluteString.length <= 0)
+    {
+        KTVHCLogDataStorage(@"Invaild loader request, %@", request.URL);
+        return nil;
+    }
+    KTVHCDataLoader * loader = [KTVHCDataLoader loaderWithRequest:request];
+    return loader;
+}
+
+- (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL
+{
+    return [[KTVHCDataUnitPool pool] cacheItemWithURL:URL];
+}
+
+- (NSArray<KTVHCDataCacheItem *> *)allCacheItems
+{
+    return [[KTVHCDataUnitPool pool] allCacheItem];
+}
+
+- (long long)totalCacheLength
+{
+    return [[KTVHCDataUnitPool pool] totalCacheLength];
+}
+
+- (void)deleteCacheWithURL:(NSURL *)URL
+{
+    [[KTVHCDataUnitPool pool] deleteUnitWithURL:URL];
+}
+
+- (void)deleteAllCaches
+{
+    [[KTVHCDataUnitPool pool] deleteAllUnits];
+}
+
+@end

+ 70 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnit.h

@@ -0,0 +1,70 @@
+//
+//  KTVHCDataUnit.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataUnitItem.h"
+
+@class KTVHCDataUnit;
+
+@protocol KTVHCDataUnitFileDelegate <NSObject>
+
+- (void)unitShouldRearchive:(KTVHCDataUnit *)unit;
+
+@end
+
+@interface KTVHCDataUnit : NSObject <NSCoding, NSLocking>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)unitWithURL:(NSURL *)URL;
+
+@property (nonatomic, assign, readonly) BOOL valid;
+
+@property (nonatomic, copy, readonly) NSURL * URL;
+@property (nonatomic, copy, readonly) NSURL * fileURL;
+@property (nonatomic, copy, readonly) NSString * key;       // Unique Identifier.
+
+@property (nonatomic, assign, readonly) NSTimeInterval createTimeInterval;
+@property (nonatomic, assign, readonly) NSTimeInterval lastItemCreateInterval;
+
+@property (nonatomic, copy, readonly) NSDictionary * requestHeaders;
+@property (nonatomic, copy, readonly) NSDictionary * responseHeaders;
+
+@property (nonatomic, assign, readonly) long long totalLength;
+@property (nonatomic, assign, readonly) long long cacheLength;
+@property (nonatomic, assign, readonly) long long validLength;
+
+/**
+ *  Unit Item
+ */
+- (NSArray <KTVHCDataUnitItem *> *)unitItems;
+- (void)insertUnitItem:(KTVHCDataUnitItem *)unitItem;
+
+/**
+ *  Info Sync
+ */
+- (void)updateRequestHeaders:(NSDictionary *)requestHeaders;
+- (void)updateResponseHeaders:(NSDictionary *)responseHeaders totalLength:(long long)totalLength;
+
+/**
+ *  Working
+ */
+@property (nonatomic, assign, readonly) NSInteger workingCount;
+
+- (void)workingRetain;
+- (void)workingRelease;
+
+/**
+ *  File Control
+ */
+@property (nonatomic, weak) id <KTVHCDataUnitFileDelegate> fileDelegate;
+
+- (void)deleteFiles;
+
+@end

+ 410 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnit.m

@@ -0,0 +1,410 @@
+//
+//  KTVHCDataUnit.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataUnit.h"
+#import "KTVHCURLTools.h"
+#import "KTVHCPathTools.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataUnit ()
+
+@property (nonatomic, strong) NSRecursiveLock * coreLock;
+@property (nonatomic, strong) NSMutableArray <KTVHCDataUnitItem *> * unitItemsInternal;
+@property (nonatomic, strong) NSMutableArray <NSArray <KTVHCDataUnitItem *> *> * lockingUnitItems;
+
+@end
+
+@implementation KTVHCDataUnit
+
++ (instancetype)unitWithURL:(NSURL *)URL
+{
+    return [[self alloc] initWithURL:URL];
+}
+
+- (instancetype)initWithURL:(NSURL *)URL
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _URL = URL;
+        _key = [KTVHCURLTools keyWithURL:self.URL];
+        _createTimeInterval = [NSDate date].timeIntervalSince1970;
+        _valid = YES;
+        [self prepare];
+    }
+    return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)aDecoder
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        @try {
+            _URL = [NSURL URLWithString:[aDecoder decodeObjectForKey:@"URLString"]];
+            _key = [aDecoder decodeObjectForKey:@"uniqueIdentifier"];
+            _valid = YES;
+        } @catch (NSException * exception) {
+            _valid = NO;
+        }
+        @try {
+            _createTimeInterval = [[aDecoder decodeObjectForKey:@"createTimeInterval"] doubleValue];
+            _requestHeaders = [aDecoder decodeObjectForKey:@"requestHeaderFields"];
+            _responseHeaders = [aDecoder decodeObjectForKey:@"responseHeaderFields"];
+            _totalLength = [[aDecoder decodeObjectForKey:@"totalContentLength"] longLongValue];
+            self.unitItemsInternal = [aDecoder decodeObjectForKey:@"unitItems"];
+            [self prepare];
+            _valid = _valid && YES;
+        } @catch (NSException * exception) {
+            _valid = NO;
+        }
+    }
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+    [self lock];
+    [aCoder encodeObject:self.URL.absoluteString forKey:@"URLString"];
+    [aCoder encodeObject:self.key forKey:@"uniqueIdentifier"];
+    [aCoder encodeObject:@(self.createTimeInterval) forKey:@"createTimeInterval"];
+    [aCoder encodeObject:self.requestHeaders forKey:@"requestHeaderFields"];
+    [aCoder encodeObject:self.responseHeaders forKey:@"responseHeaderFields"];
+    [aCoder encodeObject:@(self.totalLength) forKey:@"totalContentLength"];
+    [aCoder encodeObject:self.unitItemsInternal forKey:@"unitItems"];
+    [self unlock];
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (void)prepare
+{
+    [self lock];
+    if (!self.unitItemsInternal)
+    {
+        self.unitItemsInternal = [NSMutableArray array];
+    }
+    if (self.unitItemsInternal.count > 0)
+    {
+        NSMutableArray * removeArray = [NSMutableArray array];
+        for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+        {
+            if (obj.length <= 0)
+            {
+                [KTVHCPathTools deleteFileAtPath:obj.absolutePath];
+                [removeArray addObject:obj];
+            }
+        }
+        [self.unitItemsInternal removeObjectsInArray:removeArray];
+        [removeArray removeAllObjects];
+        [self sortUnitItems];
+    }
+    KTVHCLogDataUnit(@"%p, Create Unit\nURL : %@\nkey : %@\ntimeInterval : %@\ntotalLength : %lld\ncacheLength : %lld\nvaildLength : %lld\nrequestHeaders : %@\nresponseHeaders : %@\nunitItems : %@", self, self.URL, self.key, [NSDate dateWithTimeIntervalSince1970:self.createTimeInterval], self.totalLength, self.cacheLength, self.validLength, self.requestHeaders, self.responseHeaders, self.unitItemsInternal);
+    [self unlock];
+}
+
+- (void)sortUnitItems
+{
+    [self lock];
+    KTVHCLogDataSourceQueue(@"%p, Sort unitItems - Begin\n%@", self, self.unitItemsInternal);
+    [self.unitItemsInternal sortUsingComparator:^NSComparisonResult(KTVHCDataUnitItem * obj1, KTVHCDataUnitItem * obj2) {
+        NSComparisonResult result = NSOrderedDescending;
+        if (obj1.offset < obj2.offset)
+        {
+            result = NSOrderedAscending;
+        }
+        else if ((obj1.offset == obj2.offset) && (obj1.length > obj2.length))
+        {
+            result = NSOrderedAscending;
+        }
+        return result;
+    }];
+    KTVHCLogDataSourceQueue(@"%p, Sort unitItems - End  \n%@", self, self.unitItemsInternal);
+    [self unlock];
+}
+
+- (NSArray <KTVHCDataUnitItem *> *)unitItems
+{
+    [self lock];
+    NSMutableArray * objs = [NSMutableArray array];
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        [objs addObject:[obj copy]];
+    }
+    KTVHCLogDataSourceQueue(@"%p, Get unitItems\n%@", self, self.unitItemsInternal);
+    [self unlock];
+    return [objs copy];
+}
+
+- (void)insertUnitItem:(KTVHCDataUnitItem *)unitItem
+{
+    [self lock];
+    [self.unitItemsInternal addObject:unitItem];
+    [self sortUnitItems];
+    KTVHCLogDataUnit(@"%p, Insert unitItem, %@", self, unitItem);
+    [self unlock];
+    [self.fileDelegate unitShouldRearchive:self];
+}
+
+- (void)updateRequestHeaders:(NSDictionary *)requestHeaders
+{
+    [self lock];
+    _requestHeaders = requestHeaders;
+    KTVHCLogDataUnit(@"%p, Update requestHeaders\n%@", self, self.requestHeaders);
+    [self unlock];
+    [self.fileDelegate unitShouldRearchive:self];
+}
+
+- (void)updateResponseHeaders:(NSDictionary *)responseHeaders totalLength:(long long)totalLength
+{
+    [self lock];
+    _responseHeaders = responseHeaders;
+    _totalLength = totalLength;
+    KTVHCLogDataUnit(@"%p, Update responseHeaders\ntotalLength : %lld\n%@", self, self.totalLength, self.responseHeaders);
+    [self unlock];
+    [self.fileDelegate unitShouldRearchive:self];
+}
+
+- (NSURL *)fileURL
+{
+    [self lock];
+    NSURL * fileURL = nil;
+    KTVHCDataUnitItem * item = self.unitItemsInternal.firstObject;
+    if (item.offset == 0 && item.length > 0 && item.length == self.totalLength)
+    {
+        fileURL = [NSURL fileURLWithPath:item.absolutePath];
+        KTVHCLogDataUnit(@"%p, Get file path\n%@", self, fileURL);
+    }
+    [self unlock];
+    return fileURL;
+}
+
+- (long long)cacheLength
+{
+    [self lock];
+    long long length = 0;
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        length += obj.length;
+    }
+    [self unlock];
+    return length;
+}
+
+- (long long)validLength
+{
+    [self lock];
+    long long offset = 0;
+    long long length = 0;
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        long long invalidLength = MAX(offset - obj.offset, 0);
+        long long vaildLength = MAX(obj.length - invalidLength, 0);
+        offset = MAX(offset, obj.offset + obj.length);
+        length += vaildLength;
+    }
+    [self unlock];
+    return length;
+}
+
+- (NSTimeInterval)lastItemCreateInterval
+{
+    [self lock];
+    NSTimeInterval timeInterval = self.createTimeInterval;
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        if (obj.createTimeInterval > timeInterval)
+        {
+            timeInterval = obj.createTimeInterval;
+        }
+    }
+    [self unlock];
+    return timeInterval;
+}
+
+- (void)workingRetain
+{
+    [self lock];
+    _workingCount++;
+    KTVHCLogDataUnit(@"%p, Working retain  : %ld", self, (long)self.workingCount);
+    [self unlock];
+}
+
+- (void)workingRelease
+{
+    BOOL mergeSuccess = NO;
+    [self lock];
+    _workingCount--;
+    KTVHCLogDataUnit(@"%p, Working release : %ld", self, (long)self.workingCount);
+    if (self.workingCount <= 0)
+    {
+        mergeSuccess = [self mergeFilesIfNeeded];
+    }
+    [self unlock];
+    if (mergeSuccess)
+    {
+        [self.fileDelegate unitShouldRearchive:self];
+    }
+}
+
+- (void)deleteFiles
+{
+    if (!self.URL)
+    {
+        return;
+    }
+    [self lock];
+    NSString * path = [KTVHCPathTools directoryPathWithURL:self.URL];
+    [KTVHCPathTools deleteDirectoryAtPath:path];
+    KTVHCLogDataUnit(@"%p, Delete files", self);
+    [self unlock];
+}
+
+- (BOOL)mergeFilesIfNeeded
+{
+    [self lock];
+    if (self.workingCount > 0 || self.totalLength <= 0 || self.unitItemsInternal.count <= 0)
+    {
+        [self unlock];
+        return NO;
+    }
+    NSString * path = [KTVHCPathTools completeFilePathWithURL:self.URL];
+    if ([self.unitItemsInternal.firstObject.absolutePath isEqualToString:path])
+    {
+        [self unlock];
+        return NO;
+    }
+    if (self.totalLength != self.validLength)
+    {
+        [self unlock];
+        return NO;
+    }
+    BOOL failed = NO;
+    long long offset = 0;
+    [KTVHCPathTools deleteFileAtPath:path];
+    [KTVHCPathTools createFileAtPath:path];
+    NSFileHandle * writingHandle = [NSFileHandle fileHandleForWritingAtPath:path];
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        if (failed)
+        {
+            break;
+        }
+        NSAssert(offset >= obj.offset, @"invaild unit item.");
+        if (offset >= (obj.offset + obj.length))
+        {
+            KTVHCLogDataUnit(@"%p, Merge files continue", self);
+            continue;
+        }
+        NSFileHandle * readingHandle = [NSFileHandle fileHandleForReadingAtPath:obj.absolutePath];
+        @try
+        {
+            [readingHandle seekToFileOffset:offset - obj.offset];
+        }
+        @catch (NSException * exception)
+        {
+            KTVHCLogDataUnit(@"%p, Merge files seek exception\n%@", self, exception);
+            failed = YES;
+        }
+        if (failed)
+        {
+            break;
+        }
+        while (!failed)
+        {
+            @autoreleasepool
+            {
+                NSData * data = [readingHandle readDataOfLength:1024 * 1024 * 1];
+                if (data.length <= 0)
+                {
+                    KTVHCLogDataUnit(@"%p, Merge files break", self);
+                    break;
+                }
+                KTVHCLogDataUnit(@"%p, Merge write data : %lld", self, (long long)data.length);
+                @try
+                {
+                    [writingHandle writeData:data];
+                }
+                @catch (NSException * exception)
+                {
+                    KTVHCLogDataUnit(@"%p, Merge files write exception\n%@", self, exception);
+                    failed = YES;
+                }
+            }
+        }
+        [readingHandle closeFile];
+        offset = obj.offset + obj.length;
+        KTVHCLogDataUnit(@"%p, Merge next : %lld", self, offset);
+    }
+    @try
+    {
+        [writingHandle synchronizeFile];
+        [writingHandle closeFile];
+    }
+    @catch (NSException * exception)
+    {
+        KTVHCLogDataUnit(@"%p, Merge files close exception, %d\n%@", self, failed, exception);
+        failed = YES;
+    }
+    KTVHCLogDataUnit(@"%p, Merge finished\ntotalLength : %lld\noffset : %lld", self, self.totalLength, offset);
+    if (failed || [KTVHCPathTools sizeOfItemAtPath:path] != self.totalLength)
+    {
+        [KTVHCPathTools deleteFileAtPath:path];
+        [self unlock];
+        return NO;
+    }
+    KTVHCLogDataUnit(@"%p, Merge replace items", self);
+    KTVHCDataUnitItem * item = [[KTVHCDataUnitItem alloc] initWithPath:path offset:0];
+    for (KTVHCDataUnitItem * obj in self.unitItemsInternal)
+    {
+        [KTVHCPathTools deleteFileAtPath:obj.absolutePath];
+    }
+    [self.unitItemsInternal removeAllObjects];
+    [self.unitItemsInternal addObject:item];
+    [self unlock];
+    return YES;
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSRecursiveLock alloc] init];
+    }
+    [self.coreLock lock];
+    if (!self.lockingUnitItems)
+    {
+        self.lockingUnitItems = [NSMutableArray array];
+    }
+    NSArray <KTVHCDataUnitItem *> * objs = [NSArray arrayWithArray:self.unitItemsInternal];
+    [self.lockingUnitItems addObject:objs];
+    for (KTVHCDataUnitItem * obj in objs)
+    {
+        [obj lock];
+    }
+}
+
+- (void)unlock
+{
+    NSArray <KTVHCDataUnitItem *> * objs = self.lockingUnitItems.lastObject;
+    [self.lockingUnitItems removeLastObject];
+    if (self.lockingUnitItems.count <= 0)
+    {
+        self.lockingUnitItems = nil;
+    }
+    for (KTVHCDataUnitItem * obj in objs)
+    {
+        [obj unlock];
+    }
+    [self.coreLock unlock];
+}
+
+@end

+ 28 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.h

@@ -0,0 +1,28 @@
+//
+//  KTVHCDataUnitItem.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface KTVHCDataUnitItem : NSObject <NSCopying, NSCoding, NSLocking>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithPath:(NSString *)path offset:(long long)offset;
+
+@property (nonatomic, assign, readonly) NSTimeInterval createTimeInterval;
+
+@property (nonatomic, copy, readonly) NSString * relativePath;
+@property (nonatomic, copy, readonly) NSString * absolutePath;
+
+@property (nonatomic, assign, readonly) long long offset;
+@property (nonatomic, assign, readonly) long long length;
+
+- (void)setLength:(long long)length;
+
+@end

+ 110 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.m

@@ -0,0 +1,110 @@
+//
+//  KTVHCDataUnitItem.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataUnitItem.h"
+#import "KTVHCPathTools.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataUnitItem ()
+
+@property (nonatomic, strong) NSRecursiveLock * coreLock;
+
+@end
+
+@implementation KTVHCDataUnitItem
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    [self lock];
+    KTVHCDataUnitItem * obj = [[KTVHCDataUnitItem alloc] initForCopy];
+    obj->_relativePath = self.relativePath;
+    obj->_absolutePath = self.absolutePath;
+    obj->_createTimeInterval = self.createTimeInterval;
+    obj->_offset = self.offset;
+    obj->_length = self.length;
+    [self unlock];
+    return obj;
+}
+
+- (instancetype)initForCopy
+{
+    if (self = [super init])
+    {
+        
+    }
+    return self;
+}
+
+- (instancetype)initWithPath:(NSString *)path offset:(long long)offset
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _createTimeInterval = [NSDate date].timeIntervalSince1970;
+        _relativePath = [KTVHCPathTools relativePathWithAbsoultePath:path];
+        _offset = offset;
+        [self prepare];
+    }
+    return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)aDecoder
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _createTimeInterval = [[aDecoder decodeObjectForKey:@"createTimeInterval"] doubleValue];
+        _relativePath = [aDecoder decodeObjectForKey:@"relativePath"];
+        _offset = [[aDecoder decodeObjectForKey:@"offset"] longLongValue];
+        [self prepare];
+    }
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+    [aCoder encodeObject:@(self.createTimeInterval) forKey:@"createTimeInterval"];
+    [aCoder encodeObject:self.relativePath forKey:@"relativePath"];
+    [aCoder encodeObject:@(self.offset) forKey:@"offset"];
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (void)prepare
+{
+    _absolutePath = [KTVHCPathTools absoultePathWithRelativePath:self.relativePath];
+    self.length = [KTVHCPathTools sizeOfItemAtPath:self.absolutePath];
+    KTVHCLogDataUnitItem(@"%p, Create Unit Item\nabsolutePath : %@\nrelativePath : %@\nOffset : %lld\nLength : %lld", self, self.absolutePath, self.relativePath, self.offset, self.length);
+}
+
+- (void)setLength:(long long)length
+{
+    [self lock];
+    _length = length;
+    KTVHCLogDataUnitItem(@"%p, Set length : %lld", self, length);
+    [self unlock];
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSRecursiveLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 31 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitPool.h

@@ -0,0 +1,31 @@
+//
+//  KTVHCDataUnitPool.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataUnit.h"
+#import "KTVHCDataCacheItem.h"
+
+@interface KTVHCDataUnitPool : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)pool;
+
+- (KTVHCDataUnit *)unitWithURL:(NSURL *)URL;
+
+- (long long)totalCacheLength;
+
+- (NSArray <KTVHCDataCacheItem *> *)allCacheItem;
+- (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL;
+
+- (void)deleteUnitWithURL:(NSURL *)URL;
+- (void)deleteUnitsWithLength:(long long)length;
+- (void)deleteAllUnits;
+
+@end

+ 306 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitPool.m

@@ -0,0 +1,306 @@
+//
+//  KTVHCDataUnitPool.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataUnitPool.h"
+#import "KTVHCDataUnitQueue.h"
+#import "KTVHCDataPrivate.h"
+#import "KTVHCPathTools.h"
+#import "KTVHCURLTools.h"
+#import "KTVHCLog.h"
+
+#import <UIKit/UIKit.h>
+
+@interface KTVHCDataUnitPool () <NSLocking, KTVHCDataUnitFileDelegate>
+
+@property (nonatomic, strong) NSRecursiveLock * coreLock;
+@property (nonatomic, strong) KTVHCDataUnitQueue * unitQueue;
+@property (nonatomic, assign) int64_t expectArchiveIndex;
+@property (nonatomic, assign) int64_t actualArchiveIndex;
+@property (nonatomic, strong) dispatch_queue_t archiveQueue;
+
+@end
+
+@implementation KTVHCDataUnitPool
+
++ (instancetype)pool
+{
+    static KTVHCDataUnitPool * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        self.unitQueue = [KTVHCDataUnitQueue queueWithPath:[KTVHCPathTools archivePath]];
+        for (KTVHCDataUnit * obj in self.unitQueue.allUnits)
+        {
+            obj.fileDelegate = self;
+        }
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
+        [[NSNotificationCenter defaultCenter]  addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
+        [[NSNotificationCenter defaultCenter]  addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
+        KTVHCLogDataUnitPool(@"%p, Create Pool\nUnits : %@", self, self.unitQueue.allUnits);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (KTVHCDataUnit *)unitWithURL:(NSURL *)URL
+{
+    if (URL.absoluteString.length <= 0)
+    {
+        return nil;
+    }
+    [self lock];
+    NSString * key = [KTVHCURLTools keyWithURL:URL];
+    KTVHCDataUnit * unit = [self.unitQueue unitWithKey:key];
+    if (!unit)
+    {
+        unit = [KTVHCDataUnit unitWithURL:URL];
+        unit.fileDelegate = self;
+        KTVHCLogDataUnitPool(@"%p, Insert Unit, %@", self, unit);
+        [self.unitQueue putUnit:unit];
+        [self setNeedsArchive];
+    }
+    [unit workingRetain];
+    [self unlock];
+    return unit;
+}
+
+- (long long)totalCacheLength
+{
+    [self lock];
+    long long length = 0;
+    NSArray <KTVHCDataUnit *> * units = [self.unitQueue allUnits];
+    for (KTVHCDataUnit * obj in units)
+    {
+        length += obj.cacheLength;
+    }
+    [self unlock];
+    return length;
+}
+
+- (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL
+{
+    if (URL.absoluteString.length <= 0)
+    {
+        return nil;
+    }
+    [self lock];
+    KTVHCDataCacheItem * cacheItem = nil;
+    NSString * key = [KTVHCURLTools keyWithURL:URL];
+    KTVHCDataUnit * obj = [self.unitQueue unitWithKey:key];
+    if (obj)
+    {
+        NSArray * items = obj.unitItems;
+        NSMutableArray * itemZones = [NSMutableArray array];
+        for (KTVHCDataUnitItem * unitItem in items)
+        {
+            KTVHCDataCacheItemZone * itemZone = [KTVHCDataCacheItemZone itemZoneWithOffset:unitItem.offset length:unitItem.length];
+            [itemZones addObject:itemZone];
+        }
+        if (itemZones.count <= 0)
+        {
+            itemZones = nil;
+        }
+        cacheItem = [KTVHCDataCacheItem itemWithURL:obj.URL totalLength:obj.totalLength cacheLength:obj.cacheLength vaildLength:obj.validLength zones:itemZones];
+    }
+    [self unlock];
+    return cacheItem;
+}
+
+- (NSArray <KTVHCDataCacheItem *> *)allCacheItem
+{
+    [self lock];
+    NSMutableArray * cacheItems = [NSMutableArray array];
+    NSArray <KTVHCDataUnit *> * units = [self.unitQueue allUnits];
+    for (KTVHCDataUnit * obj in units)
+    {
+        KTVHCDataCacheItem * cacheItem = [self cacheItemWithURL:obj.URL];
+        if (cacheItem)
+        {
+            [cacheItems addObject:cacheItem];
+        }
+    }
+    if (cacheItems.count <= 0)
+    {
+        cacheItems = nil;
+    }
+    [self unlock];
+    return cacheItems;
+}
+
+- (void)deleteUnitWithURL:(NSURL *)URL
+{
+    if (URL.absoluteString.length <= 0)
+    {
+        return;
+    }
+    [self lock];
+    NSString * key = [KTVHCURLTools keyWithURL:URL];
+    KTVHCDataUnit * obj = [self.unitQueue unitWithKey:key];
+    if (obj && obj.workingCount <= 0)
+    {
+        KTVHCLogDataUnit(@"%p, Delete Unit\nUnit : %@\nFunc : %s", self, obj, __func__);
+        [obj deleteFiles];
+        [self.unitQueue popUnit:obj];
+        [self setNeedsArchive];
+    }
+    [self unlock];
+}
+
+- (void)deleteUnitsWithLength:(long long)length
+{
+    if (length <= 0)
+    {
+        return;
+    }
+    [self lock];
+    BOOL needArchive = NO;
+    long long currentLength = 0;
+    NSArray <KTVHCDataUnit *> * units = [self.unitQueue allUnits];
+    [units sortedArrayUsingComparator:^NSComparisonResult(KTVHCDataUnit * obj1, KTVHCDataUnit * obj2) {
+        NSComparisonResult result = NSOrderedDescending;
+        [obj1 lock];
+        [obj2 lock];
+        NSTimeInterval timeInterval1 = obj1.lastItemCreateInterval;
+        NSTimeInterval timeInterval2 = obj2.lastItemCreateInterval;
+        if (timeInterval1 < timeInterval2) {
+            result = NSOrderedAscending;
+        } else if (timeInterval1 == timeInterval2 && obj1.createTimeInterval < obj2.createTimeInterval) {
+            result = NSOrderedAscending;
+        }
+        [obj1 unlock];
+        [obj2 unlock];
+        return result;
+    }];
+    for (KTVHCDataUnit * obj in units)
+    {
+        if (obj.workingCount <= 0)
+        {
+            [obj lock];
+            currentLength += obj.cacheLength;
+            KTVHCLogDataUnit(@"%p, Delete Unit\nUnit : %@\nFunc : %s", self, obj, __func__);
+            [obj deleteFiles];
+            [obj unlock];
+            [self.unitQueue popUnit:obj];
+            needArchive = YES;
+        }
+        if (currentLength >= length)
+        {
+            break;
+        }
+    }
+    if (needArchive)
+    {
+        [self setNeedsArchive];
+    }
+    [self unlock];
+}
+
+- (void)deleteAllUnits
+{
+    [self lock];
+    BOOL needArchive = NO;
+    NSArray <KTVHCDataUnit *> * units = [self.unitQueue allUnits];
+    for (KTVHCDataUnit * obj in units)
+    {
+        if (obj.workingCount <= 0)
+        {
+            KTVHCLogDataUnit(@"%p, Delete Unit\nUnit : %@\nFunc : %s", self, obj, __func__);
+            [obj deleteFiles];
+            [self.unitQueue popUnit:obj];
+            needArchive = YES;
+        }
+    }
+    if (needArchive)
+    {
+        [self setNeedsArchive];
+    }
+    [self unlock];
+}
+
+- (void)unitShouldRearchive:(KTVHCDataUnit *)unit
+{
+    [self setNeedsArchive];
+}
+
+- (void)setNeedsArchive
+{
+    [self lock];
+    self.expectArchiveIndex += 1;
+    int64_t expectArchiveIndex = self.expectArchiveIndex;
+    [self unlock];
+    if (!self.archiveQueue)
+    {
+        self.archiveQueue = dispatch_queue_create("KTVHTTPCache-archiveQueue", DISPATCH_QUEUE_SERIAL);
+    }
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), self.archiveQueue, ^{
+        [self lock];
+        if (self.expectArchiveIndex == expectArchiveIndex)
+        {
+            [self archiveIfNeeded];
+        }
+        [self unlock];
+    });
+}
+
+- (void)archiveIfNeeded
+{
+    [self lock];
+    if (self.actualArchiveIndex != self.expectArchiveIndex)
+    {
+        self.actualArchiveIndex = self.expectArchiveIndex;
+        [self.unitQueue archive];
+    }
+    [self unlock];
+}
+
+#pragma mark - UIApplicationWillTerminateNotification
+
+- (void)applicationWillTerminate:(NSNotification *)notification
+{
+    [self archiveIfNeeded];
+}
+
+- (void)applicationDidEnterBackground:(NSNotification *)notification
+{
+    [self archiveIfNeeded];
+}
+
+- (void)applicationWillResignActive:(NSNotification *)notification
+{
+    [self archiveIfNeeded];
+}
+
+#pragma mark - NSLocking
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSRecursiveLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+@end

+ 27 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.h

@@ -0,0 +1,27 @@
+//
+//  KTVHCDataUnitQueue.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataUnit.h"
+
+@interface KTVHCDataUnitQueue : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)queueWithPath:(NSString *)path;
+
+- (NSArray <KTVHCDataUnit *> *)allUnits;
+- (KTVHCDataUnit *)unitWithKey:(NSString *)key;
+
+- (void)putUnit:(KTVHCDataUnit *)unit;
+- (void)popUnit:(KTVHCDataUnit *)unit;
+
+- (void)archive;
+
+@end

+ 108 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.m

@@ -0,0 +1,108 @@
+//
+//  KTVHCDataUnitQueue.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/11.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDataUnitQueue.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCDataUnitQueue ()
+
+@property (nonatomic, copy) NSString * path;
+@property (nonatomic, strong) NSMutableArray <KTVHCDataUnit *> * unitArray;
+
+@end
+
+@implementation KTVHCDataUnitQueue
+
++ (instancetype)queueWithPath:(NSString *)path
+{
+    return [[self alloc] initWithPath:path];
+}
+
+- (instancetype)initWithPath:(NSString *)path
+{
+    if (self = [super init])
+    {
+        self.path = path;
+        NSMutableArray * unitArray = nil;
+        @try {
+            unitArray = [NSKeyedUnarchiver unarchiveObjectWithFile:self.path];
+        } @catch (NSException * exception) {
+            KTVHCLogDataUnitQueue(@"%p, Init exception\nname : %@\breason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo);
+        }
+        self.unitArray = [NSMutableArray array];
+        for (KTVHCDataUnit * obj in unitArray) {
+            if (obj.valid) {
+                [self.unitArray addObject:obj];
+            } else {
+                [obj deleteFiles];
+            }
+        }
+    }
+    return self;
+}
+
+- (NSArray <KTVHCDataUnit *> *)allUnits
+{
+    if (self.unitArray.count <= 0)
+    {
+        return nil;
+    }
+    NSArray <KTVHCDataUnit *> * units = [self.unitArray copy];
+    return units;
+}
+
+- (KTVHCDataUnit *)unitWithKey:(NSString *)key
+{
+    if (key.length <= 0)
+    {
+        return nil;
+    }
+    KTVHCDataUnit * unit = nil;
+    for (KTVHCDataUnit * obj in self.unitArray)
+    {
+        if ([obj.key isEqualToString:key])
+        {
+            unit = obj;
+            break;
+        }
+    }
+    return unit;
+}
+
+- (void)putUnit:(KTVHCDataUnit *)unit
+{
+    if (!unit)
+    {
+        return;
+    }
+    if (![self.unitArray containsObject:unit])
+    {
+        [self.unitArray addObject:unit];
+    }
+}
+
+- (void)popUnit:(KTVHCDataUnit *)unit
+{
+    if (!unit)
+    {
+        return;
+    }
+    if ([self.unitArray containsObject:unit])
+    {
+        [self.unitArray removeObject:unit];
+    }
+}
+
+- (void)archive
+{
+    KTVHCLogDataUnitQueue(@"%p, Archive - Begin, %ld", self, (long)self.unitArray.count);
+    [NSKeyedArchiver archiveRootObject:self.unitArray toFile:self.path];
+    KTVHCLogDataUnitQueue(@"%p, Archive - End  , %ld", self, (long)self.unitArray.count);
+}
+
+@end

+ 53 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDownload/KTVHCDownload.h

@@ -0,0 +1,53 @@
+//
+//  KTVHCDataDownload.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCDataRequest.h"
+#import "KTVHCDataResponse.h"
+#import "KTVHCCommon.h"
+
+KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeVideo;
+KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeAudio;
+KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeApplicationMPEG4;
+KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeApplicationOctetStream;
+KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeBinaryOctetStream;
+
+@class KTVHCDownload;
+
+@protocol KTVHCDownloadDelegate <NSObject>
+
+- (void)download:(KTVHCDownload *)download didCompleteWithError:(NSError *)error;
+- (void)download:(KTVHCDownload *)download didReceiveResponse:(KTVHCDataResponse *)response;
+- (void)download:(KTVHCDownload *)download didReceiveData:(NSData *)data;
+
+@end
+
+@interface KTVHCDownload : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)download;
+
+@property (nonatomic, assign) NSTimeInterval timeoutInterval;
+
+/**
+ *  Header Fields
+ */
+@property (nonatomic, copy) NSArray <NSString *> * whitelistHeaderKeys;
+@property (nonatomic, copy) NSDictionary <NSString *, NSString *> * additionalHeaders;
+
+/**
+ *  Content-Type
+ */
+@property (nonatomic, copy) NSArray <NSString *> * acceptContentTypes;
+@property (nonatomic, copy) BOOL (^unsupportContentTypeFilter)(NSURL * URL, NSString * contentType);
+
+- (NSURLSessionTask *)downloadWithRequest:(KTVHCDataRequest *)request delegate:(id<KTVHCDownloadDelegate>)delegate;
+
+@end

+ 306 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCDownload/KTVHCDownload.m

@@ -0,0 +1,306 @@
+//
+//  KTVHCDataDownload.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCDownload.h"
+#import "KTVHCDataUnitPool.h"
+#import "KTVHCDataStorage.h"
+#import "KTVHCError.h"
+#import "KTVHCLog.h"
+
+#import <UIKit/UIKit.h>
+
+NSString * const KTVHCContentTypeVideo                  = @"video/";
+NSString * const KTVHCContentTypeAudio                  = @"audio/";
+NSString * const KTVHCContentTypeApplicationMPEG4       = @"application/mp4";
+NSString * const KTVHCContentTypeApplicationOctetStream = @"application/octet-stream";
+NSString * const KTVHCContentTypeBinaryOctetStream      = @"binary/octet-stream";
+
+@interface KTVHCDownload () <NSURLSessionDataDelegate, NSLocking>
+
+@property (nonatomic, strong) NSLock * coreLock;
+@property (nonatomic, strong) NSURLSession * session;
+@property (nonatomic, strong) NSOperationQueue * sessionDelegateQueue;
+@property (nonatomic, strong) NSURLSessionConfiguration * sessionConfiguration;
+@property (nonatomic, strong) NSMutableDictionary <NSURLSessionTask *, NSError *> * errorDictionary;
+@property (nonatomic, strong) NSMutableDictionary <NSURLSessionTask *, KTVHCDataRequest *> * requestDictionary;
+@property (nonatomic, strong) NSMutableDictionary <NSURLSessionTask *, id<KTVHCDownloadDelegate>> * delegateDictionary;
+
+@end
+
+@implementation KTVHCDownload
+
++ (instancetype)download
+{
+    static KTVHCDownload * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        self.timeoutInterval = 30.0f;
+        self.errorDictionary = [NSMutableDictionary dictionary];
+        self.requestDictionary = [NSMutableDictionary dictionary];
+        self.delegateDictionary = [NSMutableDictionary dictionary];
+        self.sessionDelegateQueue = [[NSOperationQueue alloc] init];
+        self.sessionDelegateQueue.qualityOfService = NSQualityOfServiceUserInteractive;
+        self.sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
+        self.sessionConfiguration.timeoutIntervalForRequest = self.timeoutInterval;
+        self.sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
+        self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration
+                                                     delegate:self
+                                                delegateQueue:self.sessionDelegateQueue];
+        self.acceptContentTypes = @[KTVHCContentTypeVideo,
+                                    KTVHCContentTypeAudio,
+                                    KTVHCContentTypeApplicationMPEG4,
+                                    KTVHCContentTypeApplicationOctetStream,
+                                    KTVHCContentTypeBinaryOctetStream];
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(applicationDidEnterBackground:)
+                                                     name:UIApplicationDidEnterBackgroundNotification
+                                                   object:[UIApplication sharedApplication]];
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(applicationWillEnterForeground:)
+                                                     name:UIApplicationWillEnterForegroundNotification
+                                                   object:[UIApplication sharedApplication]];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (NSArray <NSString *> *)availableHeaderKeys
+{
+    static NSArray <NSString *> * availableHeaderKeys = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        availableHeaderKeys = @[@"User-Agent",
+                                @"Connection",
+                                @"Accept",
+                                @"Accept-Encoding",
+                                @"Accept-Language",
+                                @"Range"];
+    });
+    return availableHeaderKeys;
+}
+
+- (NSURLSessionTask *)downloadWithRequest:(KTVHCDataRequest *)request delegate:(id<KTVHCDownloadDelegate>)delegate
+{
+    [self lock];
+    NSMutableURLRequest * HTTPRequest = [NSMutableURLRequest requestWithURL:request.URL];
+    [request.headers enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * obj, BOOL * stop) {
+        if ([[self availableHeaderKeys] containsObject:key] || [self.whitelistHeaderKeys containsObject:key]) {
+            [HTTPRequest setValue:obj forHTTPHeaderField:key];
+        }
+    }];
+    HTTPRequest.timeoutInterval = self.timeoutInterval;
+    HTTPRequest.cachePolicy = NSURLRequestReloadIgnoringCacheData;
+    [self.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
+        [HTTPRequest setValue:obj forHTTPHeaderField:key];
+    }];
+    NSURLSessionDataTask * task = [self.session dataTaskWithRequest:HTTPRequest];
+    task.priority = 1.0;
+    [self.requestDictionary setObject:request forKey:task];
+    [self.delegateDictionary setObject:delegate forKey:task];
+    KTVHCLogDownload(@"%p, Add Request\nrequest : %@\nURL : %@\nheaders : %@\nHTTPRequest headers : %@\nCount : %d", self, request, request.URL, request.headers, HTTPRequest.allHTTPHeaderFields, (int)self.delegateDictionary.count);
+    [task resume];
+    [self unlock];
+    return task;
+}
+
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
+{
+    [self lock];
+    KTVHCLogDownload(@"%p, Complete\nError : %@", self, error);
+    id <KTVHCDownloadDelegate> delegate = [self.delegateDictionary objectForKey:task];
+    NSError * cancelError = [self.errorDictionary objectForKey:task];
+    if (cancelError)
+    {
+        error = cancelError;
+    }
+    [delegate download:self didCompleteWithError:error];
+    [self.delegateDictionary removeObjectForKey:task];
+    [self.requestDictionary removeObjectForKey:task];
+    [self.errorDictionary removeObjectForKey:task];
+    if (self.delegateDictionary.count <= 0)
+    {
+        [self cleanBackgroundTaskAsync];
+    }
+    [self unlock];
+}
+
+- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
+{
+    [self lock];
+    NSHTTPURLResponse * HTTPResponse = (NSHTTPURLResponse *)response;
+    KTVHCDataRequest * dataRequest = [self.requestDictionary objectForKey:dataTask];
+    KTVHCDataResponse * dataResponse = [[KTVHCDataResponse alloc] initWithURL:dataRequest.URL headers:HTTPResponse.allHeaderFields];
+    KTVHCLogDownload(@"%p, Receive response\nrequest : %@\nresponse : %@\nHTTPResponse : %@", self, dataRequest, dataResponse, [(NSHTTPURLResponse *)response allHeaderFields]);
+    NSError * error = nil;
+    if (!error)
+    {
+        if (HTTPResponse.statusCode > 400)
+        {
+            error = [KTVHCError errorForResponseUnavailable:dataTask.currentRequest.URL request:dataTask.currentRequest response:dataTask.response];
+        }
+        if (!error)
+        {
+            BOOL contentTypeVaild = NO;
+            if (dataResponse.contentType.length > 0)
+            {
+                for (NSString * obj in self.acceptContentTypes)
+                {
+                    if ([[dataResponse.contentType lowercaseString] containsString:[obj lowercaseString]])
+                    {
+                        contentTypeVaild = YES;
+                    }
+                }
+                if (!contentTypeVaild && self.unsupportContentTypeFilter)
+                {
+                    contentTypeVaild = self.unsupportContentTypeFilter(dataRequest.URL, dataResponse.contentType);
+                }
+            }
+            if (!contentTypeVaild)
+            {
+                error = [KTVHCError errorForUnsupportContentType:dataTask.currentRequest.URL request:dataTask.currentRequest response:dataTask.response];
+            }
+            if (!error)
+            {
+                if (dataResponse.currentLength <= 0 ||
+                    (!KTVHCRangeIsFull(dataRequest.range) &&
+                     (dataResponse.currentLength != KTVHCRangeGetLength(dataRequest.range))))
+                {
+                    error = [KTVHCError errorForUnsupportContentType:dataTask.currentRequest.URL request:dataTask.currentRequest response:dataTask.response];
+                }
+                if (!error)
+                {
+                    long long length = dataResponse.currentLength + [KTVHCDataStorage storage].totalCacheLength - [KTVHCDataStorage storage].maxCacheLength;
+                    if (length > 0)
+                    {
+                        [[KTVHCDataUnitPool pool] deleteUnitsWithLength:length];
+                        length = dataResponse.currentLength + [KTVHCDataStorage storage].totalCacheLength - [KTVHCDataStorage storage].maxCacheLength;
+                        if (length > 0)
+                        {
+                            error = [KTVHCError errorForNotEnoughDiskSpace:dataResponse.totalLength request:dataResponse.currentLength totalCacheLength:[KTVHCDataStorage storage].totalCacheLength maxCacheLength:[KTVHCDataStorage storage].maxCacheLength];
+                        }
+                    }
+                }
+            }
+        }
+    }
+    if (error)
+    {
+        KTVHCLogDownload(@"%p, Invaild response\nError : %@", self, error);
+        [self.errorDictionary setObject:error forKey:dataTask];
+        completionHandler(NSURLSessionResponseCancel);
+    }
+    else
+    {
+        id <KTVHCDownloadDelegate> delegate = [self.delegateDictionary objectForKey:dataTask];
+        [delegate download:self didReceiveResponse:dataResponse];
+        completionHandler(NSURLSessionResponseAllow);
+    }
+    [self unlock];
+}
+
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
+{
+    [self lock];
+    KTVHCLogDownload(@"%p, Perform HTTP redirection\nresponse : %@\nrequest : %@", self, response, request);
+    completionHandler(request);
+    [self unlock];
+}
+
+- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
+{
+    [self lock];
+    KTVHCLogDownload(@"%p, Receive data - Begin\nLength : %lld\nURL : %@", self, (long long)data.length, dataTask.originalRequest.URL.absoluteString);
+    id <KTVHCDownloadDelegate> delegate = [self.delegateDictionary objectForKey:dataTask];
+    [delegate download:self didReceiveData:data];
+    KTVHCLogDownload(@"%p, Receive data - End\nLength : %lld\nURL : %@", self, (long long)data.length, dataTask.originalRequest.URL.absoluteString);
+    [self unlock];
+}
+
+- (void)lock
+{
+    if (!self.coreLock)
+    {
+        self.coreLock = [[NSLock alloc] init];
+    }
+    [self.coreLock lock];
+}
+
+- (void)unlock
+{
+    [self.coreLock unlock];
+}
+
+#pragma mark - Background Task
+
+static UIBackgroundTaskIdentifier backgroundTaskIdentifier = -1;
+
+- (void)applicationDidEnterBackground:(NSNotification *)notification
+{
+    [self cleanBackgroundTask];
+    [self lock];
+    if (self.delegateDictionary.count > 0)
+    {
+        backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+            [self cleanBackgroundTask];
+        }];
+        UIBackgroundTaskIdentifier blockIdentifier = backgroundTaskIdentifier;
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(300 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            if (blockIdentifier == backgroundTaskIdentifier)
+            {
+                [self cleanBackgroundTask];
+            }
+        });
+    }
+    [self unlock];
+}
+
+- (void)applicationWillEnterForeground:(NSNotification *)notification
+{
+    [self cleanBackgroundTask];
+}
+
+- (void)cleanBackgroundTask
+{
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        backgroundTaskIdentifier = UIBackgroundTaskInvalid;
+    });
+    if (backgroundTaskIdentifier != UIBackgroundTaskInvalid)
+    {
+        [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];
+        backgroundTaskIdentifier = UIBackgroundTaskInvalid;
+    }
+}
+
+- (void)cleanBackgroundTaskAsync
+{
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        [self lock];
+        if (self.delegateDictionary.count <= 0)
+        {
+            [self cleanBackgroundTask];
+        }
+        [self unlock];
+    });
+}
+
+@end

+ 16 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.h

@@ -0,0 +1,16 @@
+//
+//  KTVHCHTTPConnection.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCHTTPHeader.h"
+
+@interface KTVHCHTTPConnection : HTTPConnection
+
++ (NSString *)pingResponseValue;
+
+@end

+ 63 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.m

@@ -0,0 +1,63 @@
+//
+//  KTVHCHTTPConnection.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPConnection.h"
+#import "KTVHCHTTPRequest.h"
+#import "KTVHCHTTPResponse.h"
+#import "KTVHCHTTPPingResponse.h"
+#import "KTVHCHTTPURL.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHCHTTPConnection
+
++ (NSString *)pingResponseValue
+{
+    return KTVHCHTTPPingResponseResponseValue;
+}
+
+- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig
+{
+    if (self = [super initWithAsyncSocket:newSocket configuration:aConfig])
+    {
+        KTVHCLogAlloc(self);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
+{
+    KTVHCLogHTTPConnection(@"%p, Receive request\nmethod : %@\npath : %@\nURL : %@", self, method, path, request.url);
+    KTVHCHTTPURL * URL = [[KTVHCHTTPURL alloc] initWithProxyURL:request.url];
+    switch (URL.type)
+    {
+        case KTVHCHTTPURLTypeUnknown:
+            return nil;
+        case KTVHCHTTPURLTypePing:
+        {
+            KTVHCHTTPPingResponse * currentResponse = [KTVHCHTTPPingResponse responseWithConnection:self];
+            return currentResponse;
+        }
+        case KTVHCHTTPURLTypeContent:
+        {
+            KTVHCHTTPRequest * currentRequest = [[KTVHCHTTPRequest alloc] initWithURL:URL.URL headers:request.allHeaderFields];
+            currentRequest.method = request.method;
+            currentRequest.version = request.version;
+            KTVHCHTTPResponse * currentResponse = [[KTVHCHTTPResponse alloc] initWithConnection:self request:currentRequest];
+            return currentResponse;
+        }
+    }
+    return nil;
+}
+
+
+@end

+ 14 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPHeader.h

@@ -0,0 +1,14 @@
+//
+//  KTVHCHTTPHeader.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/12/5.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#ifndef KTVHCHTTPHeader_h
+#define KTVHCHTTPHeader_h
+
+#import <KTVCocoaHTTPServer/KTVCocoaHTTPServer.h>
+
+#endif /* KTVHCHTTPHeader_h */

+ 24 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPPingResponse.h

@@ -0,0 +1,24 @@
+//
+//  KTVHCHTTPPingResponse.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/10/23.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCHTTPHeader.h"
+#import "KTVHCCommon.h"
+
+@class KTVHCHTTPConnection;
+
+KTVHTTPCACHE_EXTERN NSString * const KTVHCHTTPPingResponseResponseValue;
+
+@interface KTVHCHTTPPingResponse : NSObject <HTTPResponse>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)responseWithConnection:(KTVHCHTTPConnection *)connection;
+
+@end

+ 98 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPPingResponse.m

@@ -0,0 +1,98 @@
+//
+//  KTVHCHTTPPingResponse.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/10/23.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPPingResponse.h"
+#import "KTVHCHTTPConnection.h"
+#import "KTVHCLog.h"
+
+NSString * const KTVHCHTTPPingResponseResponseValue = @"pang";
+
+@interface KTVHCHTTPPingResponse ()
+
+@property (nonatomic, weak) KTVHCHTTPConnection * connection;
+@property (nonatomic, strong) NSData * responseData;
+@property (nonatomic, assign) long long readOffset;
+
+@end
+
+@implementation KTVHCHTTPPingResponse
+
++ (instancetype)responseWithConnection:(KTVHCHTTPConnection *)connection
+{
+    return [[self alloc] initWithConnection:connection];
+}
+
+- (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        static NSData * data = nil;
+        static dispatch_once_t onceToken;
+        dispatch_once(&onceToken, ^{
+            data = [KTVHCHTTPPingResponseResponseValue dataUsingEncoding:NSUTF8StringEncoding];
+        });
+        self.responseData = data;
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    NSData * data = nil;
+    NSUInteger readLength = (NSUInteger)MIN(length, self.responseData.length - self.readOffset);
+    if (readLength == self.responseData.length)
+    {
+        data = self.responseData;
+    }
+    else if (readLength > 0)
+    {
+        data = [self.responseData subdataWithRange:NSMakeRange((NSUInteger)self.readOffset, readLength)];
+    }
+    self.readOffset += data.length;
+    KTVHCLogHTTPResponsePing(@"%p, Read data : %lld", self, (long long)data.length);
+    return data;
+}
+
+- (BOOL)delayResponseHeaders
+{
+    return NO;
+}
+
+- (UInt64)contentLength
+{
+    return self.responseData.length;
+}
+
+- (UInt64)offset
+{
+    return self.readOffset;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+    self.readOffset = offset;
+}
+
+- (BOOL)isDone
+{
+    BOOL result = self.readOffset == self.responseData.length;
+    return result;
+}
+
+- (void)connectionDidClose
+{
+    KTVHCLogHTTPResponsePing(@"%p, Connection did closed", self);
+}
+
+@end

+ 23 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPRequest.h

@@ -0,0 +1,23 @@
+//
+//  KTVHCHTTPRequest.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface KTVHCHTTPRequest : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers;
+
+@property (nonatomic, copy, readonly) NSURL * URL;
+@property (nonatomic, copy, readonly) NSDictionary * headers;
+@property (nonatomic, copy) NSString * method;
+@property (nonatomic, copy) NSString * version;
+
+@end

+ 31 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPRequest.m

@@ -0,0 +1,31 @@
+//
+//  KTVHCHTTPRequest.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPRequest.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHCHTTPRequest
+
+- (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _URL = URL;
+        _headers = headers;
+        KTVHCLogHTTPRequest(@"%p, Create reqeust\nURL : %@\nHeaders : %@", self, self.URL, self.headers);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+@end

+ 22 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.h

@@ -0,0 +1,22 @@
+//
+//  KTVHCHTTPResponse.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "KTVHCHTTPHeader.h"
+
+@class KTVHCHTTPConnection;
+@class KTVHCHTTPRequest;
+
+@interface KTVHCHTTPResponse : NSObject <HTTPResponse>
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection request:(KTVHCHTTPRequest *)request;
+
+@end

+ 132 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.m

@@ -0,0 +1,132 @@
+//
+//  KTVHCHTTPResponse.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPResponse.h"
+#import "KTVHCHTTPConnection.h"
+#import "KTVHCHTTPRequest.h"
+#import "KTVHCDataStorage.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCHTTPResponse () <KTVHCDataReaderDelegate>
+
+@property (nonatomic, weak) KTVHCHTTPConnection * connection;
+@property (nonatomic, strong) KTVHCHTTPRequest * request;
+@property (nonatomic, strong) KTVHCDataRequest * dataRequest;
+@property (nonatomic, strong) KTVHCDataReader * reader;
+@property (nonatomic, assign) BOOL waitingResponseHeader;
+
+@end
+
+@implementation KTVHCHTTPResponse
+
+- (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection request:(KTVHCHTTPRequest *)request
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        self.connection = connection;
+        self.request = request;
+        KTVHCDataRequest * dataRequest = [[KTVHCDataRequest alloc] initWithURL:self.request.URL headers:self.request.headers];
+        self.reader = [[KTVHCDataStorage storage] readerWithRequest:dataRequest];
+        self.reader.delegate = self;
+        [self.reader prepare];
+        KTVHCLogHTTPResponse(@"%p, Create response\nrequest : %@", self, self.request);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [self.reader close];
+    KTVHCLogDealloc(self);
+}
+
+#pragma mark - HTTPResponse
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+    NSData * data = [self.reader readDataOfLength:length];
+    KTVHCLogHTTPResponse(@"%p, Read data : %lld", self, (long long)data.length);
+    if (self.reader.didFinished)
+    {
+        KTVHCLogHTTPResponse(@"%p, Read data did finished", self);
+        [self.reader close];
+        [self.connection responseDidAbort:self];
+    }
+    return data;
+}
+
+- (BOOL)delayResponseHeaders
+{
+    BOOL waiting = !self.reader.didPrepared;
+    self.waitingResponseHeader = waiting;
+    KTVHCLogHTTPResponse(@"%p, Delay response : %d", self, self.waitingResponseHeader);
+    return waiting;
+}
+
+- (UInt64)contentLength
+{
+    KTVHCLogHTTPResponse(@"%p, Conetnt length : %lld", self, self.reader.response.totalLength);
+    return self.reader.response.totalLength;
+}
+
+- (NSDictionary *)httpHeaders
+{
+    KTVHCLogHTTPResponse(@"%p, Header\n%@", self, self.reader.response.headersWithoutRangeAndLength);
+    return self.reader.response.headersWithoutRangeAndLength;
+}
+
+- (UInt64)offset
+{
+    KTVHCLogHTTPResponse(@"%p, Offset : %lld", self, self.reader.readOffset);
+    return self.reader.readOffset;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+    KTVHCLogHTTPResponse(@"%p, Set offset : %lld, %lld", self, offset, self.reader.readOffset);
+}
+
+- (BOOL)isDone
+{
+    KTVHCLogHTTPResponse(@"%p, Check done : %d", self, self.reader.didFinished);
+    return self.reader.didFinished;
+}
+
+- (void)connectionDidClose
+{
+    KTVHCLogHTTPResponse(@"%p, Connection did closed : %lld, %lld", self, self.reader.response.currentLength, self.reader.readOffset);
+    [self.reader close];
+}
+
+#pragma mark - KTVHCDataReaderDelegate
+
+- (void)readerDidPrepared:(KTVHCDataReader *)reader
+{
+    KTVHCLogHTTPResponse(@"%p, Prepared", self);
+    if (self.reader.didPrepared && self.waitingResponseHeader == YES)
+    {
+        KTVHCLogHTTPResponse(@"%p, Call connection did prepared", self);
+        [self.connection responseHasAvailableData:self];
+    }
+}
+
+- (void)readerHasAvailableData:(KTVHCDataReader *)reader
+{
+    KTVHCLogHTTPResponse(@"%p, Has available data", self);
+    [self.connection responseHasAvailableData:self];
+}
+
+- (void)reader:(KTVHCDataReader *)reader didFailed:(NSError *)error
+{
+    KTVHCLogHTTPResponse(@"%p, Failed\nError : %@", self, error);
+    [self.reader close];
+    [self.connection responseDidAbort:self];
+}
+
+@end

+ 25 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.h

@@ -0,0 +1,25 @@
+//
+//  KTVHCHTTPServer.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface KTVHCHTTPServer : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)server;
+
+@property (nonatomic, assign, readonly) BOOL running;
+
+- (void)start:(NSError **)error;
+- (void)stop;
+
+- (NSURL *)URLWithOriginalURL:(NSURL *)URL;
+
+@end

+ 166 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.m

@@ -0,0 +1,166 @@
+//
+//  KTVHCHTTPServer.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPServer.h"
+#import "KTVHCHTTPHeader.h"
+#import "KTVHCHTTPConnection.h"
+#import "KTVHCHTTPURL.h"
+#import "KTVHCLog.h"
+
+@interface KTVHCHTTPServer ()
+
+@property (nonatomic, strong) HTTPServer * coreHTTPServer;
+
+@property (nonatomic, assign) BOOL pinging;
+@property (nonatomic, assign) BOOL pingResult;
+@property (nonatomic, strong) NSCondition * pingCondition;
+@property (nonatomic, strong) NSURLSession * pingSession;
+@property (nonatomic, strong) NSURLSessionDataTask * pingTask;
+
+@end
+
+@implementation KTVHCHTTPServer
+
++ (instancetype)server
+{
+    static KTVHCHTTPServer * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (instancetype)init
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+    [self stop];
+}
+
+- (BOOL)restart
+{
+    KTVHCLogHTTPServer(@"%p, Restart connection count : %lld", self, (long long)[self.coreHTTPServer numberOfHTTPConnections]);
+    [self.coreHTTPServer stop];
+    NSError * error = nil;
+    [self.coreHTTPServer start:&error];
+    if (error) {
+        KTVHCLogHTTPServer(@"%p, Restart server failed : %@", self, error);
+    } else {
+        KTVHCLogHTTPServer(@"%p, Restart server success", self);
+    }
+    return error == nil;
+}
+
+- (void)start:(NSError * __autoreleasing *)error
+{
+    self.coreHTTPServer = [[HTTPServer alloc] init];
+    [self.coreHTTPServer setConnectionClass:[KTVHCHTTPConnection class]];
+    [self.coreHTTPServer setType:@"_http._tcp."];
+    NSError * tempError = nil;
+    [self.coreHTTPServer start:&tempError];
+    if (tempError) {
+        * error = tempError;
+        KTVHCLogHTTPServer(@"%p, Start server failed : %@", self, tempError);
+    } else {
+        KTVHCLogHTTPServer(@"%p, Start server success", self);
+    }
+}
+
+- (void)stop
+{
+    if (self.running)
+    {
+        [self.coreHTTPServer stop];
+        [self.pingSession invalidateAndCancel];
+        [self.pingTask cancel];
+        self.pingTask = nil;
+        self.pingSession = nil;
+        KTVHCLogHTTPServer(@"%p, Stop server", self);
+    }
+}
+
+- (NSURL *)URLWithOriginalURL:(NSURL *)URL
+{
+    BOOL success = NO;
+    for (int i = 0; i < 2 && !success && self.running && [URL.scheme hasPrefix:@"http"]; i++)
+    {
+        if (i > 0)
+        {
+            [self restart];
+        }
+        success = [self ping];
+        KTVHCLogHTTPServer(@"%p, Ping\nsuccess : %d\nindex : %d", self, success, i);
+    }
+    if (success)
+    {
+        KTVHCHTTPURL * HCURL = [[KTVHCHTTPURL alloc] initWithOriginalURL:URL];
+        URL = [HCURL proxyURLWithPort:self.coreHTTPServer.listeningPort];
+    }
+    KTVHCLogHTTPServer(@"%p, Return URL\nURL : %@", self, URL);
+    return URL;
+}
+
+- (BOOL)ping
+{
+    if (self.running)
+    {
+        if (!self.pingCondition)
+        {
+            self.pingCondition = [[NSCondition alloc] init];
+        }
+        [self.pingCondition lock];
+        if (self.pinging)
+        {
+            [self.pingCondition wait];
+        }
+        else
+        {
+            NSURL * pingURL = [[KTVHCHTTPURL pingURL] proxyURLWithPort:self.coreHTTPServer.listeningPort];
+            if (!self.pingSession)
+            {
+                NSURLSessionConfiguration * sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
+                sessionConfiguration.timeoutIntervalForRequest = 3;
+                self.pingSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
+            }
+            self.pingTask = [self.pingSession dataTaskWithURL:pingURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+                [self.pingCondition lock];
+                if (!error && data.length > 0) {
+                    NSString * pang = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+                    self.pingResult = [pang isEqualToString:[KTVHCHTTPConnection pingResponseValue]];
+                } else {
+                    self.pingResult = NO;
+                }
+                self.pinging = NO;
+                [self.pingCondition broadcast];
+                [self.pingCondition unlock];
+            }];
+            self.pinging = YES;
+            [self.pingTask resume];
+            [self.pingCondition wait];
+        }
+        [self.pingCondition unlock];
+    }
+    KTVHCLogHTTPServer(@"%p, Ping result : %d", self, self.pingResult);
+    return self.pingResult;
+}
+
+- (BOOL)running
+{
+    return self.coreHTTPServer.isRunning;
+}
+
+@end

+ 32 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPURL.h

@@ -0,0 +1,32 @@
+//
+//  KTVHCHTTPURL.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NS_ENUM(NSUInteger, KTVHCHTTPURLType)
+{
+    KTVHCHTTPURLTypeUnknown,
+    KTVHCHTTPURLTypeContent,
+    KTVHCHTTPURLTypePing,
+};
+
+@interface KTVHCHTTPURL : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)pingURL;
+- (instancetype)initWithProxyURL:(NSURL *)URL;
+- (instancetype)initWithOriginalURL:(NSURL *)URL;
+
+@property (nonatomic, assign, readonly) KTVHCHTTPURLType type;
+@property (nonatomic, copy, readonly) NSURL * URL;
+
+- (NSURL *)proxyURLWithPort:(NSInteger)port;
+
+@end

+ 116 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPURL.m

@@ -0,0 +1,116 @@
+//
+//  KTVHCHTTPURL.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCHTTPURL.h"
+#import "KTVHCURLTools.h"
+#import "KTVHCLog.h"
+
+static NSString * const kKTVHCHTTPURLRequestURLKey      = @"originalURL";
+static NSString * const kKTVHCHTTPURLRequestTypeKey     = @"requestType";
+static NSString * const kKTVHCHTTPURLRequestTypeContent = @"content";
+static NSString * const kKTVHCHTTPURLRequestTypePing    = @"ping";
+
+@implementation KTVHCHTTPURL
+
++ (instancetype)pingURL
+{
+    static KTVHCHTTPURL * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        NSURL * URL = [NSURL URLWithString:@"KTVHTTPCache"];
+        obj = [[KTVHCHTTPURL alloc] initWithOriginalURL:URL];
+        obj->_type = KTVHCHTTPURLTypePing;
+    });
+    return obj;
+}
+
+- (instancetype)initWithProxyURL:(NSURL *)URL
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        NSRange requestTypeRange = [URL.absoluteString rangeOfString:[NSString stringWithFormat:@"%@=", kKTVHCHTTPURLRequestTypeKey]];
+        if (requestTypeRange.location != NSNotFound)
+        {
+            NSString * paramString = [URL.absoluteString substringFromIndex:requestTypeRange.location];
+            NSCharacterSet * delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&"];
+            NSScanner * scanner = [[NSScanner alloc] initWithString:paramString];
+            while (![scanner isAtEnd])
+            {
+                NSString * tupleString = nil;
+                [scanner scanUpToCharactersFromSet:delimiterSet intoString:&tupleString];
+                [scanner scanCharactersFromSet:delimiterSet intoString:NULL];
+                NSArray <NSString *> * tuple = [tupleString componentsSeparatedByString:@"="];
+                if (tuple.count == 2)
+                {
+                    NSString * key = tuple.firstObject;
+                    NSString * value = tuple.lastObject;
+                    if ([key isEqualToString:kKTVHCHTTPURLRequestURLKey])
+                    {
+                        _URL = [NSURL URLWithString:[KTVHCURLTools URLDecode:value]];
+                    }
+                    else if ([key isEqualToString:kKTVHCHTTPURLRequestTypeKey])
+                    {
+                        if ([value isEqualToString:kKTVHCHTTPURLRequestTypePing])
+                        {
+                            _type = KTVHCHTTPURLTypePing;
+                        }
+                        else if ([value isEqualToString:kKTVHCHTTPURLRequestTypeContent])
+                        {
+                            _type = KTVHCHTTPURLTypeContent;
+                        }
+                    }
+                }
+            }
+        }
+        KTVHCLogHTTPURL(@"%p, Proxy URL\n%@\n%@", self, URL, self.URL);
+    }
+    return self;
+}
+
+- (instancetype)initWithOriginalURL:(NSURL *)URL
+{
+    if (self = [super init])
+    {
+        KTVHCLogAlloc(self);
+        _URL = URL;
+        _type = KTVHCHTTPURLTypeContent;
+        KTVHCLogHTTPURL(@"%p, Original URL\n%@", self, self.URL);
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    KTVHCLogDealloc(self);
+}
+
+- (NSURL *)proxyURLWithPort:(NSInteger)port
+{
+    NSString * pathExtension = @"";
+    if (self.URL.pathExtension.length > 0)
+    {
+        pathExtension = [NSString stringWithFormat:@".%@", self.URL.pathExtension];
+    }
+    NSString * requestType = kKTVHCHTTPURLRequestTypeContent;
+    if (self.type == KTVHCHTTPURLTypePing)
+    {
+        requestType = kKTVHCHTTPURLRequestTypePing;
+    }
+    NSString * originalURLString = [KTVHCURLTools URLEncode:self.URL.absoluteString];
+    NSString * URLString = [NSString stringWithFormat:@"http://localhost:%d/request%@?%@=%@&%@=%@",
+                            (int)port,
+                            pathExtension,
+                            kKTVHCHTTPURLRequestTypeKey, requestType,
+                            kKTVHCHTTPURLRequestURLKey, originalURLString];
+    NSURL * URL = [NSURL URLWithString:URLString];
+    KTVHCLogHTTPURL(@"%p, Proxy URL\n%@", self, URL);
+    return URL;
+}
+
+@end

+ 34 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTools.h

@@ -0,0 +1,34 @@
+//
+//  KTVHCPathTools.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface KTVHCPathTools : NSObject
+
++ (NSString *)logPath;
++ (NSString *)archivePath;
+
++ (NSString *)directoryPathWithURL:(NSURL *)URL;
++ (NSString *)completeFilePathWithURL:(NSURL *)URL;
++ (NSString *)unitItemPathWithURL:(NSURL *)URL offset:(long long)offset;
+
++ (BOOL)isRelativePath:(NSString *)path;
++ (BOOL)isAbsolutePath:(NSString *)path;
+
++ (NSString *)relativePathWithAbsoultePath:(NSString *)path;
++ (NSString *)absoultePathWithRelativePath:(NSString *)path;
+
++ (void)createFileAtPath:(NSString *)path;
++ (void)createDirectoryAtPath:(NSString *)path;
+
++ (NSError *)deleteFileAtPath:(NSString *)path;
++ (NSError *)deleteDirectoryAtPath:(NSString *)path;
+
++ (long long)sizeOfItemAtPath:(NSString *)path;
+
+@end

+ 184 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTools.m

@@ -0,0 +1,184 @@
+//
+//  KTVHCPathTools.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/12.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCPathTools.h"
+#import "KTVHCURLTools.h"
+
+@implementation KTVHCPathTools
+
++ (NSString *)rootDirectory
+{
+    static NSString * obj = @"KTVHTTPCache";
+    [self createDirectoryAtPath:obj];
+    return obj;
+}
+
++ (NSString *)logPath
+{
+    NSString * path = [[self rootDirectory] stringByAppendingPathComponent:@"KTVHTTPCache.log"];
+    return [self absoultePathWithRelativePath:path];
+}
+
++ (NSString *)archivePath
+{
+    NSString * path = [[self rootDirectory] stringByAppendingPathComponent:@"KTVHTTPCache.archive"];
+    return [self absoultePathWithRelativePath:path];
+}
+
++ (NSString *)directoryPathWithURL:(NSURL *)URL
+{
+    NSString * name = [KTVHCURLTools keyWithURL:URL];
+    NSString * path = [[self rootDirectory] stringByAppendingPathComponent:name];
+    [self createDirectoryAtPath:path];
+    return [self absoultePathWithRelativePath:path];
+}
+
++ (NSString *)completeFilePathWithURL:(NSURL *)URL
+{
+    NSString * fileName = [KTVHCURLTools keyWithURL:URL];
+    fileName = [fileName stringByAppendingPathExtension:URL.pathExtension];
+    NSString * directoryPath = [self directoryPathWithURL:URL];
+    NSString * filePath = [directoryPath stringByAppendingPathComponent:fileName];
+    return [self absoultePathWithRelativePath:filePath];
+}
+
++ (NSString *)unitItemPathWithURL:(NSURL *)URL offset:(long long)offset
+{
+    NSString * baseFileName = [KTVHCURLTools keyWithURL:URL];
+    NSString * directoryPath = [self directoryPathWithURL:URL];
+    int number = 0;
+    NSString * filePath = nil;
+    while (!filePath)
+    {
+        NSString * fileName = [NSString stringWithFormat:@"%@_%lld_%d", baseFileName, offset, number];
+        NSString * currentFilePath = [directoryPath stringByAppendingPathComponent:fileName];
+        if (![[NSFileManager defaultManager] fileExistsAtPath:currentFilePath])
+        {
+            [[NSFileManager defaultManager] createFileAtPath:currentFilePath contents:nil attributes:nil];
+            filePath = currentFilePath;
+        }
+        number++;
+    }
+    return [self absoultePathWithRelativePath:filePath];;
+}
+
++ (NSString *)basePath
+{
+    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
+}
+
++ (BOOL)isRelativePath:(NSString *)path
+{
+    return ![path hasPrefix:[self basePath]];
+}
+
++ (BOOL)isAbsolutePath:(NSString *)path
+{
+    return [path hasPrefix:[self basePath]];
+}
+
++ (NSString *)relativePathWithAbsoultePath:(NSString *)path
+{
+    if ([self isAbsolutePath:path])
+    {
+        path = [path stringByReplacingOccurrencesOfString:[self basePath] withString:@""];
+    }
+    return path;
+}
+
++ (NSString *)absoultePathWithRelativePath:(NSString *)path
+{
+    if ([self isRelativePath:path])
+    {
+        path = [[self basePath] stringByAppendingPathComponent:path];;
+    }
+    return path;
+}
+
++ (void)createFileAtPath:(NSString *)path
+{
+    if (path.length <= 0)
+    {
+        return;
+    }
+    path = [self absoultePathWithRelativePath:path];
+    BOOL isDirectory = NO;
+    BOOL isExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
+    if (!isExists || isDirectory)
+    {
+        [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
+    }
+}
+
++ (void)createDirectoryAtPath:(NSString *)path
+{
+    if (path.length <= 0)
+    {
+        return;
+    }
+    path = [self absoultePathWithRelativePath:path];
+    BOOL isDirectory = NO;
+    BOOL isExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
+    if (!isExists || !isDirectory)
+    {
+        [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+    }
+}
+
++ (NSError *)deleteFileAtPath:(NSString *)path
+{
+    if (path.length <= 0)
+    {
+        return nil;
+    }
+    path = [self absoultePathWithRelativePath:path];
+    NSError * error = nil;
+    BOOL isDirectory = NO;
+    BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
+    if (result && !isDirectory)
+    {
+        result = [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
+    }
+    return error;
+}
+
++ (NSError *)deleteDirectoryAtPath:(NSString *)path
+{
+    if (path.length <= 0)
+    {
+        return nil;
+    }
+    path = [self absoultePathWithRelativePath:path];
+    NSError * error = nil;
+    BOOL isDirectory = NO;
+    BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
+    if (result && isDirectory)
+    {
+        result = [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
+    }
+    return error;
+}
+
++ (long long)sizeOfItemAtPath:(NSString *)path
+{
+    if (path.length <= 0)
+    {
+        return 0;
+    }
+    path = [self absoultePathWithRelativePath:path];
+    NSError * error;
+    NSDictionary <NSFileAttributeKey, id> * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
+    if (!error || attributes.count > 0)
+    {
+        NSNumber * size = [attributes objectForKey:NSFileSize];
+        return size.longLongValue;
+    }
+    return 0;
+}
+
+@end

+ 27 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTools.h

@@ -0,0 +1,27 @@
+//
+//  KTVHCURLTools.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NSURL * (^KTVHCURLFilter)(NSURL * URL);
+
+@interface KTVHCURLTools : NSObject
+
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype)URLTools;
+
+@property (nonatomic, copy) KTVHCURLFilter URLFilter;
+
++ (NSString *)keyWithURL:(NSURL *)URL;
+
++ (NSString *)URLEncode:(NSString *)URLString;
++ (NSString *)URLDecode:(NSString *)URLString;
+
+@end

+ 145 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTools.m

@@ -0,0 +1,145 @@
+//
+//  KTVHCURLTools.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHCURLTools.h"
+#import <CommonCrypto/CommonCrypto.h>
+
+@implementation KTVHCURLTools
+
++ (instancetype)URLTools
+{
+    static KTVHCURLTools * obj = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        obj = [[self alloc] init];
+    });
+    return obj;
+}
+
+- (NSURL *)URLThroughURLFilter:(NSURL *)URL
+{
+    if (self.URLFilter && URL.absoluteString.length > 0)
+    {
+        NSURL * retURL = self.URLFilter(URL);
+        if (retURL.absoluteString.length > 0)
+        {
+            return retURL;
+        }
+    }
+    return URL;
+}
+
++ (NSString *)keyWithURL:(NSURL *)URL
+{
+    URL = [[KTVHCURLTools URLTools] URLThroughURLFilter:URL];
+    return [self md5:URL.absoluteString];
+}
+
++ (NSString *)md5:(NSString *)URLString
+{
+    const char * value = [URLString UTF8String];
+    unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
+    CC_MD5(value, (CC_LONG)strlen(value), outputBuffer);
+    NSMutableString * outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
+    for (NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++)
+    {
+        [outputString appendFormat:@"%02x", outputBuffer[count]];
+    }
+    return outputString;
+}
+
++ (NSString *)base64Encode:(NSString *)URLString
+{
+    NSData * data = [URLString dataUsingEncoding:NSUTF8StringEncoding];
+    return [data base64EncodedStringWithOptions:0];
+}
+
++ (NSString *)base64Decode:(NSString *)URLString
+{
+    NSData * data = [[NSData alloc] initWithBase64EncodedString:URLString options:0];
+    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+}
+
++ (NSString *)URLEncode:(NSString *)URLString
+{
+    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+    NSUInteger length = [URLString length];
+    const char * c = [URLString UTF8String];
+    NSString * resultString = @"";
+    for(int i = 0; i < length; i++)
+    {
+        switch (*c)
+        {
+            case '/':
+                resultString = [resultString stringByAppendingString:@"%2F"];
+                break;
+            case '\'':
+                resultString = [resultString stringByAppendingString:@"%27"];
+                break;
+            case ';':
+                resultString = [resultString stringByAppendingString:@"%3B"];
+                break;
+            case '?':
+                resultString = [resultString stringByAppendingString:@"%3F"];
+                break;
+            case ':':
+                resultString = [resultString stringByAppendingString:@"%3A"];
+                break;
+            case '@':
+                resultString = [resultString stringByAppendingString:@"%40"];
+                break;
+            case '&':
+                resultString = [resultString stringByAppendingString:@"%26"];
+                break;
+            case '=':
+                resultString = [resultString stringByAppendingString:@"%3D"];
+                break;
+            case '+':
+                resultString = [resultString stringByAppendingString:@"%2B"];
+                break;
+            case '$':
+                resultString = [resultString stringByAppendingString:@"%24"];
+                break;
+            case ',':
+                resultString = [resultString stringByAppendingString:@"%2C"];
+                break;
+            case '[':
+                resultString = [resultString stringByAppendingString:@"%5B"];
+                break;
+            case ']':
+                resultString = [resultString stringByAppendingString:@"%5D"];
+                break;
+            case '#':
+                resultString = [resultString stringByAppendingString:@"%23"];
+                break;
+            case '!':
+                resultString = [resultString stringByAppendingString:@"%21"];
+                break;
+            case '(':
+                resultString = [resultString stringByAppendingString:@"%28"];
+                break;
+            case ')':
+                resultString = [resultString stringByAppendingString:@"%29"];
+                break;
+            case '*':
+                resultString = [resultString stringByAppendingString:@"%2A"];
+                break;
+            default:
+                resultString = [resultString stringByAppendingFormat:@"%c", *c];
+        }
+        c++;
+    }
+    return resultString;
+}
+
++ (NSString *)URLDecode:(NSString *)URLString
+{
+    return [URLString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVHTTPCache.framework/Headers/KTVHTTPCacheImp.h → Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHTTPCacheImp.h


+ 224 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/Classes/KTVHTTPCacheImp.m

@@ -0,0 +1,224 @@
+//
+//  KTVHTTPCacheImp.m
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/13.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import "KTVHTTPCacheImp.h"
+#import "KTVHCHTTPServer.h"
+#import "KTVHCDataStorage.h"
+#import "KTVHCDownload.h"
+#import "KTVHCURLTools.h"
+#import "KTVHCLog.h"
+
+@implementation KTVHTTPCache
+
+#pragma mark - HTTP Server
+
++ (void)proxyStart:(NSError * __autoreleasing *)error
+{
+    [[KTVHCHTTPServer server] start:error];
+}
+
++ (void)proxyStop
+{
+    [[KTVHCHTTPServer server] stop];
+}
+
++ (BOOL)proxyIsRunning
+{
+    return [KTVHCHTTPServer server].running;
+}
+
++ (NSURL *)proxyURLWithOriginalURL:(NSURL *)URL
+{
+    URL = [[KTVHCHTTPServer server] URLWithOriginalURL:URL];
+    return URL;
+}
+
++ (NSString *)proxyURLStringWithOriginalURLString:(NSString *)URLString
+{
+    NSURL * URL = [NSURL URLWithString:URLString];
+    URL = [[KTVHCHTTPServer server] URLWithOriginalURL:URL];
+    return URL.absoluteString;
+}
+
+#pragma mark - Data Storage
+
++ (NSURL *)cacheCompleteFileURLIfExistedWithURL:(NSURL *)URL
+{
+    URL = [[KTVHCDataStorage storage] completeFileURLIfExistedWithURL:URL];
+    return URL;
+}
+
++ (NSString *)cacheCompleteFilePathIfExistedWithURLString:(NSString *)URLString
+{
+    NSURL * URL = [NSURL URLWithString:URLString];
+    URL = [[KTVHCDataStorage storage] completeFileURLIfExistedWithURL:URL];
+    return URL.path;
+}
+
++ (KTVHCDataReader *)cacheReaderWithRequest:(KTVHCDataRequest *)request
+{
+    return [[KTVHCDataStorage storage] readerWithRequest:request];
+}
+
++ (KTVHCDataLoader *)cacheLoaderWithRequest:(KTVHCDataRequest *)request
+{
+    return [[KTVHCDataStorage storage] loaderWithRequest:request];
+}
+
++ (void)cacheSetMaxCacheLength:(long long)maxCacheLength
+{
+    [KTVHCDataStorage storage].maxCacheLength = maxCacheLength;
+}
+
++ (long long)cacheMaxCacheLength
+{
+    return [KTVHCDataStorage storage].maxCacheLength;
+}
+
++ (long long)cacheTotalCacheLength
+{
+    return [KTVHCDataStorage storage].totalCacheLength;
+}
+
++ (KTVHCDataCacheItem *)cacheCacheItemWithURL:(NSURL *)URL
+{
+    return [[KTVHCDataStorage storage] cacheItemWithURL:URL];
+}
+
++ (KTVHCDataCacheItem *)cacheCacheItemWithURLString:(NSString *)URLString
+{
+    NSURL * URL = [NSURL URLWithString:URLString];
+    return [[KTVHCDataStorage storage] cacheItemWithURL:URL];
+}
+
++ (NSArray<KTVHCDataCacheItem *> *)cacheAllCacheItems
+{
+    return [[KTVHCDataStorage storage] allCacheItems];
+}
+
++ (void)cacheDeleteCacheWithURL:(NSURL *)URL
+{
+    [[KTVHCDataStorage storage] deleteCacheWithURL:URL];
+}
+
++ (void)cacheDeleteCacheWithURLString:(NSString *)URLString
+{
+    NSURL * URL = [NSURL URLWithString:URLString];
+    [[KTVHCDataStorage storage] deleteCacheWithURL:URL];
+}
+
++ (void)cacheDeleteAllCaches
+{
+    [[KTVHCDataStorage storage] deleteAllCaches];
+}
+
+#pragma mark - Token
+
++ (void)tokenSetURLFilter:(NSURL * (^)(NSURL * URL))URLFilter
+{
+    [KTVHCURLTools URLTools].URLFilter = URLFilter;
+}
+
+#pragma mark - Download
+
++ (void)downloadSetTimeoutInterval:(NSTimeInterval)timeoutInterval
+{
+    [KTVHCDownload download].timeoutInterval = timeoutInterval;
+}
+
++ (NSTimeInterval)downloadTimeoutInterval
+{
+    return [KTVHCDownload download].timeoutInterval;
+}
+
++ (void)downloadSetWhitelistHeaderKeys:(NSArray <NSString *> *)whitelistHeaderKeys
+{
+    [KTVHCDownload download].whitelistHeaderKeys = whitelistHeaderKeys;
+}
+
++ (NSArray <NSString *> *)downloadWhitelistHeaderKeys
+{
+    return [KTVHCDownload download].whitelistHeaderKeys;
+}
+
++ (void)downloadSetAdditionalHeaders:(NSDictionary <NSString *, NSString *> *)additionalHeaders
+{
+    [KTVHCDownload download].additionalHeaders = additionalHeaders;
+}
+
++ (NSDictionary <NSString *, NSString *> *)downloadAdditionalHeaders
+{
+    return [KTVHCDownload download].additionalHeaders;
+}
+
++ (void)downloadSetAcceptContentTypes:(NSArray <NSString *> *)acceptContentTypes
+{
+    [KTVHCDownload download].acceptContentTypes = acceptContentTypes;
+}
+
++ (NSArray <NSString *> *)downloadAcceptContentTypes
+{
+    return [KTVHCDownload download].acceptContentTypes;
+}
+
++ (void)downloadSetUnsupportContentTypeFilter:(BOOL(^)(NSURL * URL, NSString * contentType))contentTypeFilter
+{
+    [KTVHCDownload download].unsupportContentTypeFilter = contentTypeFilter;
+}
+
+#pragma mark - Log
+
++ (void)logAddLog:(NSString *)log
+{
+    if (log.length > 0)
+    {
+        KTVHCLogCommon(@"%@", log);
+    }
+}
+
++ (void)logSetConsoleLogEnable:(BOOL)consoleLogEnable
+{
+    [KTVHCLog log].consoleLogEnable = consoleLogEnable;
+}
+
++ (BOOL)logConsoleLogEnable
+{
+    return [KTVHCLog log].consoleLogEnable;
+}
+
++ (BOOL)logRecordLogEnable
+{
+    return [KTVHCLog log].recordLogEnable;
+}
+
++ (NSString *)logRecordLogFilePath
+{
+    return [KTVHCLog log].recordLogFilePath;
+}
+
++ (void)logSetRecordLogEnable:(BOOL)recordLogEnable
+{
+    [KTVHCLog log].recordLogEnable = recordLogEnable;
+}
+
++ (void)logDeleteRecordLog
+{
+    [[KTVHCLog log] deleteRecordLog];
+}
+
++ (NSArray<NSError *> *)logAllErrors
+{
+    return [[KTVHCLog log] allErrors];
+}
+
++ (NSError *)logLastError
+{
+    return [[KTVHCLog log] lastError];
+}
+
+@end

+ 27 - 0
Carthage/Checkouts/KTVHTTPCache/KTVHTTPCache/KTVHTTPCache.h

@@ -0,0 +1,27 @@
+//
+//  KTVHTTPCache.h
+//  KTVHTTPCache
+//
+//  Created by Single on 2017/8/10.
+//  Copyright © 2017年 Single. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#pragma mark - Interface
+
+#import "KTVHTTPCacheImp.h"
+
+#pragma mark - Data Storage
+
+#import "KTVHCDataReader.h"
+#import "KTVHCDataLoader.h"
+#import "KTVHCDataRequest.h"
+#import "KTVHCDataResponse.h"
+#import "KTVHCDataCacheItem.h"
+#import "KTVHCDataCacheItemZone.h"
+
+#pragma mark - Common
+
+#import "KTVHCRange.h"
+#import "KTVHCCommon.h"

+ 21 - 0
Carthage/Checkouts/KTVHTTPCache/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 唱吧
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 24 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/Info.plist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0.0</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Headers/KTVCocoaHTTPServer.h → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/KTVCocoaHTTPServer.h


+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Modules/module.modulemap → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/Framework/module.modulemap


+ 16 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer.podspec

@@ -0,0 +1,16 @@
+Pod::Spec.new do |s|
+  s.name                = "KTVCocoaHTTPServer"
+  s.version             = "1.0.0"
+  s.summary             = "CocoaHTTPServer for KTV."
+  s.homepage            = "https://github.com/ChangbaDevs/KTVCocoaHTTPServer"
+  s.license             = { :type => "MIT", :file => "LICENSE" }
+  s.author              = { "Single" => "libobjc@gmail.com" }
+  s.social_media_url    = "https://weibo.com/3118550737"
+  s.platform            = :ios, "8.0"
+  s.source              = { :git => "https://github.com/ChangbaDevs/KTVCocoaHTTPServer.git", :tag => "#{s.version}" }
+  s.source_files        = "KTVCocoaHTTPServer", "KTVCocoaHTTPServer/**/*.{h,m}"
+  s.public_header_files = "KTVCocoaHTTPServer/**/*.h"
+  s.frameworks          = "Foundation"
+  s.requires_arc        = true
+  s.dependency 'CocoaAsyncSocket'
+end

+ 14 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDData.h

@@ -0,0 +1,14 @@
+#import <Foundation/Foundation.h>
+
+@interface NSData (DDData)
+
+- (NSData *)md5Digest;
+
+- (NSData *)sha1Digest;
+
+- (NSString *)hexStringValue;
+
+- (NSString *)base64Encoded;
+- (NSData *)base64Decoded;
+
+@end

+ 158 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDData.m

@@ -0,0 +1,158 @@
+#import "DDData.h"
+#import <CommonCrypto/CommonDigest.h>
+
+
+@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

+ 12 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Categories/DDNumber.h

@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+
+@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

+ 88 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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

+ 56 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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 <Foundation/NSValue.h>
+#import <Foundation/NSObjCRuntime.h>
+
+@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

+ 104 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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

+ 45 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPAuthenticationRequest.h

@@ -0,0 +1,45 @@
+#import <Foundation/Foundation.h>
+
+#if TARGET_OS_IPHONE
+  // Note: You may need to add the CFNetwork Framework to your project
+  #import <CFNetwork/CFNetwork.h>
+#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

+ 195 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPAuthenticationRequest.m

@@ -0,0 +1,195 @@
+#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
+{
+	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

+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Headers/HTTPConnection.h → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPConnection.h


+ 2708 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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<HTTPResponse> *)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 = @"<html><body>Error 404 - Not Found</body></html>";
+	//     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<HTTPResponse> *)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<HTTPResponse> *)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

+ 46 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPLogging.h

@@ -0,0 +1,46 @@
+//
+//  HTTPLogging.h
+//  CocoaHTTPServer
+//
+//  Created by Single on 2018/5/18.
+//  Copyright © 2018年 Single. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#define KTVCHSLog(level, frmt, ...) [HTTPLogging log:level format:frmt, ##__VA_ARGS__]
+
+#define THIS_FILE   self
+#define THIS_METHOD NSStringFromSelector(_cmd)
+
+#define HTTP_LOG_FLAG_ERROR   (1 << 0)
+#define HTTP_LOG_FLAG_WARN    (1 << 1)
+#define HTTP_LOG_FLAG_INFO    (1 << 2)
+#define HTTP_LOG_FLAG_VERBOSE (1 << 3)
+#define HTTP_LOG_FLAG_TRACE   (1 << 4)
+
+#define HTTP_LOG_LEVEL_OFF     0
+#define HTTP_LOG_LEVEL_ERROR   (HTTP_LOG_LEVEL_OFF   | HTTP_LOG_FLAG_ERROR)
+#define HTTP_LOG_LEVEL_WARN    (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN)
+#define HTTP_LOG_LEVEL_INFO    (HTTP_LOG_LEVEL_WARN  | HTTP_LOG_FLAG_INFO)
+#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO  | HTTP_LOG_FLAG_VERBOSE)
+
+#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)
+
+#define HTTPLogError(frmt, ...)    KTVCHSLog(HTTP_LOG_ERROR,   frmt, ##__VA_ARGS__)
+#define HTTPLogWarn(frmt, ...)     KTVCHSLog(HTTP_LOG_WARN,    frmt, ##__VA_ARGS__)
+#define HTTPLogInfo(frmt, ...)     KTVCHSLog(HTTP_LOG_INFO,    frmt, ##__VA_ARGS__)
+#define HTTPLogVerbose(frmt, ...)  KTVCHSLog(HTTP_LOG_VERBOSE, frmt, ##__VA_ARGS__)
+#define HTTPLogTrace()             KTVCHSLog(HTTP_LOG_TRACE,   @"%@ : %@", THIS_FILE, THIS_METHOD)
+#define HTTPLogTrace2(frmt, ...)   KTVCHSLog(HTTP_LOG_TRACE,   frmt, ##__VA_ARGS__)
+
+
+@interface HTTPLogging : NSObject
+
++ (void)log:(int)level format:(NSString *)format, ...;
+
+@end

+ 18 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPLogging.m

@@ -0,0 +1,18 @@
+//
+//  HTTPLogging.h
+//  CocoaHTTPServer
+//
+//  Created by Single on 2018/5/18.
+//  Copyright © 2018年 Single. All rights reserved.
+//
+
+#import "HTTPLogging.h"
+
+@implementation HTTPLogging
+
++ (void)log:(int)level format:(NSString *)format, ...
+{
+    
+}
+
+@end

+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Headers/HTTPMessage.h → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPMessage.h


+ 113 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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

+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Headers/HTTPResponse.h → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPResponse.h


+ 0 - 0
Libraries external/KTVHTTPCache/KTVCocoaHTTPServer.framework/Headers/HTTPServer.h → Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPServer.h


+ 772 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/HTTPServer.m

@@ -0,0 +1,772 @@
+#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;
+		
+		// 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.
+**/
+- (UInt16)port
+{
+	__block UInt16 result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = port;
+	});
+	
+    return result;
+}
+
+- (UInt16)listeningPort
+{
+	__block UInt16 result;
+	
+	dispatch_sync(serverQueue, ^{
+		if (isRunning)
+			result = [asyncSocket localPort];
+		else
+			result = 0;
+	});
+	
+	return result;
+}
+
+- (void)setPort:(UInt16)value
+{
+	HTTPLogTrace();
+	
+	dispatch_async(serverQueue, ^{
+		port = value;
+	});
+}
+
+/**
+ * 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 {
+		
+		success = [asyncSocket acceptOnInterface:interface port:port error:&err];
+		if (success)
+		{
+			HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
+			
+			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:<port>/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]];
+	[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 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];
+}
+
+@end

+ 65 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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 <NSObject> 
+@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<MultipartFormDataParserDelegate>                  delegate;
+#else
+	__unsafe_unretained id<MultipartFormDataParserDelegate>     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

+ 529 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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

+ 33 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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 <Foundation/Foundation.h>
+
+
+//-----------------------------------------------------------------
+// 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

+ 86 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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

+ 23 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Mime/MultipartMessageHeaderField.h

@@ -0,0 +1,23 @@
+
+#import <Foundation/Foundation.h>
+
+//-----------------------------------------------------------------
+// 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

+ 211 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/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 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;
+}
+

+ 75 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Responses/HTTPAsyncFileResponse.h

@@ -0,0 +1,75 @@
+#import <Foundation/Foundation.h>
+#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 <HTTPResponse>
+{	
+	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.
+**/

+ 405 - 0
Carthage/Checkouts/KTVHTTPCache/Vendors/KTVCocoaHTTPServer/KTVCocoaHTTPServer/Classes/Responses/HTTPAsyncFileResponse.m

@@ -0,0 +1,405 @@
+#import "HTTPAsyncFileResponse.h"
+#import "HTTPConnection.h"
+#import "HTTPLogging.h"
+
+#import <unistd.h>
+#import <fcntl.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
+
+/**
+ * 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

Some files were not shown because too many files changed in this diff