1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "WebKitDLL.h" 28 #include "WebDownload.h" 29 30 #include "CString.h" 31 #include "DefaultDownloadDelegate.h" 32 #include "MarshallingHelpers.h" 33 #include "WebError.h" 34 #include "WebKit.h" 35 #include "WebKitLogging.h" 36 #include "WebMutableURLRequest.h" 37 #include "WebURLAuthenticationChallenge.h" 38 #include "WebURLCredential.h" 39 #include "WebURLResponse.h" 40 41 #include <io.h> 42 #include <sys/stat.h> 43 #include <sys/types.h> 44 45 #pragma warning(push, 0) 46 #include <WebCore/BString.h> 47 #include <WebCore/NotImplemented.h> 48 #include <WebCore/ResourceError.h> 49 #include <WebCore/ResourceHandle.h> 50 #include <WebCore/ResourceRequest.h> 51 #include <WebCore/ResourceResponse.h> 52 #include <wtf/CurrentTime.h> 53 #include <wtf/StdLibExtras.h> 54 #pragma warning(pop) 55 56 using namespace WebCore; 57 58 // Download Bundle file utilities ---------------------------------------------------------------- 59 const String& WebDownload::bundleExtension() 60 { 61 DEFINE_STATIC_LOCAL(const String, bundleExtension, (".download")); 62 return bundleExtension; 63 } 64 65 UInt32 WebDownload::bundleMagicNumber() 66 { 67 return 0xDECAF4EA; 68 } 69 70 // WebDownload ---------------------------------------------------------------- 71 72 WebDownload::WebDownload() 73 : m_refCount(0) 74 { 75 gClassCount++; 76 gClassNameCount.add("WebDownload"); 77 } 78 79 WebDownload::~WebDownload() 80 { 81 LOG(Download, "WebDownload - Destroying download (%p)", this); 82 cancel(); 83 gClassCount--; 84 gClassNameCount.remove("WebDownload"); 85 } 86 87 WebDownload* WebDownload::createInstance() 88 { 89 WebDownload* instance = new WebDownload(); 90 instance->AddRef(); 91 return instance; 92 } 93 94 WebDownload* WebDownload::createInstance(ResourceHandle* handle, const ResourceRequest& request, const ResourceResponse& response, IWebDownloadDelegate* delegate) 95 { 96 WebDownload* instance = new WebDownload(); 97 instance->AddRef(); 98 instance->init(handle, request, response, delegate); 99 return instance; 100 } 101 102 WebDownload* WebDownload::createInstance(const KURL& url, IWebDownloadDelegate* delegate) 103 { 104 WebDownload* instance = new WebDownload(); 105 instance->AddRef(); 106 instance->init(url, delegate); 107 return instance; 108 } 109 110 // IUnknown ------------------------------------------------------------------- 111 112 HRESULT STDMETHODCALLTYPE WebDownload::QueryInterface(REFIID riid, void** ppvObject) 113 { 114 *ppvObject = 0; 115 if (IsEqualGUID(riid, IID_IUnknown)) 116 *ppvObject = static_cast<IWebDownload*>(this); 117 else if (IsEqualGUID(riid, IID_IWebDownload)) 118 *ppvObject = static_cast<IWebDownload*>(this); 119 else if (IsEqualGUID(riid, IID_IWebURLAuthenticationChallengeSender)) 120 *ppvObject = static_cast<IWebURLAuthenticationChallengeSender*>(this); 121 else if (IsEqualGUID(riid, CLSID_WebDownload)) 122 *ppvObject = static_cast<WebDownload*>(this); 123 else 124 return E_NOINTERFACE; 125 126 AddRef(); 127 return S_OK; 128 } 129 130 ULONG STDMETHODCALLTYPE WebDownload::AddRef(void) 131 { 132 return ++m_refCount; 133 } 134 135 ULONG STDMETHODCALLTYPE WebDownload::Release(void) 136 { 137 ULONG newRef = --m_refCount; 138 if (!newRef) 139 delete(this); 140 141 return newRef; 142 } 143 144 // IWebDownload ------------------------------------------------------------------- 145 146 HRESULT STDMETHODCALLTYPE WebDownload::canResumeDownloadDecodedWithEncodingMIMEType( 147 /* [in] */ BSTR, 148 /* [out, retval] */ BOOL*) 149 { 150 notImplemented(); 151 return E_FAIL; 152 } 153 154 HRESULT STDMETHODCALLTYPE WebDownload::bundlePathForTargetPath( 155 /* [in] */ BSTR targetPath, 156 /* [out, retval] */ BSTR* bundlePath) 157 { 158 if (!targetPath) 159 return E_INVALIDARG; 160 161 String bundle(targetPath, SysStringLen(targetPath)); 162 if (bundle.isEmpty()) 163 return E_INVALIDARG; 164 165 if (bundle[bundle.length()-1] == '/') 166 bundle.truncate(1); 167 168 bundle += bundleExtension(); 169 *bundlePath = SysAllocStringLen(bundle.characters(), bundle.length()); 170 if (!*bundlePath) 171 return E_FAIL; 172 return S_OK; 173 } 174 175 HRESULT STDMETHODCALLTYPE WebDownload::request( 176 /* [out, retval] */ IWebURLRequest** request) 177 { 178 if (request) { 179 *request = m_request.get(); 180 if (*request) 181 (*request)->AddRef(); 182 } 183 return S_OK; 184 } 185 186 // Download Bundle file utilities ---------------------------------------------------------------- 187 188 CFDataRef WebDownload::extractResumeDataFromBundle(const String& bundlePath) 189 { 190 if (bundlePath.isEmpty()) { 191 LOG_ERROR("Cannot create resume data from empty download bundle path"); 192 return 0; 193 } 194 195 // Open a handle to the bundle file 196 String nullifiedPath = bundlePath; 197 FILE* bundle = 0; 198 if (_wfopen_s(&bundle, nullifiedPath.charactersWithNullTermination(), TEXT("r+b")) || !bundle) { 199 LOG_ERROR("Failed to open file %s to get resume data", bundlePath.ascii().data()); 200 return 0; 201 } 202 203 CFDataRef result = 0; 204 Vector<UInt8> footerBuffer; 205 206 // Stat the file to get its size 207 struct _stat64 fileStat; 208 if (_fstat64(_fileno(bundle), &fileStat)) 209 goto exit; 210 211 // Check for the bundle magic number at the end of the file 212 fpos_t footerMagicNumberPosition = fileStat.st_size - 4; 213 ASSERT(footerMagicNumberPosition >= 0); 214 if (footerMagicNumberPosition < 0) 215 goto exit; 216 if (fsetpos(bundle, &footerMagicNumberPosition)) 217 goto exit; 218 219 UInt32 footerMagicNumber = 0; 220 if (fread(&footerMagicNumber, 4, 1, bundle) != 1) { 221 LOG_ERROR("Failed to read footer magic number from the bundle - errno(%i)", errno); 222 goto exit; 223 } 224 225 if (footerMagicNumber != bundleMagicNumber()) { 226 LOG_ERROR("Footer's magic number does not match 0x%X - errno(%i)", bundleMagicNumber(), errno); 227 goto exit; 228 } 229 230 // Now we're *reasonably* sure this is a .download bundle we actually wrote. 231 // Get the length of the resume data 232 fpos_t footerLengthPosition = fileStat.st_size - 8; 233 ASSERT(footerLengthPosition >= 0); 234 if (footerLengthPosition < 0) 235 goto exit; 236 237 if (fsetpos(bundle, &footerLengthPosition)) 238 goto exit; 239 240 UInt32 footerLength = 0; 241 if (fread(&footerLength, 4, 1, bundle) != 1) { 242 LOG_ERROR("Failed to read ResumeData length from the bundle - errno(%i)", errno); 243 goto exit; 244 } 245 246 // Make sure theres enough bytes to read in for the resume data, and perform the read 247 fpos_t footerStartPosition = fileStat.st_size - 8 - footerLength; 248 ASSERT(footerStartPosition >= 0); 249 if (footerStartPosition < 0) 250 goto exit; 251 if (fsetpos(bundle, &footerStartPosition)) 252 goto exit; 253 254 footerBuffer.resize(footerLength); 255 if (fread(footerBuffer.data(), 1, footerLength, bundle) != footerLength) { 256 LOG_ERROR("Failed to read ResumeData from the bundle - errno(%i)", errno); 257 goto exit; 258 } 259 260 // CFURLDownload will seek to the appropriate place in the file (before our footer) and start overwriting from there 261 // However, say we were within a few hundred bytes of the end of a download when it was paused - 262 // The additional footer extended the length of the file beyond its final length, and there will be junk data leftover 263 // at the end. Therefore, now that we've retrieved the footer data, we need to truncate it. 264 if (errno_t resizeError = _chsize_s(_fileno(bundle), footerStartPosition)) { 265 LOG_ERROR("Failed to truncate the resume footer off the end of the file - errno(%i)", resizeError); 266 goto exit; 267 } 268 269 // Finally, make the resume data. Now, it is possible by some twist of fate the bundle magic number 270 // was naturally at the end of the file and its not actually a valid bundle. That, or someone engineered 271 // it that way to try to attack us. In that cause, this CFData will successfully create but when we 272 // actually try to start the CFURLDownload using this bogus data, it will fail and we will handle that gracefully 273 result = CFDataCreate(0, footerBuffer.data(), footerLength); 274 exit: 275 fclose(bundle); 276 return result; 277 } 278 279 HRESULT WebDownload::appendResumeDataToBundle(CFDataRef resumeData, const String& bundlePath) 280 { 281 if (!resumeData) { 282 LOG_ERROR("Invalid resume data to write to bundle path"); 283 return E_FAIL; 284 } 285 if (bundlePath.isEmpty()) { 286 LOG_ERROR("Cannot write resume data to empty download bundle path"); 287 return E_FAIL; 288 } 289 290 String nullifiedPath = bundlePath; 291 FILE* bundle = 0; 292 if (_wfopen_s(&bundle, nullifiedPath.charactersWithNullTermination(), TEXT("ab")) || !bundle) { 293 LOG_ERROR("Failed to open file %s to append resume data", bundlePath.ascii().data()); 294 return E_FAIL; 295 } 296 297 HRESULT hr = E_FAIL; 298 299 const UInt8* resumeBytes = CFDataGetBytePtr(resumeData); 300 ASSERT(resumeBytes); 301 if (!resumeBytes) 302 goto exit; 303 304 UInt32 resumeLength = CFDataGetLength(resumeData); 305 ASSERT(resumeLength > 0); 306 if (resumeLength < 1) 307 goto exit; 308 309 if (fwrite(resumeBytes, 1, resumeLength, bundle) != resumeLength) { 310 LOG_ERROR("Failed to write resume data to the bundle - errno(%i)", errno); 311 goto exit; 312 } 313 314 if (fwrite(&resumeLength, 4, 1, bundle) != 1) { 315 LOG_ERROR("Failed to write footer length to the bundle - errno(%i)", errno); 316 goto exit; 317 } 318 319 const UInt32& magic = bundleMagicNumber(); 320 if (fwrite(&magic, 4, 1, bundle) != 1) { 321 LOG_ERROR("Failed to write footer magic number to the bundle - errno(%i)", errno); 322 goto exit; 323 } 324 325 hr = S_OK; 326 exit: 327 fclose(bundle); 328 return hr; 329 } 330