Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* nsJARInputStream.cpp |
michael@0 | 3 | * |
michael@0 | 4 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 7 | |
michael@0 | 8 | #include "nsJARInputStream.h" |
michael@0 | 9 | #include "zipstruct.h" // defines ZIP compression codes |
michael@0 | 10 | #include "nsZipArchive.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "nsNetUtil.h" |
michael@0 | 13 | #include "nsEscape.h" |
michael@0 | 14 | #include "nsIFile.h" |
michael@0 | 15 | #include "nsDebug.h" |
michael@0 | 16 | #include <algorithm> |
michael@0 | 17 | #if defined(XP_WIN) |
michael@0 | 18 | #include <windows.h> |
michael@0 | 19 | #endif |
michael@0 | 20 | |
michael@0 | 21 | /*--------------------------------------------- |
michael@0 | 22 | * nsISupports implementation |
michael@0 | 23 | *--------------------------------------------*/ |
michael@0 | 24 | |
michael@0 | 25 | NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream) |
michael@0 | 26 | |
michael@0 | 27 | /*---------------------------------------------------------- |
michael@0 | 28 | * nsJARInputStream implementation |
michael@0 | 29 | *--------------------------------------------------------*/ |
michael@0 | 30 | |
michael@0 | 31 | nsresult |
michael@0 | 32 | nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item) |
michael@0 | 33 | { |
michael@0 | 34 | nsresult rv = NS_OK; |
michael@0 | 35 | NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); |
michael@0 | 36 | NS_ABORT_IF_FALSE(item, "Argument may not be null"); |
michael@0 | 37 | |
michael@0 | 38 | // Mark it as closed, in case something fails in initialisation |
michael@0 | 39 | mMode = MODE_CLOSED; |
michael@0 | 40 | //-- prepare for the compression type |
michael@0 | 41 | switch (item->Compression()) { |
michael@0 | 42 | case STORED: |
michael@0 | 43 | mMode = MODE_COPY; |
michael@0 | 44 | break; |
michael@0 | 45 | |
michael@0 | 46 | case DEFLATED: |
michael@0 | 47 | rv = gZlibInit(&mZs); |
michael@0 | 48 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 49 | |
michael@0 | 50 | mMode = MODE_INFLATE; |
michael@0 | 51 | mInCrc = item->CRC32(); |
michael@0 | 52 | mOutCrc = crc32(0L, Z_NULL, 0); |
michael@0 | 53 | break; |
michael@0 | 54 | |
michael@0 | 55 | default: |
michael@0 | 56 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 57 | } |
michael@0 | 58 | |
michael@0 | 59 | // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data |
michael@0 | 60 | mFd = aJar->mZip->GetFD(); |
michael@0 | 61 | mZs.next_in = (Bytef *)aJar->mZip->GetData(item); |
michael@0 | 62 | if (!mZs.next_in) |
michael@0 | 63 | return NS_ERROR_FILE_CORRUPTED; |
michael@0 | 64 | mZs.avail_in = item->Size(); |
michael@0 | 65 | mOutSize = item->RealSize(); |
michael@0 | 66 | mZs.total_out = 0; |
michael@0 | 67 | return NS_OK; |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | nsresult |
michael@0 | 71 | nsJARInputStream::InitDirectory(nsJAR* aJar, |
michael@0 | 72 | const nsACString& aJarDirSpec, |
michael@0 | 73 | const char* aDir) |
michael@0 | 74 | { |
michael@0 | 75 | NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); |
michael@0 | 76 | NS_ABORT_IF_FALSE(aDir, "Argument may not be null"); |
michael@0 | 77 | |
michael@0 | 78 | // Mark it as closed, in case something fails in initialisation |
michael@0 | 79 | mMode = MODE_CLOSED; |
michael@0 | 80 | |
michael@0 | 81 | // Keep the zipReader for getting the actual zipItems |
michael@0 | 82 | mJar = aJar; |
michael@0 | 83 | nsZipFind *find; |
michael@0 | 84 | nsresult rv; |
michael@0 | 85 | // We can get aDir's contents as strings via FindEntries |
michael@0 | 86 | // with the following pattern (see nsIZipReader.findEntries docs) |
michael@0 | 87 | // assuming dirName is properly escaped: |
michael@0 | 88 | // |
michael@0 | 89 | // dirName + "?*~" + dirName + "?*/?*" |
michael@0 | 90 | nsDependentCString dirName(aDir); |
michael@0 | 91 | mNameLen = dirName.Length(); |
michael@0 | 92 | |
michael@0 | 93 | // iterate through dirName and copy it to escDirName, escaping chars |
michael@0 | 94 | // which are special at the "top" level of the regexp so FindEntries |
michael@0 | 95 | // works correctly |
michael@0 | 96 | nsAutoCString escDirName; |
michael@0 | 97 | const char* curr = dirName.BeginReading(); |
michael@0 | 98 | const char* end = dirName.EndReading(); |
michael@0 | 99 | while (curr != end) { |
michael@0 | 100 | switch (*curr) { |
michael@0 | 101 | case '*': |
michael@0 | 102 | case '?': |
michael@0 | 103 | case '$': |
michael@0 | 104 | case '[': |
michael@0 | 105 | case ']': |
michael@0 | 106 | case '^': |
michael@0 | 107 | case '~': |
michael@0 | 108 | case '(': |
michael@0 | 109 | case ')': |
michael@0 | 110 | case '\\': |
michael@0 | 111 | escDirName.Append('\\'); |
michael@0 | 112 | // fall through |
michael@0 | 113 | default: |
michael@0 | 114 | escDirName.Append(*curr); |
michael@0 | 115 | } |
michael@0 | 116 | ++curr; |
michael@0 | 117 | } |
michael@0 | 118 | nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") + |
michael@0 | 119 | escDirName + NS_LITERAL_CSTRING("?*/?*"); |
michael@0 | 120 | rv = mJar->mZip->FindInit(pattern.get(), &find); |
michael@0 | 121 | if (NS_FAILED(rv)) return rv; |
michael@0 | 122 | |
michael@0 | 123 | const char *name; |
michael@0 | 124 | uint16_t nameLen; |
michael@0 | 125 | while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) { |
michael@0 | 126 | // Must copy, to make it zero-terminated |
michael@0 | 127 | mArray.AppendElement(nsCString(name,nameLen)); |
michael@0 | 128 | } |
michael@0 | 129 | delete find; |
michael@0 | 130 | |
michael@0 | 131 | if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) { |
michael@0 | 132 | return NS_ERROR_FAILURE; // no error translation |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | // Sort it |
michael@0 | 136 | mArray.Sort(); |
michael@0 | 137 | |
michael@0 | 138 | mBuffer.AssignLiteral("300: "); |
michael@0 | 139 | mBuffer.Append(aJarDirSpec); |
michael@0 | 140 | mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n"); |
michael@0 | 141 | |
michael@0 | 142 | // Open for reading |
michael@0 | 143 | mMode = MODE_DIRECTORY; |
michael@0 | 144 | mZs.total_out = 0; |
michael@0 | 145 | mArrPos = 0; |
michael@0 | 146 | return NS_OK; |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | NS_IMETHODIMP |
michael@0 | 150 | nsJARInputStream::Available(uint64_t *_retval) |
michael@0 | 151 | { |
michael@0 | 152 | // A lot of callers don't check the error code. |
michael@0 | 153 | // They just use the _retval value. |
michael@0 | 154 | *_retval = 0; |
michael@0 | 155 | |
michael@0 | 156 | switch (mMode) { |
michael@0 | 157 | case MODE_NOTINITED: |
michael@0 | 158 | break; |
michael@0 | 159 | |
michael@0 | 160 | case MODE_CLOSED: |
michael@0 | 161 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 162 | |
michael@0 | 163 | case MODE_DIRECTORY: |
michael@0 | 164 | *_retval = mBuffer.Length(); |
michael@0 | 165 | break; |
michael@0 | 166 | |
michael@0 | 167 | case MODE_INFLATE: |
michael@0 | 168 | case MODE_COPY: |
michael@0 | 169 | *_retval = mOutSize - mZs.total_out; |
michael@0 | 170 | break; |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | return NS_OK; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | NS_IMETHODIMP |
michael@0 | 177 | nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) |
michael@0 | 178 | { |
michael@0 | 179 | NS_ENSURE_ARG_POINTER(aBuffer); |
michael@0 | 180 | NS_ENSURE_ARG_POINTER(aBytesRead); |
michael@0 | 181 | |
michael@0 | 182 | *aBytesRead = 0; |
michael@0 | 183 | |
michael@0 | 184 | nsresult rv = NS_OK; |
michael@0 | 185 | MOZ_WIN_MEM_TRY_BEGIN |
michael@0 | 186 | switch (mMode) { |
michael@0 | 187 | case MODE_NOTINITED: |
michael@0 | 188 | return NS_OK; |
michael@0 | 189 | |
michael@0 | 190 | case MODE_CLOSED: |
michael@0 | 191 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 192 | |
michael@0 | 193 | case MODE_DIRECTORY: |
michael@0 | 194 | return ReadDirectory(aBuffer, aCount, aBytesRead); |
michael@0 | 195 | |
michael@0 | 196 | case MODE_INFLATE: |
michael@0 | 197 | if (mFd) { |
michael@0 | 198 | rv = ContinueInflate(aBuffer, aCount, aBytesRead); |
michael@0 | 199 | } |
michael@0 | 200 | // be aggressive about releasing the file! |
michael@0 | 201 | // note that sometimes, we will release mFd before we've finished |
michael@0 | 202 | // deflating - this is because zlib buffers the input |
michael@0 | 203 | if (mZs.avail_in == 0) { |
michael@0 | 204 | mFd = nullptr; |
michael@0 | 205 | } |
michael@0 | 206 | break; |
michael@0 | 207 | |
michael@0 | 208 | case MODE_COPY: |
michael@0 | 209 | if (mFd) { |
michael@0 | 210 | uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out)); |
michael@0 | 211 | if (count) { |
michael@0 | 212 | memcpy(aBuffer, mZs.next_in + mZs.total_out, count); |
michael@0 | 213 | mZs.total_out += count; |
michael@0 | 214 | } |
michael@0 | 215 | *aBytesRead = count; |
michael@0 | 216 | } |
michael@0 | 217 | // be aggressive about releasing the file! |
michael@0 | 218 | // note that sometimes, we will release mFd before we've finished copying. |
michael@0 | 219 | if (mZs.total_out >= mOutSize) { |
michael@0 | 220 | mFd = nullptr; |
michael@0 | 221 | } |
michael@0 | 222 | break; |
michael@0 | 223 | } |
michael@0 | 224 | MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE) |
michael@0 | 225 | return rv; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | NS_IMETHODIMP |
michael@0 | 229 | nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) |
michael@0 | 230 | { |
michael@0 | 231 | // don't have a buffer to read from, so this better not be called! |
michael@0 | 232 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | NS_IMETHODIMP |
michael@0 | 236 | nsJARInputStream::IsNonBlocking(bool *aNonBlocking) |
michael@0 | 237 | { |
michael@0 | 238 | *aNonBlocking = false; |
michael@0 | 239 | return NS_OK; |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | NS_IMETHODIMP |
michael@0 | 243 | nsJARInputStream::Close() |
michael@0 | 244 | { |
michael@0 | 245 | if (mMode == MODE_INFLATE) { |
michael@0 | 246 | inflateEnd(&mZs); |
michael@0 | 247 | } |
michael@0 | 248 | mMode = MODE_CLOSED; |
michael@0 | 249 | mFd = nullptr; |
michael@0 | 250 | return NS_OK; |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | nsresult |
michael@0 | 254 | nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount, |
michael@0 | 255 | uint32_t* aBytesRead) |
michael@0 | 256 | { |
michael@0 | 257 | // No need to check the args, ::Read did that, but assert them at least |
michael@0 | 258 | NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); |
michael@0 | 259 | NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); |
michael@0 | 260 | |
michael@0 | 261 | // Keep old total_out count |
michael@0 | 262 | const uint32_t oldTotalOut = mZs.total_out; |
michael@0 | 263 | |
michael@0 | 264 | // make sure we aren't reading too much |
michael@0 | 265 | mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut)); |
michael@0 | 266 | mZs.next_out = (unsigned char*)aBuffer; |
michael@0 | 267 | |
michael@0 | 268 | // now inflate |
michael@0 | 269 | int zerr = inflate(&mZs, Z_SYNC_FLUSH); |
michael@0 | 270 | if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) |
michael@0 | 271 | return NS_ERROR_FILE_CORRUPTED; |
michael@0 | 272 | |
michael@0 | 273 | *aBytesRead = (mZs.total_out - oldTotalOut); |
michael@0 | 274 | |
michael@0 | 275 | // Calculate the CRC on the output |
michael@0 | 276 | mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead); |
michael@0 | 277 | |
michael@0 | 278 | // be aggressive about ending the inflation |
michael@0 | 279 | // for some reason we don't always get Z_STREAM_END |
michael@0 | 280 | if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) { |
michael@0 | 281 | inflateEnd(&mZs); |
michael@0 | 282 | |
michael@0 | 283 | // stop returning valid data as soon as we know we have a bad CRC |
michael@0 | 284 | if (mOutCrc != mInCrc) |
michael@0 | 285 | return NS_ERROR_FILE_CORRUPTED; |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | return NS_OK; |
michael@0 | 289 | } |
michael@0 | 290 | |
michael@0 | 291 | nsresult |
michael@0 | 292 | nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) |
michael@0 | 293 | { |
michael@0 | 294 | // No need to check the args, ::Read did that, but assert them at least |
michael@0 | 295 | NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); |
michael@0 | 296 | NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); |
michael@0 | 297 | |
michael@0 | 298 | // If the buffer contains data, copy what's there up to the desired amount |
michael@0 | 299 | uint32_t numRead = CopyDataToBuffer(aBuffer, aCount); |
michael@0 | 300 | |
michael@0 | 301 | if (aCount > 0) { |
michael@0 | 302 | // empty the buffer and start writing directory entry lines to it |
michael@0 | 303 | mBuffer.Truncate(); |
michael@0 | 304 | mCurPos = 0; |
michael@0 | 305 | const uint32_t arrayLen = mArray.Length(); |
michael@0 | 306 | |
michael@0 | 307 | for ( ;aCount > mBuffer.Length(); mArrPos++) { |
michael@0 | 308 | // have we consumed all the directory contents? |
michael@0 | 309 | if (arrayLen <= mArrPos) |
michael@0 | 310 | break; |
michael@0 | 311 | |
michael@0 | 312 | const char * entryName = mArray[mArrPos].get(); |
michael@0 | 313 | uint32_t entryNameLen = mArray[mArrPos].Length(); |
michael@0 | 314 | nsZipItem* ze = mJar->mZip->GetItem(entryName); |
michael@0 | 315 | NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); |
michael@0 | 316 | |
michael@0 | 317 | // Last Modified Time |
michael@0 | 318 | PRExplodedTime tm; |
michael@0 | 319 | PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm); |
michael@0 | 320 | char itemLastModTime[65]; |
michael@0 | 321 | PR_FormatTimeUSEnglish(itemLastModTime, |
michael@0 | 322 | sizeof(itemLastModTime), |
michael@0 | 323 | " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", |
michael@0 | 324 | &tm); |
michael@0 | 325 | |
michael@0 | 326 | // write a 201: line to the buffer for this item |
michael@0 | 327 | // 200: filename content-length last-modified file-type |
michael@0 | 328 | mBuffer.AppendLiteral("201: "); |
michael@0 | 329 | |
michael@0 | 330 | // Names must be escaped and relative, so use the pre-calculated length |
michael@0 | 331 | // of the directory name as the offset into the string |
michael@0 | 332 | // NS_EscapeURL adds the escaped URL to the give string buffer |
michael@0 | 333 | NS_EscapeURL(entryName + mNameLen, |
michael@0 | 334 | entryNameLen - mNameLen, |
michael@0 | 335 | esc_Minimal | esc_AlwaysCopy, |
michael@0 | 336 | mBuffer); |
michael@0 | 337 | |
michael@0 | 338 | mBuffer.Append(' '); |
michael@0 | 339 | mBuffer.AppendInt(ze->RealSize(), 10); |
michael@0 | 340 | mBuffer.Append(itemLastModTime); // starts/ends with ' ' |
michael@0 | 341 | if (ze->IsDirectory()) |
michael@0 | 342 | mBuffer.AppendLiteral("DIRECTORY\n"); |
michael@0 | 343 | else |
michael@0 | 344 | mBuffer.AppendLiteral("FILE\n"); |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | // Copy up to the desired amount of data to buffer |
michael@0 | 348 | numRead += CopyDataToBuffer(aBuffer, aCount); |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | *aBytesRead = numRead; |
michael@0 | 352 | return NS_OK; |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | uint32_t |
michael@0 | 356 | nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount) |
michael@0 | 357 | { |
michael@0 | 358 | const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos); |
michael@0 | 359 | |
michael@0 | 360 | if (writeLength > 0) { |
michael@0 | 361 | memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength); |
michael@0 | 362 | mCurPos += writeLength; |
michael@0 | 363 | aCount -= writeLength; |
michael@0 | 364 | aBuffer += writeLength; |
michael@0 | 365 | } |
michael@0 | 366 | |
michael@0 | 367 | // return number of bytes copied to the buffer so the |
michael@0 | 368 | // Read method can return the number of bytes copied |
michael@0 | 369 | return writeLength; |
michael@0 | 370 | } |