// nsIStreamListener implementation NS_IMETHODIMP nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) { nsresult rv = NS_OK; AutoFree buffer(nullptr); uint32_t bufLen = 0, read = 0; NS_ASSERTION(request, "multimixed converter needs a request"); nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); if (NS_FAILED(rv)) return rv; // fill buffer { bufLen = count + mBufLen; NS_ENSURE_TRUE((bufLen >= count) && (bufLen >= mBufLen), NS_ERROR_FAILURE); buffer = (char *) malloc(bufLen); if (!buffer) return NS_ERROR_OUT_OF_MEMORY; if (mBufLen) { // incorporate any buffered data into the parsing memcpy(buffer, mBuffer, mBufLen); free(mBuffer); mBuffer = 0; mBufLen = 0; } rv = inStr->Read(buffer + (bufLen - count), count, &read); if (NS_FAILED(rv) || read == 0) return rv; NS_ASSERTION(read == count, "poor data size assumption"); } char *cursor = buffer; if (mFirstOnData) { // this is the first OnData() for this request. some servers // don't bother sending a token in the first "part." This is // illegal, but we'll handle the case anyway by shoving the // boundary token in for the server. mFirstOnData = false; NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data"); const char * token = mToken.get(); PushOverLine(cursor, bufLen); bool needMoreChars = bufLen < mTokenLen + 2; nsAutoCString firstBuffer(buffer, bufLen); int32_t posCR = firstBuffer.Find("\r"); if (needMoreChars || (posCR == kNotFound)) { // we don't have enough data yet to make this comparison. // skip this check, and try again the next time OnData() // is called. mFirstOnData = true; } else if (mPackagedApp) { // We need to check the line starts with -- if (!StringBeginsWith(firstBuffer, NS_LITERAL_CSTRING("--"))) { return NS_ERROR_FAILURE; } // If the boundary was set in the header, // we need to check it matches with the one in the file. if (mTokenLen && !StringBeginsWith(Substring(firstBuffer, 2), mToken)) { return NS_ERROR_FAILURE; } // Save the token. if (!mTokenLen) { mToken = nsCString(Substring(firstBuffer, 2).BeginReading(), posCR - 2); mTokenLen = mToken.Length(); } cursor = buffer; } else if (!PL_strnstr(cursor, token, mTokenLen + 2)) { char *newBuffer = (char *) realloc(buffer, bufLen + mTokenLen + 1); if (!newBuffer) return NS_ERROR_OUT_OF_MEMORY; buffer = newBuffer; memmove(buffer + mTokenLen + 1, buffer, bufLen); memcpy(buffer, token, mTokenLen); buffer[mTokenLen] = '\n'; bufLen += (mTokenLen + 1); // need to reset cursor to the buffer again (bug 100595) cursor = buffer; } } char *token = nullptr; // This may get initialized by ParseHeaders and the resulting // HttpResponseHead will be passed to nsPartChannel by SendStart if (mProcessingHeaders) { // we were not able to process all the headers // for this "part" given the previous buffer given to // us in the previous OnDataAvailable callback. bool done = false; rv = ParseHeaders(channel, cursor, bufLen, &done); if (NS_FAILED(rv)) return rv; if (done) { mProcessingHeaders = false; rv = SendStart(channel); if (NS_FAILED(rv)) return rv; } } int32_t tokenLinefeed = 1; while ( (token = FindToken(cursor, bufLen)) ) { if (((token + mTokenLen) < (cursor + bufLen)) && (*(token + mTokenLen + 1) == '-')) { // This was the last delimiter so we can stop processing rv = SendData(cursor, LengthToToken(cursor, token)); if (NS_FAILED(rv)) return rv; if (mPartChannel) { mPartChannel->SetIsLastPart(); } return SendStop(NS_OK); } if (!mNewPart && token > cursor) { // headers are processed, we're pushing data now. NS_ASSERTION(!mProcessingHeaders, "we should be pushing raw data"); rv = SendData(cursor, LengthToToken(cursor, token)); bufLen -= token - cursor; if (NS_FAILED(rv)) return rv; } // XXX else NS_ASSERTION(token == cursor, "?"); token += mTokenLen; bufLen -= mTokenLen; tokenLinefeed = PushOverLine(token, bufLen); if (mNewPart) { // parse headers mNewPart = false; cursor = token; bool done = false; rv = ParseHeaders(channel, cursor, bufLen, &done); if (NS_FAILED(rv)) return rv; if (done) { rv = SendStart(channel); if (NS_FAILED(rv)) return rv; } else { // we haven't finished processing header info. // we'll break out and try to process later. mProcessingHeaders = true; break; } } else { mNewPart = true; // Reset state so we don't carry it over from part to part mContentType.Truncate(); mContentLength = UINT64_MAX; mContentDisposition.Truncate(); mIsByteRangeRequest = false; mByteRangeStart = 0; mByteRangeEnd = 0; rv = SendStop(NS_OK); if (NS_FAILED(rv)) return rv; // reset the token to front. this allows us to treat // the token as a starting token. token -= mTokenLen + tokenLinefeed; bufLen += mTokenLen + tokenLinefeed; cursor = token; } } // at this point, we want to buffer up whatever amount (bufLen) // we have leftover. However, we *always* want to ensure that // we buffer enough data to handle a broken token. // carry over uint32_t bufAmt = 0; if (mProcessingHeaders) bufAmt = bufLen; else if (bufLen) { // if the data ends in a linefeed, and we're in the middle // of a "part" (ie. mPartChannel exists) don't bother // buffering, go ahead and send the data we have. Otherwise // if we don't have a channel already, then we don't even // have enough info to start a part, go ahead and buffer // enough to collect a boundary token. if (!mPartChannel || !(cursor[bufLen-1] == nsCRT::LF) ) bufAmt = std::min(mTokenLen - 1, bufLen); } if (bufAmt) { rv = BufferData(cursor + (bufLen - bufAmt), bufAmt); if (NS_FAILED(rv)) return rv; bufLen -= bufAmt; } if (bufLen) { rv = SendData(cursor, bufLen); if (NS_FAILED(rv)) return rv; } return rv; }
PRemoteOpenFileParent* NeckoParent::AllocPRemoteOpenFileParent(const URIParams& aURI, const OptionalURIParams& aAppURI) { nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri); if (!fileURL) { return nullptr; } // security checks if (UsingNeckoIPCSecurity()) { nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID); if (!appsService) { return nullptr; } bool haveValidBrowser = false; bool hasManage = false; nsCOMPtr<mozIApplication> mozApp; for (uint32_t i = 0; i < Manager()->ManagedPBrowserParent().Length(); i++) { nsRefPtr<TabParent> tabParent = static_cast<TabParent*>(Manager()->ManagedPBrowserParent()[i]); uint32_t appId = tabParent->OwnOrContainingAppId(); nsresult rv = appsService->GetAppByLocalId(appId, getter_AddRefs(mozApp)); if (NS_FAILED(rv) || !mozApp) { continue; } hasManage = false; rv = mozApp->HasPermission("webapps-manage", &hasManage); if (NS_FAILED(rv)) { continue; } haveValidBrowser = true; break; } if (!haveValidBrowser) { return nullptr; } nsAutoCString requestedPath; fileURL->GetPath(requestedPath); NS_UnescapeURL(requestedPath); // Check if we load the whitelisted app uri for the neterror page. bool netErrorWhiteList = false; nsCOMPtr<nsIURI> appUri = DeserializeURI(aAppURI); if (appUri) { nsAdoptingString netErrorURI; netErrorURI = Preferences::GetString("b2g.neterror.url"); if (netErrorURI) { nsAutoCString spec; appUri->GetSpec(spec); netErrorWhiteList = spec.Equals(NS_ConvertUTF16toUTF8(netErrorURI).get()); } } if (hasManage || netErrorWhiteList) { // webapps-manage permission means allow reading any application.zip file // in either the regular webapps directory, or the core apps directory (if // we're using one). NS_NAMED_LITERAL_CSTRING(appzip, "/application.zip"); nsAutoCString pathEnd; requestedPath.Right(pathEnd, appzip.Length()); if (!pathEnd.Equals(appzip)) { return nullptr; } nsAutoCString pathStart; requestedPath.Left(pathStart, mWebAppsBasePath.Length()); if (!pathStart.Equals(mWebAppsBasePath)) { if (mCoreAppsBasePath.IsEmpty()) { return nullptr; } requestedPath.Left(pathStart, mCoreAppsBasePath.Length()); if (!pathStart.Equals(mCoreAppsBasePath)) { return nullptr; } } // Finally: make sure there are no "../" in URI. // Note: not checking for symlinks (would cause I/O for each path // component). So it's up to us to avoid creating symlinks that could // provide attack vectors. if (PL_strnstr(requestedPath.BeginReading(), "/../", requestedPath.Length())) { printf_stderr("NeckoParent::AllocPRemoteOpenFile: " "FATAL error: requested file URI '%s' contains '/../' " "KILLING CHILD PROCESS\n", requestedPath.get()); return nullptr; } } else { // regular packaged apps can only access their own application.zip file nsAutoString basePath; nsresult rv = mozApp->GetBasePath(basePath); if (NS_FAILED(rv)) { return nullptr; } nsAutoString uuid; rv = mozApp->GetId(uuid); if (NS_FAILED(rv)) { return nullptr; } nsPrintfCString mustMatch("%s/%s/application.zip", NS_LossyConvertUTF16toASCII(basePath).get(), NS_LossyConvertUTF16toASCII(uuid).get()); if (!requestedPath.Equals(mustMatch)) { printf_stderr("NeckoParent::AllocPRemoteOpenFile: " "FATAL error: app without webapps-manage permission is " "requesting file '%s' but is only allowed to open its " "own application.zip at %s: KILLING CHILD PROCESS\n", requestedPath.get(), mustMatch.get()); return nullptr; } } } RemoteOpenFileParent* parent = new RemoteOpenFileParent(fileURL); return parent; }