Home | History | Annotate | Download | only in win
      1 /*
      2  * Copyright (C) 2011 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 "DownloadBundle.h"
     28 
     29 #include <CoreFoundation/CoreFoundation.h>
     30 #include <io.h>
     31 #include <sys/stat.h>
     32 #include <sys/types.h>
     33 #include <wtf/text/CString.h>
     34 #include <wtf/text/WTFString.h>
     35 
     36 namespace WebCore {
     37 
     38 namespace DownloadBundle {
     39 
     40 static UInt32 magicNumber()
     41 {
     42     return 0xDECAF4EA;
     43 }
     44 
     45 const String& fileExtension()
     46 {
     47     DEFINE_STATIC_LOCAL(const String, extension, (".download"));
     48     return extension;
     49 }
     50 
     51 bool appendResumeData(CFDataRef resumeData, const String& bundlePath)
     52 {
     53     if (!resumeData) {
     54         LOG_ERROR("Invalid resume data to write to bundle path");
     55         return false;
     56     }
     57     if (bundlePath.isEmpty()) {
     58         LOG_ERROR("Cannot write resume data to empty download bundle path");
     59         return false;
     60     }
     61 
     62     String nullifiedPath = bundlePath;
     63     FILE* bundle = 0;
     64     if (_wfopen_s(&bundle, nullifiedPath.charactersWithNullTermination(), TEXT("ab")) || !bundle) {
     65         LOG_ERROR("Failed to open file %s to append resume data", bundlePath.ascii().data());
     66         return false;
     67     }
     68 
     69     bool result = false;
     70 
     71     const UInt8* resumeBytes = CFDataGetBytePtr(resumeData);
     72     ASSERT(resumeBytes);
     73     if (!resumeBytes)
     74         goto exit;
     75 
     76     CFIndex resumeLength = CFDataGetLength(resumeData);
     77     ASSERT(resumeLength > 0);
     78     if (resumeLength < 1)
     79         goto exit;
     80 
     81     if (fwrite(resumeBytes, 1, resumeLength, bundle) != resumeLength) {
     82         LOG_ERROR("Failed to write resume data to the bundle - errno(%i)", errno);
     83         goto exit;
     84     }
     85 
     86     if (fwrite(&resumeLength, 4, 1, bundle) != 1) {
     87         LOG_ERROR("Failed to write footer length to the bundle - errno(%i)", errno);
     88         goto exit;
     89     }
     90 
     91     const UInt32& magic = magicNumber();
     92     if (fwrite(&magic, 4, 1, bundle) != 1) {
     93         LOG_ERROR("Failed to write footer magic number to the bundle - errno(%i)", errno);
     94         goto exit;
     95     }
     96 
     97     result = true;
     98 exit:
     99     fclose(bundle);
    100     return result;
    101 }
    102 
    103 CFDataRef extractResumeData(const String& bundlePath)
    104 {
    105     if (bundlePath.isEmpty()) {
    106         LOG_ERROR("Cannot create resume data from empty download bundle path");
    107         return 0;
    108     }
    109 
    110     // Open a handle to the bundle file
    111     String nullifiedPath = bundlePath;
    112     FILE* bundle = 0;
    113     if (_wfopen_s(&bundle, nullifiedPath.charactersWithNullTermination(), TEXT("r+b")) || !bundle) {
    114         LOG_ERROR("Failed to open file %s to get resume data", bundlePath.ascii().data());
    115         return 0;
    116     }
    117 
    118     CFDataRef result = 0;
    119     Vector<UInt8> footerBuffer;
    120 
    121     // Stat the file to get its size
    122     struct _stat64 fileStat;
    123     if (_fstat64(_fileno(bundle), &fileStat))
    124         goto exit;
    125 
    126     // Check for the bundle magic number at the end of the file
    127     fpos_t footerMagicNumberPosition = fileStat.st_size - 4;
    128     ASSERT(footerMagicNumberPosition >= 0);
    129     if (footerMagicNumberPosition < 0)
    130         goto exit;
    131     if (fsetpos(bundle, &footerMagicNumberPosition))
    132         goto exit;
    133 
    134     UInt32 footerMagicNumber = 0;
    135     if (fread(&footerMagicNumber, 4, 1, bundle) != 1) {
    136         LOG_ERROR("Failed to read footer magic number from the bundle - errno(%i)", errno);
    137         goto exit;
    138     }
    139 
    140     if (footerMagicNumber != magicNumber()) {
    141         LOG_ERROR("Footer's magic number does not match 0x%X - errno(%i)", magicNumber(), errno);
    142         goto exit;
    143     }
    144 
    145     // Now we're *reasonably* sure this is a .download bundle we actually wrote.
    146     // Get the length of the resume data
    147     fpos_t footerLengthPosition = fileStat.st_size - 8;
    148     ASSERT(footerLengthPosition >= 0);
    149     if (footerLengthPosition < 0)
    150         goto exit;
    151 
    152     if (fsetpos(bundle, &footerLengthPosition))
    153         goto exit;
    154 
    155     UInt32 footerLength = 0;
    156     if (fread(&footerLength, 4, 1, bundle) != 1) {
    157         LOG_ERROR("Failed to read ResumeData length from the bundle - errno(%i)", errno);
    158         goto exit;
    159     }
    160 
    161     // Make sure theres enough bytes to read in for the resume data, and perform the read
    162     fpos_t footerStartPosition = fileStat.st_size - 8 - footerLength;
    163     ASSERT(footerStartPosition >= 0);
    164     if (footerStartPosition < 0)
    165         goto exit;
    166     if (fsetpos(bundle, &footerStartPosition))
    167         goto exit;
    168 
    169     footerBuffer.resize(footerLength);
    170     if (fread(footerBuffer.data(), 1, footerLength, bundle) != footerLength) {
    171         LOG_ERROR("Failed to read ResumeData from the bundle - errno(%i)", errno);
    172         goto exit;
    173     }
    174 
    175     // CFURLDownload will seek to the appropriate place in the file (before our footer) and start overwriting from there
    176     // However, say we were within a few hundred bytes of the end of a download when it was paused -
    177     // The additional footer extended the length of the file beyond its final length, and there will be junk data leftover
    178     // at the end.  Therefore, now that we've retrieved the footer data, we need to truncate it.
    179     if (errno_t resizeError = _chsize_s(_fileno(bundle), footerStartPosition)) {
    180         LOG_ERROR("Failed to truncate the resume footer off the end of the file - errno(%i)", resizeError);
    181         goto exit;
    182     }
    183 
    184     // Finally, make the resume data.  Now, it is possible by some twist of fate the bundle magic number
    185     // was naturally at the end of the file and its not actually a valid bundle.  That, or someone engineered
    186     // it that way to try to attack us.  In that cause, this CFData will successfully create but when we
    187     // actually try to start the CFURLDownload using this bogus data, it will fail and we will handle that gracefully
    188     result = CFDataCreate(0, footerBuffer.data(), footerLength);
    189 exit:
    190     fclose(bundle);
    191     return result;
    192 }
    193 
    194 } // namespace DownloadBundle
    195 
    196 } // namespace WebCore
    197