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