Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Lots of changes for the second release

Major new features include:
 - Universal binary
 - Bookmarks and bookmark sync
 - Offline browsing
 - Login / register
  • Loading branch information...
commit 1b264b27ea2b5c87089554d92960e5fb212b7897 1 parent b32c573
@dmpatierno dmpatierno authored
Showing with 33,119 additions and 3,300 deletions.
  1. +4 −0 .gitignore
  2. +338 −0 AreasView.xib
  3. +35 −0 Classes/ASIHTTPRequest/ASIAuthenticationDialog.h
  4. +487 −0 Classes/ASIHTTPRequest/ASIAuthenticationDialog.m
  5. +103 −0 Classes/ASIHTTPRequest/ASICacheDelegate.h
  6. +42 −0 Classes/ASIHTTPRequest/ASIDataCompressor.h
  7. +219 −0 Classes/ASIHTTPRequest/ASIDataCompressor.m
  8. +41 −0 Classes/ASIHTTPRequest/ASIDataDecompressor.h
  9. +218 −0 Classes/ASIHTTPRequest/ASIDataDecompressor.m
  10. +42 −0 Classes/ASIHTTPRequest/ASIDownloadCache.h
  11. +504 −0 Classes/ASIHTTPRequest/ASIDownloadCache.m
  12. +76 −0 Classes/ASIHTTPRequest/ASIFormDataRequest.h
  13. +355 −0 Classes/ASIHTTPRequest/ASIFormDataRequest.m
  14. +997 −0 Classes/ASIHTTPRequest/ASIHTTPRequest.h
  15. +5,038 −0 Classes/ASIHTTPRequest/ASIHTTPRequest.m
  16. +37 −0 Classes/ASIHTTPRequest/ASIHTTPRequestConfig.h
  17. +35 −0 Classes/ASIHTTPRequest/ASIHTTPRequestDelegate.h
  18. +26 −0 Classes/ASIHTTPRequest/ASIInputStream.h
  19. +136 −0 Classes/ASIHTTPRequest/ASIInputStream.m
  20. +108 −0 Classes/ASIHTTPRequest/ASINetworkQueue.h
  21. +343 −0 Classes/ASIHTTPRequest/ASINetworkQueue.m
  22. +38 −0 Classes/ASIHTTPRequest/ASIProgressDelegate.h
  23. +80 −0 Classes/ASIHTTPRequest/ASIWebPageRequest/ASIWebPageRequest.h
  24. +703 −0 Classes/ASIHTTPRequest/ASIWebPageRequest/ASIWebPageRequest.m
  25. +58 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesCDNRequest.h
  26. +147 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesCDNRequest.m
  27. +41 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainer.h
  28. +28 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainer.m
  29. +43 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainerRequest.h
  30. +134 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainerRequest.m
  31. +31 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainerXMLParserDelegate.h
  32. +72 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesContainerXMLParserDelegate.m
  33. +30 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesObject.h
  34. +29 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesObject.m
  35. +71 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesObjectRequest.h
  36. +261 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesObjectRequest.m
  37. +40 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesRequest.h
  38. +123 −0 Classes/ASIHTTPRequest/CloudFiles/ASICloudFilesRequest.m
  39. +193 −0 Classes/ASIHTTPRequest/External/Reachability/Reachability.h
  40. +814 −0 Classes/ASIHTTPRequest/External/Reachability/Reachability.m
  41. +23 −0 Classes/ASIHTTPRequest/S3/ASINSXMLParserCompat.h
  42. +34 −0 Classes/ASIHTTPRequest/S3/ASIS3Bucket.h
  43. +32 −0 Classes/ASIHTTPRequest/S3/ASIS3Bucket.m
  44. +54 −0 Classes/ASIHTTPRequest/S3/ASIS3BucketObject.h
  45. +74 −0 Classes/ASIHTTPRequest/S3/ASIS3BucketObject.m
  46. +72 −0 Classes/ASIHTTPRequest/S3/ASIS3BucketRequest.h
  47. +175 −0 Classes/ASIHTTPRequest/S3/ASIS3BucketRequest.m
  48. +80 −0 Classes/ASIHTTPRequest/S3/ASIS3ObjectRequest.h
  49. +162 −0 Classes/ASIHTTPRequest/S3/ASIS3ObjectRequest.m
  50. +109 −0 Classes/ASIHTTPRequest/S3/ASIS3Request.h
  51. +311 −0 Classes/ASIHTTPRequest/S3/ASIS3Request.m
  52. +31 −0 Classes/ASIHTTPRequest/S3/ASIS3ServiceRequest.h
  53. +80 −0 Classes/ASIHTTPRequest/S3/ASIS3ServiceRequest.m
  54. +14 −4 Classes/{RootViewController.h → AreasViewController.h}
  55. +414 −0 Classes/AreasViewController.m
  56. +173 −0 Classes/BookmarksView.xib
  57. +24 −0 Classes/BookmarksViewController.h
  58. +356 −0 Classes/BookmarksViewController.m
  59. +0 −32 Classes/CachedImageLoader.h
  60. +0 −196 Classes/CachedImageLoader.m
  61. +44 −0 Classes/Config.h
  62. +94 −0 Classes/Config.m
  63. +9 −5 Classes/DetailViewController.h
  64. +129 −86 Classes/DetailViewController.m
  65. +41 −0 Classes/DictionaryHelper.h
  66. +66 −0 Classes/DictionaryHelper.m
  67. +0 −28 Classes/DiskCache.h
  68. +0 −211 Classes/DiskCache.m
  69. +380 −0 Classes/DozukiInfoView.xib
  70. +23 −0 Classes/DozukiInfoViewController.h
  71. +123 −0 Classes/DozukiInfoViewController.m
  72. +173 −0 Classes/DozukiSelectSiteView.xib
  73. +19 −0 Classes/DozukiSelectSiteViewController.h
  74. +206 −0 Classes/DozukiSelectSiteViewController.m
  75. +4,589 −0 Classes/DozukiSplashView.xib
  76. +11 −0 Classes/DozukiSplashViewController.h
  77. +66 −0 Classes/DozukiSplashViewController.m
  78. +4 −1 Classes/Guide.h
  79. +18 −2 Classes/Guide.m
  80. +37 −0 Classes/GuideBookmarker.h
  81. +200 −0 Classes/GuideBookmarker.m
  82. +52 −0 Classes/GuideBookmarks.h
  83. +347 −0 Classes/GuideBookmarks.m
  84. +19 −0 Classes/GuideCatchingWebView.h
  85. +137 −0 Classes/GuideCatchingWebView.m
  86. +11 −0 Classes/GuideCell.h
  87. +28 −0 Classes/GuideCell.m
  88. +49 −19 Classes/GuideImageViewController.m
  89. +121 −314 Classes/GuideIntroView.xib
  90. +14 −6 Classes/GuideIntroViewController.h
  91. +72 −32 Classes/GuideIntroViewController.m
  92. +7 −8 Classes/GuideStepViewController.h
  93. +125 −106 Classes/GuideStepViewController.m
  94. +43 −275 Classes/GuideView.xib
  95. +6 −5 Classes/GuideViewController.h
  96. +132 −60 Classes/GuideViewController.m
  97. +18 −0 Classes/ListViewController.h
  98. +119 −0 Classes/ListViewController.m
  99. +46 −0 Classes/LoginViewController.h
  100. +495 −0 Classes/LoginViewController.m
  101. +13 −0 Classes/LoginViewControllerDelegate.h
  102. +295 −0 Classes/RegexKitLite.h
  103. +2,636 −0 Classes/RegexKitLite.m
  104. +0 −167 Classes/RootViewController.m
  105. +538 −0 Classes/SDWebImage/.svn/entries
  106. +5 −0 Classes/SDWebImage/.svn/prop-base/LICENSE.svn-base
  107. +5 −0 Classes/SDWebImage/.svn/prop-base/README.md.svn-base
  108. +5 −0 Classes/SDWebImage/.svn/prop-base/SDImageCache.h.svn-base
  109. +5 −0 Classes/SDWebImage/.svn/prop-base/SDImageCache.m.svn-base
  110. +5 −0 Classes/SDWebImage/.svn/prop-base/SDImageCacheDelegate.h.svn-base
  111. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageDownloader.h.svn-base
  112. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageDownloader.m.svn-base
  113. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageDownloaderDelegate.h.svn-base
  114. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageManager.h.svn-base
  115. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageManager.m.svn-base
  116. +5 −0 Classes/SDWebImage/.svn/prop-base/SDWebImageManagerDelegate.h.svn-base
  117. +5 −0 Classes/SDWebImage/.svn/prop-base/UIImageView+WebCache.h.svn-base
  118. +5 −0 Classes/SDWebImage/.svn/prop-base/UIImageView+WebCache.m.svn-base
  119. +20 −0 Classes/SDWebImage/.svn/text-base/LICENSE.svn-base
  120. +168 −0 Classes/SDWebImage/.svn/text-base/README.md.svn-base
  121. +34 −0 Classes/SDWebImage/.svn/text-base/SDImageCache.h.svn-base
  122. +343 −0 Classes/SDWebImage/.svn/text-base/SDImageCache.m.svn-base
  123. +19 −0 Classes/SDWebImage/.svn/text-base/SDImageCacheDelegate.h.svn-base
  124. +38 −0 Classes/SDWebImage/.svn/text-base/SDWebImageDownloader.h.svn-base
  125. +144 −0 Classes/SDWebImage/.svn/text-base/SDWebImageDownloader.m.svn-base
  126. +19 −0 Classes/SDWebImage/.svn/text-base/SDWebImageDownloaderDelegate.h.svn-base
  127. +28 −0 Classes/SDWebImage/.svn/text-base/SDWebImageManager.h.svn-base
  128. +178 −0 Classes/SDWebImage/.svn/text-base/SDWebImageManager.m.svn-base
  129. +17 −0 Classes/SDWebImage/.svn/text-base/SDWebImageManagerDelegate.h.svn-base
  130. +18 −0 Classes/SDWebImage/.svn/text-base/UIImageView+WebCache.h.svn-base
  131. +44 −0 Classes/SDWebImage/.svn/text-base/UIImageView+WebCache.m.svn-base
  132. +20 −0 Classes/SDWebImage/LICENSE
  133. +168 −0 Classes/SDWebImage/README.md
  134. +34 −0 Classes/SDWebImage/SDImageCache.h
  135. +343 −0 Classes/SDWebImage/SDImageCache.m
  136. +19 −0 Classes/SDWebImage/SDImageCacheDelegate.h
  137. +38 −0 Classes/SDWebImage/SDWebImageDownloader.h
  138. +144 −0 Classes/SDWebImage/SDWebImageDownloader.m
  139. +19 −0 Classes/SDWebImage/SDWebImageDownloaderDelegate.h
  140. +28 −0 Classes/SDWebImage/SDWebImageManager.h
  141. +178 −0 Classes/SDWebImage/SDWebImageManager.m
  142. +17 −0 Classes/SDWebImage/SDWebImageManagerDelegate.h
  143. +17 −0 Classes/SDWebImage/UIButton+WebCache.h
  144. +44 −0 Classes/SDWebImage/UIButton+WebCache.m
  145. +18 −0 Classes/SDWebImage/UIImageView+WebCache.h
  146. +44 −0 Classes/SDWebImage/UIImageView+WebCache.m
  147. +347 −0 Classes/SmallGuideIntroView.xib
  148. +533 −0 Classes/SmallGuideStepView.xib
  149. +360 −0 Classes/SmallGuideView.xib
  150. +432 −378 Classes/SplashView.xib
  151. +11 −6 Classes/SplashViewController.h
  152. +60 −85 Classes/SplashViewController.m
  153. +225 −0 Classes/ThinDeviceView.xib
  154. +24 −0 Classes/ThinDeviceViewController.h
  155. +88 −0 Classes/ThinDeviceViewController.m
  156. +11 −0 Classes/UIImage+Coder.m
  157. +25 −0 Classes/User.h
  158. +26 −0 Classes/User.m
  159. +29 −0 Classes/iFixitAPI.h
  160. +256 −5 Classes/iFixitAPI.m
  161. +16 −8 Classes/iFixitAppDelegate.h
  162. +172 −65 Classes/iFixitAppDelegate.m
  163. +142 −0 Classes/iPhoneDeviceView.xib
  164. +20 −0 Classes/iPhoneDeviceViewController.h
  165. +200 −0 Classes/iPhoneDeviceViewController.m
  166. +41 −0 Classes/wbprogresshud/WBFont.h
  167. +29 −0 Classes/wbprogresshud/WBFont.mm
  168. +155 −0 Classes/wbprogresshud/WBGeometry.h
  169. +109 −0 Classes/wbprogresshud/WBProgressHUD.h
  170. +227 −0 Classes/wbprogresshud/WBProgressHUD.mm
  171. +44 −0 Classes/wbprogresshud/WBView.h
  172. +46 −0 Classes/wbprogresshud/WBView.mm
  173. +58 −0 Classes/wbprogresshud/WBViewRect.h
  174. +157 −0 Classes/wbprogresshud/WBViewRect.mm
  175. BIN  Default-Landscape~ipad.png
  176. BIN  Default-Portrait~ipad.png
  177. BIN  Default.png
  178. BIN  Default@2x.png
  179. +52 −247 DetailView.xib
  180. 0  { → Graphics}/Default-Landscape.png
  181. 0  { → Graphics}/Default-Portrait.png
  182. 0  { → Graphics}/NoImage_300x225.jpg
  183. 0  { → Graphics}/NoImage_592x444.jpg
  184. BIN  Graphics/Sites/Dozuki/Default-Landscape~ipad.png
  185. BIN  Graphics/Sites/Dozuki/Default-Portrait~ipad.png
  186. BIN  Graphics/Sites/Dozuki/Default.png
  187. BIN  Graphics/Sites/Dozuki/Default@2x.png
  188. BIN  Graphics/Sites/Dozuki/icon.psd
  189. BIN  Graphics/Sites/Dozuki/icon114.png
  190. BIN  Graphics/Sites/Dozuki/icon512.png
  191. BIN  Graphics/Sites/Dozuki/icon57.png
  192. BIN  Graphics/Sites/Dozuki/icon72.png
  193. BIN  Graphics/Sites/Make/Default-Landscape.png
  194. BIN  Graphics/Sites/Make/Default-Landscape_black.png
  195. BIN  Graphics/Sites/Make/Default-Portrait.png
  196. BIN  Graphics/Sites/Make/Default-Portrait_black.png
  197. BIN  Graphics/Sites/Make/icon114.png
  198. BIN  Graphics/Sites/Make/icon512.png
  199. BIN  Graphics/Sites/Make/icon57.png
  200. BIN  Graphics/Sites/Make/icon72.png
  201. BIN  Graphics/Sites/iFixit/Default-Landscape~ipad.png
  202. BIN  Graphics/Sites/iFixit/Default-Portrait~ipad.png
  203. BIN  Graphics/Sites/iFixit/Default.png
  204. BIN  Graphics/Sites/iFixit/Default@2x.png
  205. 0  { → Graphics/Sites/iFixit}/icon114.png
  206. 0  { → Graphics/Sites/iFixit}/icon512.png
  207. 0  { → Graphics/Sites/iFixit}/icon57.png
  208. 0  { → Graphics/Sites/iFixit}/icon72.png
  209. 0  { → Graphics}/back.png
  210. 0  { → Graphics}/back@2x.png
  211. 0  { → Graphics}/backbutton.psd
  212. BIN  Graphics/backtosites.png
  213. BIN  Graphics/backtosites@2x.png
  214. 0  { → Graphics}/browse_manuals.png
  215. BIN  Graphics/browse_projects.png
  216. 0  { → Graphics}/forward.png
  217. 0  { → Graphics}/forward@2x.png
  218. BIN  Graphics/getstarted.png
  219. BIN  Graphics/icon114.png
  220. BIN  Graphics/icon512.png
  221. BIN  Graphics/icon57.png
  222. BIN  Graphics/icon72.png
  223. BIN  Graphics/login.png
  224. 0  { → Graphics}/logo_black.png
  225. BIN  Graphics/logo_make.png
  226. BIN  Graphics/logo_make_black.png
  227. BIN  Graphics/register.png
  228. BIN  Graphics/splashButton.png
  229. BIN  Graphics/splashButton@2x.png
  230. 0  { → Graphics}/splash_header.png
  231. BIN  Graphics/splash_header_make.png
  232. BIN  Graphics/splash_header_make_black.png
  233. BIN  Graphics/titleImage.png
  234. BIN  Graphics/titleImage.psd
  235. BIN  Graphics/titleImage@2x.png
  236. BIN  Graphics/titleImage_make.png
  237. BIN  Graphics/titleImage_make@2x.png
  238. BIN  Graphics/x-icon.png
  239. BIN  Graphics/x-icon@2x.png
  240. +113 −378 GuideStepView.xib
  241. +376 −0 MainWindow-iPad.xib
  242. +335 −566 MainWindow.xib
  243. +1 −0  README
  244. +43 −0 TestFlight/TestFlight.h
  245. BIN  TestFlight/libTestFlight.a
  246. BIN  backarrow.png
  247. +18 −0 css/ifixit_intro.css
  248. +75 −0 css/ifixit_step.css
  249. +18 −0 css/make_intro.css
  250. +75 −0 css/make_step.css
  251. +11 −5 iFixit-Info.plist
