• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
magicNumber()40 static UInt32 magicNumber()
41 {
42     return 0xDECAF4EA;
43 }
44 
fileExtension()45 const String& fileExtension()
46 {
47     DEFINE_STATIC_LOCAL(const String, extension, (".download"));
48     return extension;
49 }
50 
appendResumeData(CFDataRef resumeData,const String & bundlePath)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 
extractResumeData(const String & bundlePath)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