Home | History | Annotate | Download | only in win
      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