Sorry, we could not display the entire diff because it was too big.
View
4 .gitignore
@@ -1,3 +1,7 @@
build
iFixit.xcodeproj/dmpatierno.mode1v3
iFixit.xcodeproj/dmpatierno.pbxuser
+iFixit.xcodeproj/project.xcworkspace
+iFixit.xcodeproj/xcuserdata
+.svn
+.gitignore
View
338 AreasView.xib
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">784</int>
+ <string key="IBDocument.SystemVersion">10J567</string>
+ <string key="IBDocument.InterfaceBuilderVersion">1305</string>
+ <string key="IBDocument.AppKitVersion">1038.35</string>
+ <string key="IBDocument.HIToolboxVersion">462.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="NS.object.0">300</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>IBProxyObject</string>
+ <string>IBUISearchBar</string>
+ <string>IBUITableView</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBProxyObject" id="841351856">
+ <string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBProxyObject" id="371349661">
+ <string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUITableView" id="709618507">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">274</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUISearchBar" id="264602578">
+ <reference key="NSNextResponder" ref="709618507"/>
+ <int key="NSvFlags">290</int>
+ <string key="NSFrameSize">{320, 44}</string>
+ <reference key="NSSuperview" ref="709618507"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <int key="IBUIContentMode">3</int>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <string key="IBPrompt"/>
+ <string key="IBPlaceholder">Search</string>
+ <object class="NSColor" key="IBTintColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MAA</bytes>
+ </object>
+ <object class="IBUITextInputTraits" key="IBTextInputTraits">
+ <int key="IBUIAutocorrectionType">1</int>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="NSArray" key="IBScopeButtonTitles">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>Title</string>
+ <string>Title</string>
+ </object>
+ </object>
+ </object>
+ <string key="NSFrameSize">{320, 247}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="264602578"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ </object>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
+ <int key="IBUIInterfaceOrientation">3</int>
+ <int key="interfaceOrientation">3</int>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <double key="IBUIContentInset.top">0.0</double>
+ <double key="IBUIContentInset.bottom">44</double>
+ <double key="IBUIContentInset.left">0.0</double>
+ <double key="IBUIContentInset.right">0.0</double>
+ <bool key="IBUIBouncesZoom">NO</bool>
+ <int key="IBUISeparatorStyle">1</int>
+ <int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
+ <bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
+ <float key="IBUIRowHeight">44</float>
+ <float key="IBUISectionHeaderHeight">22</float>
+ <float key="IBUISectionFooterHeight">22</float>
+ <reference key="IBUITableHeaderView" ref="264602578"/>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">view</string>
+ <reference key="source" ref="841351856"/>
+ <reference key="destination" ref="709618507"/>
+ </object>
+ <int key="connectionID">3</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">dataSource</string>
+ <reference key="source" ref="709618507"/>
+ <reference key="destination" ref="841351856"/>
+ </object>
+ <int key="connectionID">4</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="709618507"/>
+ <reference key="destination" ref="841351856"/>
+ </object>
+ <int key="connectionID">5</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">searchBar</string>
+ <reference key="source" ref="841351856"/>
+ <reference key="destination" ref="264602578"/>
+ </object>
+ <int key="connectionID">7</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="264602578"/>
+ <reference key="destination" ref="841351856"/>
+ </object>
+ <int key="connectionID">8</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <reference key="object" ref="0"/>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="841351856"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="371349661"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">2</int>
+ <reference key="object" ref="709618507"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="264602578"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="264602578"/>
+ <reference key="parent" ref="709618507"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.CustomClassName</string>
+ <string>-2.CustomClassName</string>
+ <string>2.IBEditorWindowLastContentRect</string>
+ <string>2.IBPluginDependency</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>RootViewController</string>
+ <string>UIResponder</string>
+ <string>{{144, 609}, {320, 247}}</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">12</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">DetailViewController</string>
+ <string key="superclassName">UIViewController</string>
+ <object class="NSMutableDictionary" key="actions">
+ <string key="NS.key.0">showSplash:</string>
+ <string key="NS.object.0">UIBarButtonItem</string>
+ </object>
+ <object class="NSMutableDictionary" key="actionInfosByName">
+ <string key="NS.key.0">showSplash:</string>
+ <object class="IBActionInfo" key="NS.object.0">
+ <string key="name">showSplash:</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>fwdButton</string>
+ <string>toolbar</string>
+ <string>webView</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>UIBarButtonItem</string>
+ <string>UIBarButtonItem</string>
+ <string>UIToolbar</string>
+ <string>UIWebView</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>fwdButton</string>
+ <string>toolbar</string>
+ <string>webView</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">backButton</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">fwdButton</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">toolbar</string>
+ <string key="candidateClassName">UIToolbar</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">webView</string>
+ <string key="candidateClassName">UIWebView</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/DetailViewController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">RootViewController</string>
+ <string key="superclassName">UITableViewController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>detailViewController</string>
+ <string>searchBar</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>DetailViewController</string>
+ <string>UISearchBar</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>detailViewController</string>
+ <string>searchBar</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">detailViewController</string>
+ <string key="candidateClassName">DetailViewController</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">searchBar</string>
+ <string key="candidateClassName">UISearchBar</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/RootViewController.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <integer value="784" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="3100" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <string key="IBCocoaTouchPluginVersion">300</string>
+ </data>
+</archive>
View
35 Classes/ASIHTTPRequest/ASIAuthenticationDialog.h
@@ -0,0 +1,35 @@
+//
+// ASIAuthenticationDialog.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 21/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+@class ASIHTTPRequest;
+
+typedef enum _ASIAuthenticationType {
+ ASIStandardAuthenticationType = 0,
+ ASIProxyAuthenticationType = 1
+} ASIAuthenticationType;
+
+@interface ASIAutorotatingViewController : UIViewController
+@end
+
+@interface ASIAuthenticationDialog : ASIAutorotatingViewController <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
+ ASIHTTPRequest *request;
+ ASIAuthenticationType type;
+ UITableView *tableView;
+ UIViewController *presentingController;
+ BOOL didEnableRotationNotifications;
+}
++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
++ (void)dismiss;
+
+@property (retain) ASIHTTPRequest *request;
+@property (assign) ASIAuthenticationType type;
+@property (assign) BOOL didEnableRotationNotifications;
+@property (retain, nonatomic) UIViewController *presentingController;
+@end
View
487 Classes/ASIHTTPRequest/ASIAuthenticationDialog.m
@@ -0,0 +1,487 @@
+//
+// ASIAuthenticationDialog.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 21/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIAuthenticationDialog.h"
+#import "ASIHTTPRequest.h"
+#import <QuartzCore/QuartzCore.h>
+
+static ASIAuthenticationDialog *sharedDialog = nil;
+BOOL isDismissing = NO;
+static NSMutableArray *requestsNeedingAuthentication = nil;
+
+static const NSUInteger kUsernameRow = 0;
+static const NSUInteger kUsernameSection = 0;
+static const NSUInteger kPasswordRow = 1;
+static const NSUInteger kPasswordSection = 0;
+static const NSUInteger kDomainRow = 0;
+static const NSUInteger kDomainSection = 1;
+
+
+@implementation ASIAutorotatingViewController
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
+{
+ return YES;
+}
+
+@end
+
+
+@interface ASIAuthenticationDialog ()
+- (void)showTitle;
+- (void)show;
+- (NSArray *)requestsRequiringTheseCredentials;
+- (void)presentNextDialog;
+- (void)keyboardWillShow:(NSNotification *)notification;
+- (void)orientationChanged:(NSNotification *)notification;
+- (void)cancelAuthenticationFromDialog:(id)sender;
+- (void)loginWithCredentialsFromDialog:(id)sender;
+@property (retain) UITableView *tableView;
+@end
+
+@implementation ASIAuthenticationDialog
+
+#pragma mark init / dealloc
+
++ (void)initialize
+{
+ if (self == [ASIAuthenticationDialog class]) {
+ requestsNeedingAuthentication = [[NSMutableArray array] retain];
+ }
+}
+
++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest
+{
+ // No need for a lock here, this will always be called on the main thread
+ if (!sharedDialog) {
+ sharedDialog = [[self alloc] init];
+ [sharedDialog setRequest:theRequest];
+ if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) {
+ [sharedDialog setType:ASIProxyAuthenticationType];
+ } else {
+ [sharedDialog setType:ASIStandardAuthenticationType];
+ }
+ [sharedDialog show];
+ } else {
+ [requestsNeedingAuthentication addObject:theRequest];
+ }
+}
+
+- (id)init
+{
+ if ((self = [self initWithNibName:nil bundle:nil])) {
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+#endif
+ if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
+ [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+ [self setDidEnableRotationNotifications:YES];
+ }
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ }
+#endif
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if ([self didEnableRotationNotifications]) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
+ }
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
+
+ [request release];
+ [tableView release];
+ [presentingController.view removeFromSuperview];
+ [presentingController release];
+ [super dealloc];
+}
+
+#pragma mark keyboard notifications
+
+- (void)keyboardWillShow:(NSNotification *)notification
+{
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+#endif
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2
+ NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
+#else
+ NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey];
+#endif
+ CGRect keyboardBounds;
+ [keyboardBoundsValue getValue:&keyboardBounds];
+ UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0);
+ [[self tableView] setScrollIndicatorInsets:e];
+ [[self tableView] setContentInset:e];
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ }
+#endif
+}
+
+// Manually handles orientation changes on iPhone
+- (void)orientationChanged:(NSNotification *)notification
+{
+ [self showTitle];
+
+ UIDeviceOrientation o = [[UIApplication sharedApplication] statusBarOrientation];
+ CGFloat angle = 0;
+ switch (o) {
+ case UIDeviceOrientationLandscapeLeft: angle = 90; break;
+ case UIDeviceOrientationLandscapeRight: angle = -90; break;
+ case UIDeviceOrientationPortraitUpsideDown: angle = 180; break;
+ default: break;
+ }
+
+ CGRect f = [[UIScreen mainScreen] applicationFrame];
+
+ // Swap the frame height and width if necessary
+ if (UIDeviceOrientationIsLandscape(o)) {
+ CGFloat t;
+ t = f.size.width;
+ f.size.width = f.size.height;
+ f.size.height = t;
+ }
+
+ CGAffineTransform previousTransform = self.view.layer.affineTransform;
+ CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0));
+
+ // Reset the transform so we can set the size
+ self.view.layer.affineTransform = CGAffineTransformIdentity;
+ self.view.frame = (CGRect){ { 0, 0 }, f.size};
+
+ // Revert to the previous transform for correct animation
+ self.view.layer.affineTransform = previousTransform;
+
+ [UIView beginAnimations:nil context:NULL];
+ [UIView setAnimationDuration:0.3];
+
+ // Set the new transform
+ self.view.layer.affineTransform = newTransform;
+
+ // Fix the view origin
+ self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size};
+ [UIView commitAnimations];
+}
+
+#pragma mark utilities
+
+- (UIViewController *)presentingController
+{
+ if (!presentingController) {
+ presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil];
+
+ // Attach to the window, but don't interfere.
+ UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
+ [window addSubview:[presentingController view]];
+ [[presentingController view] setFrame:CGRectZero];
+ [[presentingController view] setUserInteractionEnabled:NO];
+ }
+
+ return presentingController;
+}
+
+- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section
+{
+ return [[[[[self tableView] cellForRowAtIndexPath:
+ [NSIndexPath indexPathForRow:row inSection:section]]
+ contentView] subviews] objectAtIndex:0];
+}
+
+- (UITextField *)usernameField
+{
+ return [self textFieldInRow:kUsernameRow section:kUsernameSection];
+}
+
+- (UITextField *)passwordField
+{
+ return [self textFieldInRow:kPasswordRow section:kPasswordSection];
+}
+
+- (UITextField *)domainField
+{
+ return [self textFieldInRow:kDomainRow section:kDomainSection];
+}
+
+#pragma mark show / dismiss
+
++ (void)dismiss
+{
+ [[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES];
+}
+
+- (void)viewDidDisappear:(BOOL)animated
+{
+ [self retain];
+ [sharedDialog release];
+ sharedDialog = nil;
+ [self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0];
+ [self release];
+}
+
+- (void)dismiss
+{
+ if (self == sharedDialog) {
+ [[self class] dismiss];
+ } else {
+ [[self parentViewController] dismissModalViewControllerAnimated:YES];
+ }
+}
+
+- (void)showTitle
+{
+ UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0];
+ UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0];
+ if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
+ // Setup the title
+ if ([self type] == ASIProxyAuthenticationType) {
+ [navItem setPrompt:@"Login to this secure proxy server."];
+ } else {
+ [navItem setPrompt:@"Login to this secure server."];
+ }
+ } else {
+ [navItem setPrompt:nil];
+ }
+ [navigationBar sizeToFit];
+ CGRect f = [[self view] bounds];
+ f.origin.y = [navigationBar frame].size.height;
+ f.size.height -= f.origin.y;
+ [[self tableView] setFrame:f];
+}
+
+- (void)show
+{
+ // Remove all subviews
+ UIView *v;
+ while ((v = [[[self view] subviews] lastObject])) {
+ [v removeFromSuperview];
+ }
+
+ // Setup toolbar
+ UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease];
+ [bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+
+ UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease];
+ bar.items = [NSArray arrayWithObject:navItem];
+
+ [[self view] addSubview:bar];
+
+ [self showTitle];
+
+ // Setup toolbar buttons
+ if ([self type] == ASIProxyAuthenticationType) {
+ [navItem setTitle:[[self request] proxyHost]];
+ } else {
+ [navItem setTitle:[[[self request] url] host]];
+ }
+
+ [navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]];
+ [navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
+
+ // We show the login form in a table view, similar to Safari's authentication dialog
+ [bar sizeToFit];
+ CGRect f = [[self view] bounds];
+ f.origin.y = [bar frame].size.height;
+ f.size.height -= f.origin.y;
+
+ [self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]];
+ [[self tableView] setDelegate:self];
+ [[self tableView] setDataSource:self];
+ [[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
+ [[self view] addSubview:[self tableView]];
+
+ // Force reload the table content, and focus the first field to show the keyboard
+ [[self tableView] reloadData];
+ [[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder];
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
+ [self setModalPresentationStyle:UIModalPresentationFormSheet];
+ }
+#endif
+
+ [[self presentingController] presentModalViewController:self animated:YES];
+}
+
+#pragma mark button callbacks
+
+- (void)cancelAuthenticationFromDialog:(id)sender
+{
+ for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
+ [theRequest cancelAuthentication];
+ [requestsNeedingAuthentication removeObject:theRequest];
+ }
+ [self dismiss];
+}
+
+- (NSArray *)requestsRequiringTheseCredentials
+{
+ NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array];
+ NSURL *requestURL = [[self request] url];
+ for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) {
+ NSURL *theURL = [otherRequest url];
+ if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) {
+ [requestsRequiringTheseCredentials addObject:otherRequest];
+ }
+ }
+ [requestsRequiringTheseCredentials addObject:[self request]];
+ return requestsRequiringTheseCredentials;
+}
+
+- (void)presentNextDialog
+{
+ if ([requestsNeedingAuthentication count]) {
+ ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0];
+ [requestsNeedingAuthentication removeObjectAtIndex:0];
+ [[self class] presentAuthenticationDialogForRequest:nextRequest];
+ }
+}
+
+
+- (void)loginWithCredentialsFromDialog:(id)sender
+{
+ for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
+
+ NSString *username = [[self usernameField] text];
+ NSString *password = [[self passwordField] text];
+
+ if (username == nil) { username = @""; }
+ if (password == nil) { password = @""; }
+
+ if ([self type] == ASIProxyAuthenticationType) {
+ [theRequest setProxyUsername:username];
+ [theRequest setProxyPassword:password];
+ } else {
+ [theRequest setUsername:username];
+ [theRequest setPassword:password];
+ }
+
+ // Handle NTLM domains
+ NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
+ if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
+ NSString *domain = [[self domainField] text];
+ if ([self type] == ASIProxyAuthenticationType) {
+ [theRequest setProxyDomain:domain];
+ } else {
+ [theRequest setDomain:domain];
+ }
+ }
+
+ [theRequest retryUsingSuppliedCredentials];
+ [requestsNeedingAuthentication removeObject:theRequest];
+ }
+ [self dismiss];
+}
+
+#pragma mark table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
+{
+ NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
+ if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
+ return 2;
+ }
+ return 1;
+}
+
+- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section
+{
+ if (section == [self numberOfSectionsInTableView:aTableView]-1) {
+ return 30;
+ }
+ return 0;
+}
+
+- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section
+{
+ if (section == 0) {
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
+ return 54;
+ }
+#endif
+ return 30;
+ }
+ return 0;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ if (section == 0) {
+ return [[self request] authenticationRealm];
+ }
+ return nil;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0
+ UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
+#else
+ UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease];
+#endif
+
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+
+ CGRect f = CGRectInset([cell bounds], 10, 10);
+ UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease];
+ [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
+ [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+ [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
+
+ NSUInteger s = [indexPath section];
+ NSUInteger r = [indexPath row];
+
+ if (s == kUsernameSection && r == kUsernameRow) {
+ [textField setPlaceholder:@"User"];
+ } else if (s == kPasswordSection && r == kPasswordRow) {
+ [textField setPlaceholder:@"Password"];
+ [textField setSecureTextEntry:YES];
+ } else if (s == kDomainSection && r == kDomainRow) {
+ [textField setPlaceholder:@"Domain"];
+ }
+ [cell.contentView addSubview:textField];
+
+ return cell;
+}
+
+- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
+{
+ if (section == 0) {
+ return 2;
+ } else {
+ return 1;
+ }
+}
+
+- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section
+{
+ if (section == [self numberOfSectionsInTableView:aTableView]-1) {
+ // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message
+ if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) {
+ return @"Password will be sent in the clear.";
+ // We are using Digest, NTLM, or any scheme over SSL
+ } else {
+ return @"Password will be sent securely.";
+ }
+ }
+ return nil;
+}
+
+#pragma mark -
+
+@synthesize request;
+@synthesize type;
+@synthesize tableView;
+@synthesize didEnableRotationNotifications;
+@synthesize presentingController;
+@end
View
103 Classes/ASIHTTPRequest/ASICacheDelegate.h
@@ -0,0 +1,103 @@
+//
+// ASICacheDelegate.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 01/05/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class ASIHTTPRequest;
+
+// Cache policies control the behaviour of a cache and how requests use the cache
+// When setting a cache policy, you can use a combination of these values as a bitmask
+// For example: [request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy|ASIDoNotWriteToCacheCachePolicy];
+// Note that some of the behaviours below are mutally exclusive - you cannot combine ASIAskServerIfModifiedWhenStaleCachePolicy and ASIAskServerIfModifiedCachePolicy, for example.
+typedef enum _ASICachePolicy {
+
+ // The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy
+ // ASIDownloadCache's default cache policy is 'ASIAskServerIfModifiedWhenStaleCachePolicy'
+ ASIUseDefaultCachePolicy = 0,
+
+ // Tell the request not to read from the cache
+ ASIDoNotReadFromCacheCachePolicy = 1,
+
+ // The the request not to write to the cache
+ ASIDoNotWriteToCacheCachePolicy = 2,
+
+ // Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale
+ ASIAskServerIfModifiedWhenStaleCachePolicy = 4,
+
+ // Always ask the server if there is an updated version of this resource (using a conditional GET)
+ ASIAskServerIfModifiedCachePolicy = 8,
+
+ // If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache
+ ASIOnlyLoadIfNotCachedCachePolicy = 16,
+
+ // If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request)
+ ASIDontLoadCachePolicy = 32,
+
+ // Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above.
+ ASIFallbackToCacheIfLoadFailsCachePolicy = 64
+} ASICachePolicy;
+
+// Cache storage policies control whether cached data persists between application launches (ASICachePermanentlyCacheStoragePolicy) or not (ASICacheForSessionDurationCacheStoragePolicy)
+// Calling [ASIHTTPRequest clearSession] will remove any data stored using ASICacheForSessionDurationCacheStoragePolicy
+typedef enum _ASICacheStoragePolicy {
+ ASICacheForSessionDurationCacheStoragePolicy = 0,
+ ASICachePermanentlyCacheStoragePolicy = 1
+} ASICacheStoragePolicy;
+
+
+@protocol ASICacheDelegate <NSObject>
+
+@required
+
+// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy
+- (ASICachePolicy)defaultCachePolicy;
+
+// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date.
+- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
+
+// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date.
+- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
+
+// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid
+- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request;
+
+// Removes cached data for a particular request
+- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request;
+
+// Should return YES if the cache considers its cached response current for the request
+// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired
+- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request;
+
+// Should store the response for the passed request in the cache
+// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response
+- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
+
+// Removes cached data for a particular url
+- (void)removeCachedDataForURL:(NSURL *)url;
+
+// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache
+- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url;
+
+// Should return the cached body of a response for the passed URL, if it is stored in the cache
+- (NSData *)cachedResponseDataForURL:(NSURL *)url;
+
+// Returns a path to the cached response data, if it exists
+- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url;
+
+// Returns a path to the cached response headers, if they url
+- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url;
+
+// Returns the location to use to store cached response headers for a particular request
+- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request;
+
+// Returns the location to use to store a cached response body for a particular request
+- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request;
+
+// Clear cached data stored for the passed storage policy
+- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)cachePolicy;
+
+@end
View
42 Classes/ASIHTTPRequest/ASIDataCompressor.h
@@ -0,0 +1,42 @@
+//
+// ASIDataCompressor.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 17/08/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk
+// You may also find it helpful if you need to deflate data and files yourself - see the class methods below
+// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
+
+#import <Foundation/Foundation.h>
+#import <zlib.h>
+
+@interface ASIDataCompressor : NSObject {
+ BOOL streamReady;
+ z_stream zStream;
+}
+
+// Convenience constructor will call setupStream for you
++ (id)compressor;
+
+// Compress the passed chunk of data
+// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data
+- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish;
+
+// Convenience method - pass it some data, and you'll get deflated data back
++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;
+
+// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath
++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
+
+// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor'
+- (NSError *)setupStream;
+
+// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through
+// If deflating finishes or fails, this method will be called automatically
+- (NSError *)closeStream;
+
+@property (assign, readonly) BOOL streamReady;
+@end
View
219 Classes/ASIHTTPRequest/ASIDataCompressor.m
@@ -0,0 +1,219 @@
+//
+// ASIDataCompressor.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 17/08/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIDataCompressor.h"
+#import "ASIHTTPRequest.h"
+
+#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
+#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION
+
+@interface ASIDataCompressor ()
++ (NSError *)deflateErrorWithCode:(int)code;
+@end
+
+@implementation ASIDataCompressor
+
++ (id)compressor
+{
+ ASIDataCompressor *compressor = [[[self alloc] init] autorelease];
+ [compressor setupStream];
+ return compressor;
+}
+
+- (void)dealloc
+{
+ if (streamReady) {
+ [self closeStream];
+ }
+ [super dealloc];
+}
+
+- (NSError *)setupStream
+{
+ if (streamReady) {
+ return nil;
+ }
+ // Setup the inflate stream
+ zStream.zalloc = Z_NULL;
+ zStream.zfree = Z_NULL;
+ zStream.opaque = Z_NULL;
+ zStream.avail_in = 0;
+ zStream.next_in = 0;
+ int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
+ if (status != Z_OK) {
+ return [[self class] deflateErrorWithCode:status];
+ }
+ streamReady = YES;
+ return nil;
+}
+
+- (NSError *)closeStream
+{
+ if (!streamReady) {
+ return nil;
+ }
+ // Close the deflate stream
+ streamReady = NO;
+ int status = deflateEnd(&zStream);
+ if (status != Z_OK) {
+ return [[self class] deflateErrorWithCode:status];
+ }
+ return nil;
+}
+
+- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
+{
+ if (length == 0) return nil;
+
+ NSUInteger halfLength = length/2;
+
+ // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below
+ NSMutableData *outputData = [NSMutableData dataWithLength:length/2];
+
+ int status;
+
+ zStream.next_in = bytes;
+ zStream.avail_in = (unsigned int)length;
+ zStream.avail_out = 0;
+
+ NSInteger bytesProcessedAlready = zStream.total_out;
+ while (zStream.avail_out == 0) {
+
+ if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
+ [outputData increaseLengthBy:halfLength];
+ }
+
+ zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
+ zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
+ status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH);
+
+ if (status == Z_STREAM_END) {
+ break;
+ } else if (status != Z_OK) {
+ if (err) {
+ *err = [[self class] deflateErrorWithCode:status];
+ }
+ return NO;
+ }
+ }
+
+ // Set real length
+ [outputData setLength: zStream.total_out-bytesProcessedAlready];
+ return outputData;
+}
+
+
++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
+{
+ NSError *theError = nil;
+ NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES];
+ if (theError) {
+ if (err) {
+ *err = theError;
+ }
+ return nil;
+ }
+ return outputData;
+}
+
+
+
++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
+{
+ NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
+
+ // Create an empty file at the destination path
+ if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
+ }
+ return NO;
+ }
+
+ // Ensure the source file exists
+ if (![fileManager fileExistsAtPath:sourcePath]) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
+ }
+ return NO;
+ }
+
+ UInt8 inputData[DATA_CHUNK_SIZE];
+ NSData *outputData;
+ NSInteger readLength;
+ NSError *theError = nil;
+
+ ASIDataCompressor *compressor = [ASIDataCompressor compressor];
+
+ NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
+ [inputStream open];
+ NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
+ [outputStream open];
+
+ while ([compressor streamReady]) {
+
+ // Read some data from the file
+ readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
+
+ // Make sure nothing went wrong
+ if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
+ }
+ [compressor closeStream];
+ return NO;
+ }
+ // Have we reached the end of the input data?
+ if (!readLength) {
+ break;
+ }
+
+ // Attempt to deflate the chunk of data
+ outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ];
+ if (theError) {
+ if (err) {
+ *err = theError;
+ }
+ [compressor closeStream];
+ return NO;
+ }
+
+ // Write the deflated data out to the destination file
+ [outputStream write:[outputData bytes] maxLength:[outputData length]];
+
+ // Make sure nothing went wrong
+ if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
+ }
+ [compressor closeStream];
+ return NO;
+ }
+
+ }
+ [inputStream close];
+ [outputStream close];
+
+ NSError *error = [compressor closeStream];
+ if (error) {
+ if (err) {
+ *err = error;
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
++ (NSError *)deflateErrorWithCode:(int)code
+{
+ return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
+}
+
+@synthesize streamReady;
+@end
View
41 Classes/ASIHTTPRequest/ASIDataDecompressor.h
@@ -0,0 +1,41 @@
+//
+// ASIDataDecompressor.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 17/08/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk
+// You may also find it helpful if you need to inflate data and files yourself - see the class methods below
+// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
+
+#import <Foundation/Foundation.h>
+#import <zlib.h>
+
+@interface ASIDataDecompressor : NSObject {
+ BOOL streamReady;
+ z_stream zStream;
+}
+
+// Convenience constructor will call setupStream for you
++ (id)decompressor;
+
+// Uncompress the passed chunk of data
+- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err;
+
+// Convenience method - pass it some deflated data, and you'll get inflated data back
++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err;
+
+// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath
++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
+
+// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor'
+- (NSError *)setupStream;
+
+// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through
+// If inflating finishes or fails, this method will be called automatically
+- (NSError *)closeStream;
+
+@property (assign, readonly) BOOL streamReady;
+@end
View
218 Classes/ASIHTTPRequest/ASIDataDecompressor.m
@@ -0,0 +1,218 @@
+//
+// ASIDataDecompressor.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 17/08/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIDataDecompressor.h"
+#import "ASIHTTPRequest.h"
+
+#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
+
+@interface ASIDataDecompressor ()
++ (NSError *)inflateErrorWithCode:(int)code;
+@end;
+
+@implementation ASIDataDecompressor
+
++ (id)decompressor
+{
+ ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease];
+ [decompressor setupStream];
+ return decompressor;
+}
+
+- (void)dealloc
+{
+ if (streamReady) {
+ [self closeStream];
+ }
+ [super dealloc];
+}
+
+- (NSError *)setupStream
+{
+ if (streamReady) {
+ return nil;
+ }
+ // Setup the inflate stream
+ zStream.zalloc = Z_NULL;
+ zStream.zfree = Z_NULL;
+ zStream.opaque = Z_NULL;
+ zStream.avail_in = 0;
+ zStream.next_in = 0;
+ int status = inflateInit2(&zStream, (15+32));
+ if (status != Z_OK) {
+ return [[self class] inflateErrorWithCode:status];
+ }
+ streamReady = YES;
+ return nil;
+}
+
+- (NSError *)closeStream
+{
+ if (!streamReady) {
+ return nil;
+ }
+ // Close the inflate stream
+ streamReady = NO;
+ int status = inflateEnd(&zStream);
+ if (status != Z_OK) {
+ return [[self class] inflateErrorWithCode:status];
+ }
+ return nil;
+}
+
+- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err
+{
+ if (length == 0) return nil;
+
+ NSUInteger halfLength = length/2;
+ NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength];
+
+ int status;
+
+ zStream.next_in = bytes;
+ zStream.avail_in = (unsigned int)length;
+ zStream.avail_out = 0;
+
+ NSInteger bytesProcessedAlready = zStream.total_out;
+ while (zStream.avail_in != 0) {
+
+ if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
+ [outputData increaseLengthBy:halfLength];
+ }
+
+ zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
+ zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
+
+ status = inflate(&zStream, Z_NO_FLUSH);
+
+ if (status == Z_STREAM_END) {
+ break;
+ } else if (status != Z_OK) {
+ if (err) {
+ *err = [[self class] inflateErrorWithCode:status];
+ }
+ return nil;
+ }
+ }
+
+ // Set real length
+ [outputData setLength: zStream.total_out-bytesProcessedAlready];
+ return outputData;
+}
+
+
++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err
+{
+ NSError *theError = nil;
+ NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError];
+ if (theError) {
+ if (err) {
+ *err = theError;
+ }
+ return nil;
+ }
+ return outputData;
+}
+
++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
+{
+ NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
+
+ // Create an empty file at the destination path
+ if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
+ }
+ return NO;
+ }
+
+ // Ensure the source file exists
+ if (![fileManager fileExistsAtPath:sourcePath]) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
+ }
+ return NO;
+ }
+
+ UInt8 inputData[DATA_CHUNK_SIZE];
+ NSData *outputData;
+ NSInteger readLength;
+ NSError *theError = nil;
+
+
+ ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor];
+
+ NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
+ [inputStream open];
+ NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
+ [outputStream open];
+
+ while ([decompressor streamReady]) {
+
+ // Read some data from the file
+ readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
+
+ // Make sure nothing went wrong
+ if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
+ }
+ [decompressor closeStream];
+ return NO;
+ }
+ // Have we reached the end of the input data?
+ if (!readLength) {
+ break;
+ }
+
+ // Attempt to inflate the chunk of data
+ outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError];
+ if (theError) {
+ if (err) {
+ *err = theError;
+ }
+ [decompressor closeStream];
+ return NO;
+ }
+
+ // Write the inflated data out to the destination file
+ [outputStream write:[outputData bytes] maxLength:[outputData length]];
+
+ // Make sure nothing went wrong
+ if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if (err) {
+ *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
+ }
+ [decompressor closeStream];
+ return NO;
+ }
+
+ }
+
+ [inputStream close];
+ [outputStream close];
+
+ NSError *error = [decompressor closeStream];
+ if (error) {
+ if (err) {
+ *err = error;
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
+
++ (NSError *)inflateErrorWithCode:(int)code
+{
+ return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
+}
+
+@synthesize streamReady;
+@end
View
42 Classes/ASIHTTPRequest/ASIDownloadCache.h
@@ -0,0 +1,42 @@
+//
+// ASIDownloadCache.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 01/05/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "ASICacheDelegate.h"
+
+@interface ASIDownloadCache : NSObject <ASICacheDelegate> {
+
+ // The default cache policy for this cache
+ // Requests that store data in the cache will use this cache policy if their cache policy is set to ASIUseDefaultCachePolicy
+ // Defaults to ASIAskServerIfModifiedWhenStaleCachePolicy
+ ASICachePolicy defaultCachePolicy;
+
+ // The directory in which cached data will be stored
+ // Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory
+ NSString *storagePath;
+
+ // Mediates access to the cache
+ NSRecursiveLock *accessLock;
+
+ // When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them
+ BOOL shouldRespectCacheControlHeaders;
+}
+
+// Returns a static instance of an ASIDownloadCache
+// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache
+// To make ASIHTTPRequests use it automatically, use [ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
++ (id)sharedCache;
+
+// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers
++ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request;
+
+@property (assign, nonatomic) ASICachePolicy defaultCachePolicy;
+@property (retain, nonatomic) NSString *storagePath;
+@property (retain) NSRecursiveLock *accessLock;
+@property (assign) BOOL shouldRespectCacheControlHeaders;
+@end
View
504 Classes/ASIHTTPRequest/ASIDownloadCache.m
@@ -0,0 +1,504 @@
+//
+// ASIDownloadCache.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 01/05/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIDownloadCache.h"
+#import "ASIHTTPRequest.h"
+#import <CommonCrypto/CommonHMAC.h>
+
+static ASIDownloadCache *sharedCache = nil;
+
+static NSString *sessionCacheFolder = @"SessionStore";
+static NSString *permanentCacheFolder = @"PermanentStore";
+
+@interface ASIDownloadCache ()
++ (NSString *)keyForURL:(NSURL *)url;
+- (NSString *)pathToFile:(NSString *)file;
+@end
+
+@implementation ASIDownloadCache
+
+- (id)init
+{
+ self = [super init];
+ [self setShouldRespectCacheControlHeaders:YES];
+ [self setDefaultCachePolicy:ASIUseDefaultCachePolicy];
+ [self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
+ return self;
+}
+
++ (id)sharedCache
+{
+ if (!sharedCache) {
+ sharedCache = [[self alloc] init];
+ [sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]];
+
+ }
+ return sharedCache;
+}
+
+- (void)dealloc
+{
+ [storagePath release];
+ [accessLock release];
+ [super dealloc];
+}
+
+- (NSString *)storagePath
+{
+ [[self accessLock] lock];
+ NSString *p = [[storagePath retain] autorelease];
+ [[self accessLock] unlock];
+ return p;
+}
+
+
+- (void)setStoragePath:(NSString *)path
+{
+ [[self accessLock] lock];
+ [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
+ [storagePath release];
+ storagePath = [path retain];
+
+ NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
+
+ BOOL isDirectory = NO;
+ NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
+ for (NSString *directory in directories) {
+ BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory];
+ if (exists && !isDirectory) {
+ [[self accessLock] unlock];
+ [NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
+ } else if (!exists) {
+ [fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil];
+ if (![fileManager fileExistsAtPath:directory]) {
+ [[self accessLock] unlock];
+ [NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
+ }
+ }
+ }
+ [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
+ [[self accessLock] unlock];
+}
+
+- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+{
+ NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
+ NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath];
+ if (!cachedHeaders) {
+ return;
+ }
+ NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
+ if (!expires) {
+ return;
+ }
+ [cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ [cachedHeaders writeToFile:headerPath atomically:NO];
+}
+
+- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+{
+ NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
+
+ // If we weren't given a custom max-age, lets look for one in the response headers
+ if (!maxAge) {
+ NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString];
+ if (cacheControl) {
+ NSScanner *scanner = [NSScanner scannerWithString:cacheControl];
+ [scanner scanUpToString:@"max-age" intoString:NULL];
+ if ([scanner scanString:@"max-age" intoString:NULL]) {
+ [scanner scanString:@"=" intoString:NULL];
+ [scanner scanDouble:&maxAge];
+ }
+ }
+ }
+
+ // RFC 2612 says max-age must override any Expires header
+ if (maxAge) {
+ return [[NSDate date] addTimeInterval:maxAge];
+ } else {
+ NSString *expires = [responseHeaders objectForKey:@"Expires"];
+ if (expires) {
+ return [ASIHTTPRequest dateFromRFC1123String:expires];
+ }
+ }
+ return nil;
+}
+
+- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
+{
+ [[self accessLock] lock];
+
+ if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ // We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection)
+ int responseCode = [request responseStatusCode];
+ if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
+ [[self accessLock] unlock];
+ return;
+ }
+
+ NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
+ NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
+
+ NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
+ if ([request isResponseCompressed]) {
+ [responseHeaders removeObjectForKey:@"Content-Encoding"];
+ }
+
+ // Create a special 'X-ASIHTTPRequest-Expires' header
+ // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time
+ // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive
+
+ NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
+ if (expires) {
+ [responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
+ }
+
+ // Store the response code in a custom header so we can reuse it later
+
+ // We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET
+ int statusCode = [request responseStatusCode];
+ if (statusCode == 304) {
+ statusCode = 200;
+ }
+ [responseHeaders setObject:[NSNumber numberWithInt:[request responseStatusCode]] forKey:@"X-ASIHTTPRequest-Response-Status-Code"];
+
+ [responseHeaders writeToFile:headerPath atomically:NO];
+
+ if ([request responseData]) {
+ [[request responseData] writeToFile:dataPath atomically:NO];
+ } else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) {
+ NSError *error = nil;
+ [[[[NSFileManager alloc] init] autorelease] copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error];
+ }
+ [[self accessLock] unlock];
+}
+
+- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url
+{
+ NSString *path = [self pathToCachedResponseHeadersForURL:url];
+ if (path) {
+ return [NSDictionary dictionaryWithContentsOfFile:path];
+ }
+ return nil;
+}
+
+- (NSData *)cachedResponseDataForURL:(NSURL *)url
+{
+ NSString *path = [self pathToCachedResponseDataForURL:url];
+ if (path) {
+ return [NSData dataWithContentsOfFile:path];
+ }
+ return nil;
+}
+
+- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url
+{
+ // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
+ NSString *extension = [[url path] pathExtension];
+ if (![extension length]) {
+ extension = @"html";
+ }
+ return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
+}
+
+- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url
+{
+ return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]];
+}
+
+- (NSString *)pathToFile:(NSString *)file
+{
+ [[self accessLock] lock];
+ if (![self storagePath]) {
+ [[self accessLock] unlock];
+ return nil;
+ }
+
+ NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
+
+ // Look in the session store
+ NSString *dataPath = [[[self storagePath] stringByAppendingPathComponent:sessionCacheFolder] stringByAppendingPathComponent:file];
+ if ([fileManager fileExistsAtPath:dataPath]) {
+ [[self accessLock] unlock];
+ return dataPath;
+ }
+ // Look in the permanent store
+ dataPath = [[[self storagePath] stringByAppendingPathComponent:permanentCacheFolder] stringByAppendingPathComponent:file];
+ if ([fileManager fileExistsAtPath:dataPath]) {
+ [[self accessLock] unlock];
+ return dataPath;
+ }
+ [[self accessLock] unlock];
+ return nil;
+}
+
+
+- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request
+{
+ [[self accessLock] lock];
+ if (![self storagePath]) {
+ [[self accessLock] unlock];
+ return nil;
+ }
+
+ NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
+
+ // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
+ NSString *extension = [[[request url] path] pathExtension];
+ if (![extension length]) {
+ extension = @"html";
+ }
+ path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]];
+ [[self accessLock] unlock];
+ return path;
+}
+
+- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request
+{
+ [[self accessLock] lock];
+ if (![self storagePath]) {
+ [[self accessLock] unlock];
+ return nil;
+ }
+ NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
+ path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]];
+ [[self accessLock] unlock];
+ return path;
+}
+
+- (void)removeCachedDataForURL:(NSURL *)url
+{
+ [[self accessLock] lock];
+ if (![self storagePath]) {
+ [[self accessLock] unlock];
+ return;
+ }
+ NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
+
+ NSString *path = [self pathToCachedResponseHeadersForURL:url];
+ if (path) {
+ [fileManager removeItemAtPath:path error:NULL];
+ }
+
+ path = [self pathToCachedResponseDataForURL:url];
+ if (path) {
+ [fileManager removeItemAtPath:path error:NULL];
+ }
+ [[self accessLock] unlock];
+}
+
+- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request
+{
+ [self removeCachedDataForURL:[request url]];
+}
+
+- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
+{
+ [[self accessLock] lock];
+ if (![self storagePath]) {
+ [[self accessLock] unlock];
+ return NO;
+ }
+ NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]];
+ if (!cachedHeaders) {
+ [[self accessLock] unlock];
+ return NO;
+ }
+ NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
+ if (!dataPath) {
+ [[self accessLock] unlock];
+ return NO;
+ }
+
+ // New content is not different
+ if ([request responseStatusCode] == 304) {
+ [[self accessLock] unlock];
+ return YES;
+ }
+
+ // If we already have response headers for this request, check to see if the new content is different
+ // We check [request complete] so that we don't end up comparing response headers from a redirection with these
+ if ([request responseHeaders] && [request complete]) {
+
+ // If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again
+ NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil];
+ for (NSString *header in headersToCompare) {
+ if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) {
+ [[self accessLock] unlock];
+ return NO;
+ }
+ }
+ }
+
+ if ([self shouldRespectCacheControlHeaders]) {
+
+ // Look for X-ASIHTTPRequest-Expires header to see if the content is out of date
+ NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"];
+ if (expires) {
+ if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) {
+ [[self accessLock] unlock];
+ return YES;
+ }
+ }
+
+ // No explicit expiration time sent by the server
+ [[self accessLock] unlock];
+ return NO;
+ }
+
+
+ [[self accessLock] unlock];
+ return YES;
+}
+
+- (ASICachePolicy)defaultCachePolicy
+{
+ [[self accessLock] lock];
+ ASICachePolicy cp = defaultCachePolicy;
+ [[self accessLock] unlock];
+ return cp;
+}
+
+
+- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
+{
+ [[self accessLock] lock];
+ if (!cachePolicy) {
+ defaultCachePolicy = ASIAskServerIfModifiedWhenStaleCachePolicy;