1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/modules/libjar/nsJARInputStream.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,370 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* nsJARInputStream.cpp 1.6 + * 1.7 + * This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "nsJARInputStream.h" 1.12 +#include "zipstruct.h" // defines ZIP compression codes 1.13 +#include "nsZipArchive.h" 1.14 + 1.15 +#include "nsNetUtil.h" 1.16 +#include "nsEscape.h" 1.17 +#include "nsIFile.h" 1.18 +#include "nsDebug.h" 1.19 +#include <algorithm> 1.20 +#if defined(XP_WIN) 1.21 +#include <windows.h> 1.22 +#endif 1.23 + 1.24 +/*--------------------------------------------- 1.25 + * nsISupports implementation 1.26 + *--------------------------------------------*/ 1.27 + 1.28 +NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream) 1.29 + 1.30 +/*---------------------------------------------------------- 1.31 + * nsJARInputStream implementation 1.32 + *--------------------------------------------------------*/ 1.33 + 1.34 +nsresult 1.35 +nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item) 1.36 +{ 1.37 + nsresult rv = NS_OK; 1.38 + NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); 1.39 + NS_ABORT_IF_FALSE(item, "Argument may not be null"); 1.40 + 1.41 + // Mark it as closed, in case something fails in initialisation 1.42 + mMode = MODE_CLOSED; 1.43 + //-- prepare for the compression type 1.44 + switch (item->Compression()) { 1.45 + case STORED: 1.46 + mMode = MODE_COPY; 1.47 + break; 1.48 + 1.49 + case DEFLATED: 1.50 + rv = gZlibInit(&mZs); 1.51 + NS_ENSURE_SUCCESS(rv, rv); 1.52 + 1.53 + mMode = MODE_INFLATE; 1.54 + mInCrc = item->CRC32(); 1.55 + mOutCrc = crc32(0L, Z_NULL, 0); 1.56 + break; 1.57 + 1.58 + default: 1.59 + return NS_ERROR_NOT_IMPLEMENTED; 1.60 + } 1.61 + 1.62 + // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data 1.63 + mFd = aJar->mZip->GetFD(); 1.64 + mZs.next_in = (Bytef *)aJar->mZip->GetData(item); 1.65 + if (!mZs.next_in) 1.66 + return NS_ERROR_FILE_CORRUPTED; 1.67 + mZs.avail_in = item->Size(); 1.68 + mOutSize = item->RealSize(); 1.69 + mZs.total_out = 0; 1.70 + return NS_OK; 1.71 +} 1.72 + 1.73 +nsresult 1.74 +nsJARInputStream::InitDirectory(nsJAR* aJar, 1.75 + const nsACString& aJarDirSpec, 1.76 + const char* aDir) 1.77 +{ 1.78 + NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); 1.79 + NS_ABORT_IF_FALSE(aDir, "Argument may not be null"); 1.80 + 1.81 + // Mark it as closed, in case something fails in initialisation 1.82 + mMode = MODE_CLOSED; 1.83 + 1.84 + // Keep the zipReader for getting the actual zipItems 1.85 + mJar = aJar; 1.86 + nsZipFind *find; 1.87 + nsresult rv; 1.88 + // We can get aDir's contents as strings via FindEntries 1.89 + // with the following pattern (see nsIZipReader.findEntries docs) 1.90 + // assuming dirName is properly escaped: 1.91 + // 1.92 + // dirName + "?*~" + dirName + "?*/?*" 1.93 + nsDependentCString dirName(aDir); 1.94 + mNameLen = dirName.Length(); 1.95 + 1.96 + // iterate through dirName and copy it to escDirName, escaping chars 1.97 + // which are special at the "top" level of the regexp so FindEntries 1.98 + // works correctly 1.99 + nsAutoCString escDirName; 1.100 + const char* curr = dirName.BeginReading(); 1.101 + const char* end = dirName.EndReading(); 1.102 + while (curr != end) { 1.103 + switch (*curr) { 1.104 + case '*': 1.105 + case '?': 1.106 + case '$': 1.107 + case '[': 1.108 + case ']': 1.109 + case '^': 1.110 + case '~': 1.111 + case '(': 1.112 + case ')': 1.113 + case '\\': 1.114 + escDirName.Append('\\'); 1.115 + // fall through 1.116 + default: 1.117 + escDirName.Append(*curr); 1.118 + } 1.119 + ++curr; 1.120 + } 1.121 + nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") + 1.122 + escDirName + NS_LITERAL_CSTRING("?*/?*"); 1.123 + rv = mJar->mZip->FindInit(pattern.get(), &find); 1.124 + if (NS_FAILED(rv)) return rv; 1.125 + 1.126 + const char *name; 1.127 + uint16_t nameLen; 1.128 + while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) { 1.129 + // Must copy, to make it zero-terminated 1.130 + mArray.AppendElement(nsCString(name,nameLen)); 1.131 + } 1.132 + delete find; 1.133 + 1.134 + if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) { 1.135 + return NS_ERROR_FAILURE; // no error translation 1.136 + } 1.137 + 1.138 + // Sort it 1.139 + mArray.Sort(); 1.140 + 1.141 + mBuffer.AssignLiteral("300: "); 1.142 + mBuffer.Append(aJarDirSpec); 1.143 + mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n"); 1.144 + 1.145 + // Open for reading 1.146 + mMode = MODE_DIRECTORY; 1.147 + mZs.total_out = 0; 1.148 + mArrPos = 0; 1.149 + return NS_OK; 1.150 +} 1.151 + 1.152 +NS_IMETHODIMP 1.153 +nsJARInputStream::Available(uint64_t *_retval) 1.154 +{ 1.155 + // A lot of callers don't check the error code. 1.156 + // They just use the _retval value. 1.157 + *_retval = 0; 1.158 + 1.159 + switch (mMode) { 1.160 + case MODE_NOTINITED: 1.161 + break; 1.162 + 1.163 + case MODE_CLOSED: 1.164 + return NS_BASE_STREAM_CLOSED; 1.165 + 1.166 + case MODE_DIRECTORY: 1.167 + *_retval = mBuffer.Length(); 1.168 + break; 1.169 + 1.170 + case MODE_INFLATE: 1.171 + case MODE_COPY: 1.172 + *_retval = mOutSize - mZs.total_out; 1.173 + break; 1.174 + } 1.175 + 1.176 + return NS_OK; 1.177 +} 1.178 + 1.179 +NS_IMETHODIMP 1.180 +nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) 1.181 +{ 1.182 + NS_ENSURE_ARG_POINTER(aBuffer); 1.183 + NS_ENSURE_ARG_POINTER(aBytesRead); 1.184 + 1.185 + *aBytesRead = 0; 1.186 + 1.187 + nsresult rv = NS_OK; 1.188 +MOZ_WIN_MEM_TRY_BEGIN 1.189 + switch (mMode) { 1.190 + case MODE_NOTINITED: 1.191 + return NS_OK; 1.192 + 1.193 + case MODE_CLOSED: 1.194 + return NS_BASE_STREAM_CLOSED; 1.195 + 1.196 + case MODE_DIRECTORY: 1.197 + return ReadDirectory(aBuffer, aCount, aBytesRead); 1.198 + 1.199 + case MODE_INFLATE: 1.200 + if (mFd) { 1.201 + rv = ContinueInflate(aBuffer, aCount, aBytesRead); 1.202 + } 1.203 + // be aggressive about releasing the file! 1.204 + // note that sometimes, we will release mFd before we've finished 1.205 + // deflating - this is because zlib buffers the input 1.206 + if (mZs.avail_in == 0) { 1.207 + mFd = nullptr; 1.208 + } 1.209 + break; 1.210 + 1.211 + case MODE_COPY: 1.212 + if (mFd) { 1.213 + uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out)); 1.214 + if (count) { 1.215 + memcpy(aBuffer, mZs.next_in + mZs.total_out, count); 1.216 + mZs.total_out += count; 1.217 + } 1.218 + *aBytesRead = count; 1.219 + } 1.220 + // be aggressive about releasing the file! 1.221 + // note that sometimes, we will release mFd before we've finished copying. 1.222 + if (mZs.total_out >= mOutSize) { 1.223 + mFd = nullptr; 1.224 + } 1.225 + break; 1.226 + } 1.227 +MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE) 1.228 + return rv; 1.229 +} 1.230 + 1.231 +NS_IMETHODIMP 1.232 +nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) 1.233 +{ 1.234 + // don't have a buffer to read from, so this better not be called! 1.235 + return NS_ERROR_NOT_IMPLEMENTED; 1.236 +} 1.237 + 1.238 +NS_IMETHODIMP 1.239 +nsJARInputStream::IsNonBlocking(bool *aNonBlocking) 1.240 +{ 1.241 + *aNonBlocking = false; 1.242 + return NS_OK; 1.243 +} 1.244 + 1.245 +NS_IMETHODIMP 1.246 +nsJARInputStream::Close() 1.247 +{ 1.248 + if (mMode == MODE_INFLATE) { 1.249 + inflateEnd(&mZs); 1.250 + } 1.251 + mMode = MODE_CLOSED; 1.252 + mFd = nullptr; 1.253 + return NS_OK; 1.254 +} 1.255 + 1.256 +nsresult 1.257 +nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount, 1.258 + uint32_t* aBytesRead) 1.259 +{ 1.260 + // No need to check the args, ::Read did that, but assert them at least 1.261 + NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); 1.262 + NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); 1.263 + 1.264 + // Keep old total_out count 1.265 + const uint32_t oldTotalOut = mZs.total_out; 1.266 + 1.267 + // make sure we aren't reading too much 1.268 + mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut)); 1.269 + mZs.next_out = (unsigned char*)aBuffer; 1.270 + 1.271 + // now inflate 1.272 + int zerr = inflate(&mZs, Z_SYNC_FLUSH); 1.273 + if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) 1.274 + return NS_ERROR_FILE_CORRUPTED; 1.275 + 1.276 + *aBytesRead = (mZs.total_out - oldTotalOut); 1.277 + 1.278 + // Calculate the CRC on the output 1.279 + mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead); 1.280 + 1.281 + // be aggressive about ending the inflation 1.282 + // for some reason we don't always get Z_STREAM_END 1.283 + if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) { 1.284 + inflateEnd(&mZs); 1.285 + 1.286 + // stop returning valid data as soon as we know we have a bad CRC 1.287 + if (mOutCrc != mInCrc) 1.288 + return NS_ERROR_FILE_CORRUPTED; 1.289 + } 1.290 + 1.291 + return NS_OK; 1.292 +} 1.293 + 1.294 +nsresult 1.295 +nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) 1.296 +{ 1.297 + // No need to check the args, ::Read did that, but assert them at least 1.298 + NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); 1.299 + NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); 1.300 + 1.301 + // If the buffer contains data, copy what's there up to the desired amount 1.302 + uint32_t numRead = CopyDataToBuffer(aBuffer, aCount); 1.303 + 1.304 + if (aCount > 0) { 1.305 + // empty the buffer and start writing directory entry lines to it 1.306 + mBuffer.Truncate(); 1.307 + mCurPos = 0; 1.308 + const uint32_t arrayLen = mArray.Length(); 1.309 + 1.310 + for ( ;aCount > mBuffer.Length(); mArrPos++) { 1.311 + // have we consumed all the directory contents? 1.312 + if (arrayLen <= mArrPos) 1.313 + break; 1.314 + 1.315 + const char * entryName = mArray[mArrPos].get(); 1.316 + uint32_t entryNameLen = mArray[mArrPos].Length(); 1.317 + nsZipItem* ze = mJar->mZip->GetItem(entryName); 1.318 + NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); 1.319 + 1.320 + // Last Modified Time 1.321 + PRExplodedTime tm; 1.322 + PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm); 1.323 + char itemLastModTime[65]; 1.324 + PR_FormatTimeUSEnglish(itemLastModTime, 1.325 + sizeof(itemLastModTime), 1.326 + " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", 1.327 + &tm); 1.328 + 1.329 + // write a 201: line to the buffer for this item 1.330 + // 200: filename content-length last-modified file-type 1.331 + mBuffer.AppendLiteral("201: "); 1.332 + 1.333 + // Names must be escaped and relative, so use the pre-calculated length 1.334 + // of the directory name as the offset into the string 1.335 + // NS_EscapeURL adds the escaped URL to the give string buffer 1.336 + NS_EscapeURL(entryName + mNameLen, 1.337 + entryNameLen - mNameLen, 1.338 + esc_Minimal | esc_AlwaysCopy, 1.339 + mBuffer); 1.340 + 1.341 + mBuffer.Append(' '); 1.342 + mBuffer.AppendInt(ze->RealSize(), 10); 1.343 + mBuffer.Append(itemLastModTime); // starts/ends with ' ' 1.344 + if (ze->IsDirectory()) 1.345 + mBuffer.AppendLiteral("DIRECTORY\n"); 1.346 + else 1.347 + mBuffer.AppendLiteral("FILE\n"); 1.348 + } 1.349 + 1.350 + // Copy up to the desired amount of data to buffer 1.351 + numRead += CopyDataToBuffer(aBuffer, aCount); 1.352 + } 1.353 + 1.354 + *aBytesRead = numRead; 1.355 + return NS_OK; 1.356 +} 1.357 + 1.358 +uint32_t 1.359 +nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount) 1.360 +{ 1.361 + const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos); 1.362 + 1.363 + if (writeLength > 0) { 1.364 + memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength); 1.365 + mCurPos += writeLength; 1.366 + aCount -= writeLength; 1.367 + aBuffer += writeLength; 1.368 + } 1.369 + 1.370 + // return number of bytes copied to the buffer so the 1.371 + // Read method can return the number of bytes copied 1.372 + return writeLength; 1.373 +}