String XSSAuditor::decodeURL(const String& string, const TextEncoding& encoding, bool decodeEntities, bool decodeURLEscapeSequencesTwice) { String result; String url = string; url.replace('+', ' '); result = decodeURLEscapeSequences(url); CString utf8Url = result.utf8(); String decodedResult = encoding.decode(utf8Url.data(), utf8Url.length()); if (!decodedResult.isEmpty()) result = decodedResult; if (decodeURLEscapeSequencesTwice) { result = decodeURLEscapeSequences(result); utf8Url = result.utf8(); decodedResult = encoding.decode(utf8Url.data(), utf8Url.length()); if (!decodedResult.isEmpty()) result = decodedResult; } if (decodeEntities) result = decodeHTMLEntities(result); return result; }
bool ScriptController::executeIfJavaScriptURL(const KURL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL) { if (!protocolIsJavaScript(url)) return false; if (!m_frame->page() || !m_frame->page()->javaScriptURLsAreAllowed() || !m_frame->document()->contentSecurityPolicy()->allowJavaScriptURLs() || m_frame->inViewSourceMode()) return true; // We need to hold onto the Frame here because executing script can // destroy the frame. RefPtr<Frame> protector(m_frame); RefPtr<Document> ownerDocument(m_frame->document()); const int javascriptSchemeLength = sizeof("javascript:") - 1; String decodedURL = decodeURLEscapeSequences(url.string()); ScriptValue result = executeScript(decodedURL.substring(javascriptSchemeLength)); // If executing script caused this frame to be removed from the page, we // don't want to try to replace its document! if (!m_frame->page()) return true; String scriptResult; #if USE(JSC) JSDOMWindowShell* shell = windowShell(mainThreadNormalWorld()); JSC::ExecState* exec = shell->window()->globalExec(); if (!result.getString(exec, scriptResult)) return true; #else if (!result.getString(scriptResult)) return true; #endif // FIXME: We should always replace the document, but doing so // synchronously can cause crashes: // http://bugs.webkit.org/show_bug.cgi?id=16782 if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) { // We're still in a frame, so there should be a DocumentLoader. ASSERT(m_frame->document()->loader()); // DocumentWriter::replaceDocument can cause the DocumentLoader to get deref'ed and possible destroyed, // so protect it with a RefPtr. if (RefPtr<DocumentLoader> loader = m_frame->document()->loader()) loader->writer()->replaceDocument(scriptResult, ownerDocument.get()); } return true; }
String KURL::fileSystemPath() const { if (!isValid()) return String(); if (isLocalFile()) return static_cast<QUrl>(*this).toLocalFile(); // A valid qrc resource path begins with a colon. if (protocolIs("qrc")) return ":" + decodeURLEscapeSequences(path()); return String(); }
// Tests that KURL::decodeURLEscapeSequences works as expected TEST(KURLTest, Decode) { struct DecodeCase { const char* input; const char* output; } decodeCases[] = { {"hello, world", "hello, world"}, {"%01%02%03%04%05%06%07%08%09%0a%0B%0C%0D%0e%0f/", "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0B\x0C\x0D\x0e\x0f/"}, {"%10%11%12%13%14%15%16%17%18%19%1a%1B%1C%1D%1e%1f/", "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1B\x1C\x1D\x1e\x1f/"}, {"%20%21%22%23%24%25%26%27%28%29%2a%2B%2C%2D%2e%2f/", " !\"#$%&'()*+,-.//"}, {"%30%31%32%33%34%35%36%37%38%39%3a%3B%3C%3D%3e%3f/", "0123456789:;<=>?/"}, {"%40%41%42%43%44%45%46%47%48%49%4a%4B%4C%4D%4e%4f/", "@ABCDEFGHIJKLMNO/"}, {"%50%51%52%53%54%55%56%57%58%59%5a%5B%5C%5D%5e%5f/", "PQRSTUVWXYZ[\\]^_/"}, {"%60%61%62%63%64%65%66%67%68%69%6a%6B%6C%6D%6e%6f/", "`abcdefghijklmno/"}, {"%70%71%72%73%74%75%76%77%78%79%7a%7B%7C%7D%7e%7f/", "pqrstuvwxyz{|}~\x7f/"}, // Test un-UTF-8-ization. {"%e4%bd%a0%e5%a5%bd", "\xe4\xbd\xa0\xe5\xa5\xbd"}, }; for (size_t i = 0; i < arraysize(decodeCases); i++) { String input(decodeCases[i].input); String str = decodeURLEscapeSequences(input); EXPECT_STREQ(decodeCases[i].output, str.utf8().data()); } // Our decode should decode %00 String zero = decodeURLEscapeSequences("%00"); EXPECT_STRNE("%00", zero.utf8().data()); // Test the error behavior for invalid UTF-8 (we differ from WebKit here). String invalid = decodeURLEscapeSequences("%e4%a0%e5%a5%bd"); UChar invalidExpectedHelper[4] = { 0x00e4, 0x00a0, 0x597d, 0 }; String invalidExpected( reinterpret_cast<const ::UChar*>(invalidExpectedHelper), 3); EXPECT_EQ(invalidExpected, invalid); }
bool DOMFileSystemBase::crackFileSystemURL(const KURL& url, FileSystemType& type, String& filePath) { if (!url.protocolIs("filesystem")) return false; if (!url.innerURL()) return false; String typeString = url.innerURL()->path().substring(1); if (!pathPrefixToFileSystemType(typeString, type)) return false; filePath = decodeURLEscapeSequences(url.path()); return true; }
bool ScriptController::executeIfJavaScriptURL(const KURL& url, bool userGesture, bool replaceDocument) { if (!protocolIsJavaScript(url)) return false; if (m_frame->page() && !m_frame->page()->javaScriptURLsAreAllowed()) return true; if (m_frame->inViewSourceMode()) return true; #if PLATFORM(APOLLO) // We should return true even though the script is not going to be executed. // Otherwise the frame will actually try to navigate to "javascript:" // which will eventually fail, but will also stop any other in progress requests in this page // like CSS files, images or JS files if (!m_frame->loader()->client()->canExecuteScriptURL()) return true; #endif const int javascriptSchemeLength = sizeof("javascript:") - 1; String decodedURL = decodeURLEscapeSequences(url.string()); ScriptValue result; if (xssAuditor()->canEvaluateJavaScriptURL(decodedURL)) result = executeScript(decodedURL.substring(javascriptSchemeLength), userGesture, AllowXSS); String scriptResult; #if USE(JSC) JSDOMWindowShell* shell = windowShell(mainThreadNormalWorld()); JSC::ExecState* exec = shell->window()->globalExec(); if (!result.getString(exec, scriptResult)) return true; #else if (!result.getString(scriptResult)) return true; #endif // FIXME: We should always replace the document, but doing so // synchronously can cause crashes: // http://bugs.webkit.org/show_bug.cgi?id=16782 if (replaceDocument) m_frame->loader()->writer()->replaceDocument(scriptResult); return true; }
void didReceiveResponse(ResourceHandle*, const ResourceResponse& response) { m_response = adoptGRef(response.toSoupMessage()); m_download->didReceiveResponse(response); if (response.httpStatusCode() >= 400) { downloadFailed(platformDownloadNetworkError(response.httpStatusCode(), response.url().string(), response.httpStatusText())); return; } String suggestedFilename = response.suggestedFilename(); if (suggestedFilename.isEmpty()) { URL url = response.url(); url.setQuery(String()); url.removeFragmentIdentifier(); suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent()); } bool overwrite; String destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename, overwrite); if (destinationURI.isEmpty()) { #if PLATFORM(GTK) GOwnPtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data())); String errorMessage = String::fromUTF8(buffer.get()); #else String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename); #endif downloadFailed(platformDownloadDestinationError(response, errorMessage)); return; } GRefPtr<GFile> file = adoptGRef(g_file_new_for_uri(destinationURI.utf8().data())); GOwnPtr<GError> error; m_outputStream = adoptGRef(g_file_replace(file.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr())); if (!m_outputStream) { downloadFailed(platformDownloadDestinationError(response, error->message)); return; } GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new()); g_file_info_set_attribute_string(info.get(), "metadata::download-uri", response.url().string().utf8().data()); g_file_set_attributes_async(file.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, 0, 0, 0); m_download->didCreateDestination(destinationURI); }
void MediaPlayerPrivateEA::load(const String& url) { if (!mpEAWebKitView) mpEAWebKitView = GetEAWebKitMediaView(mpWebCorePlayer); // Note: We get the autoplay and loop flags from the element directly. HTMLMediaElement* element = static_cast<HTMLMediaElement*>(mpWebCorePlayer->mediaPlayerClient()); mIsLooping = element->loop(); MediaUpdateInfo& info = GetMediaUpdateInfo(); info.mIsLooping = mIsLooping; WebCore::KURL kurl(WebCore::KURL(), url); String escapedUrl = kurl.protocolIsInHTTPFamily() ? url : decodeURLEscapeSequences(url); GetFixedString(info.mURI)->assign(escapedUrl.characters(), escapedUrl.length()); ClientUpdate(MediaUpdateInfo::kLoad); // Set info up to play setMuted(mpWebCorePlayer->muted()); setVolume(mpWebCorePlayer->volume()); if(mNetworkState != MediaPlayer::Loading) { mNetworkState = MediaPlayer::Loading; mpWebCorePlayer->networkStateChanged(); } if(mReadyState != MediaPlayer::HaveNothing) { mReadyState = MediaPlayer::HaveNothing; mpWebCorePlayer->readyStateChanged(); } bool autoPlay = element->autoplay(); if(autoPlay) mpWebCorePlayer->play(); // This just calls our play. ClientUpdateStates(); // This will update the pause/play state right away. // Set up an update timer to control the repaint and playback. if (mUpdateTimer.isActive()) mUpdateTimer.stop(); const double kUpdateTime = 1.0/60.0; // We want to update at 60hz so we keep up with the render calls. mUpdateTimer.startRepeating(kUpdateTime); }
bool CSPSourceList::parsePath(const UChar* begin, const UChar* end, String& path) { ASSERT(begin <= end); ASSERT(path.isEmpty()); const UChar* position = begin; skipWhile<UChar, isPathComponentCharacter>(position, end); // path/to/file.js?query=string || path/to/file.js#anchor // ^ ^ if (position < end) m_policy->reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position); path = decodeURLEscapeSequences(String(begin, position - begin)); ASSERT(position <= end); ASSERT(position == end || (*position == '#' || *position == '?')); return true; }
void PluginView::performJavaScriptURLRequest(URLRequest* request) { ASSERT(protocolIsJavaScript(request->request().url())); RefPtr<Frame> frame = m_pluginElement->document()->frame(); if (!frame) return; String jsString = decodeURLEscapeSequences(request->request().url().string().substring(sizeof("javascript:") - 1)); if (!request->target().isNull()) { // For security reasons, only allow JS requests to be made on the frame that contains the plug-in. if (frame->tree()->find(request->target()) != frame) { // Let the plug-in know that its frame load failed. m_plugin->frameDidFail(request->requestID(), false); return; } } // Evaluate the JavaScript code. Note that running JavaScript here could cause the plug-in to be destroyed, so we // grab references to the plug-in here. RefPtr<Plugin> plugin = m_plugin; ScriptValue result = frame->script()->executeScript(jsString, request->allowPopups()); // Check if evaluating the JavaScript destroyed the plug-in. if (!plugin->controller()) return; // Don't notify the plug-in at all about targeted javascript: requests. This matches Mozilla and WebKit1. if (!request->target().isNull()) return; #if USE(JSC) ScriptState* scriptState = frame->script()->globalObject(pluginWorld())->globalExec(); #elif USE(V8) ScriptState* scriptState = ScriptState::forContext(frame->script()->proxy()->context(frame.get())); #endif String resultString; result.getString(scriptState, resultString); // Send the result back to the plug-in. plugin->didEvaluateJavaScript(request->requestID(), resultString); }
WebString WebPluginContainerImpl::executeScriptURL(const WebURL& url, bool popupsAllowed) { Frame* frame = m_element->document()->frame(); if (!frame) return WebString(); const KURL& kurl = url; ASSERT(kurl.protocolIs("javascript")); String script = decodeURLEscapeSequences( kurl.string().substring(strlen("javascript:"))); ScriptValue result = frame->script()->executeScript(script, popupsAllowed); // Failure is reported as a null string. String resultStr; result.getString(resultStr); return resultStr; }
void didReceiveResponse(ResourceHandle*, const ResourceResponse& response) { m_response = response; m_download->didReceiveResponse(response); if (response.httpStatusCode() >= 400) { downloadFailed(platformDownloadNetworkError(response.httpStatusCode(), response.url().string(), response.httpStatusText())); return; } String suggestedFilename = response.suggestedFilename(); if (suggestedFilename.isEmpty()) { URL url = response.url(); url.setQuery(String()); url.removeFragmentIdentifier(); suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent()); } bool overwrite; m_destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename, overwrite); if (m_destinationURI.isEmpty()) { #if PLATFORM(GTK) GUniquePtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data())); String errorMessage = String::fromUTF8(buffer.get()); #else String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename); #endif downloadFailed(platformDownloadDestinationError(response, errorMessage)); return; } String intermediateURI = m_destinationURI + ".wkdownload"; m_intermediateFile = adoptGRef(g_file_new_for_uri(intermediateURI.utf8().data())); GOwnPtr<GError> error; m_outputStream = adoptGRef(g_file_replace(m_intermediateFile.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr())); if (!m_outputStream) { downloadFailed(platformDownloadDestinationError(response, error->message)); return; } m_download->didCreateDestination(m_destinationURI); }
bool DOMFileSystemBase::crackFileSystemURL(const KURL& url, FileSystemType& type, String& filePath) { if (!url.protocolIs("filesystem")) return false; if (!url.innerURL()) return false; String typeString = url.innerURL()->path().substring(1); if (typeString == temporaryPathPrefix) type = FileSystemTypeTemporary; else if (typeString == persistentPathPrefix) type = FileSystemTypePersistent; else if (typeString == externalPathPrefix) type = FileSystemTypeExternal; else return false; filePath = decodeURLEscapeSequences(url.path()); return true; }
void didReceiveResponse(ResourceHandle*, const ResourceResponse& response) { m_response = adoptGRef(response.toSoupMessage()); m_download->didReceiveResponse(response); if (response.httpStatusCode() >= 400) { downloadFailed(downloadNetworkError(ResourceError(errorDomainDownload, response.httpStatusCode(), response.url().string(), response.httpStatusText()))); return; } String suggestedFilename = response.suggestedFilename(); if (suggestedFilename.isEmpty()) { KURL url = response.url(); url.setQuery(String()); url.removeFragmentIdentifier(); suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent()); } bool overwrite; String destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename.utf8().data(), overwrite); if (destinationURI.isEmpty()) { GOwnPtr<char> errorMessage(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data())); downloadFailed(downloadDestinationError(response, errorMessage.get())); return; } GRefPtr<GFile> file = adoptGRef(g_file_new_for_uri(destinationURI.utf8().data())); GOwnPtr<GError> error; m_outputStream = adoptGRef(g_file_replace(file.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr())); if (!m_outputStream) { downloadFailed(downloadDestinationError(response, error->message)); return; } m_download->didCreateDestination(destinationURI); }
bool ScriptController::executeIfJavaScriptURL(const KURL& url, bool userGesture, bool replaceDocument) { if (!protocolIsJavaScript(url)) return false; if (m_frame->page() && !m_frame->page()->javaScriptURLsAreAllowed()) return true; if (m_frame->inViewSourceMode()) return true; const int javascriptSchemeLength = sizeof("javascript:") - 1; String script = decodeURLEscapeSequences(url.string().substring(javascriptSchemeLength)); ScriptValue result; if (xssAuditor()->canEvaluateJavaScriptURL(script)) result = executeScript(script, userGesture); String scriptResult; #if USE(JSC) JSDOMWindowShell* shell = windowShell(mainThreadNormalWorld()); JSC::ExecState* exec = shell->window()->globalExec(); if (!result.getString(exec, scriptResult)) return true; #else if (!result.getString(scriptResult)) return true; #endif // FIXME: We should always replace the document, but doing so // synchronously can cause crashes: // http://bugs.webkit.org/show_bug.cgi?id=16782 if (replaceDocument) m_frame->loader()->replaceDocument(scriptResult); return true; }
RefPtr<SecurityOrigin> SecurityOrigin::maybeCreateFromDatabaseIdentifier(const String& databaseIdentifier) { // Make sure there's a first separator size_t separator1 = databaseIdentifier.find(separatorCharacter); if (separator1 == notFound) return nullptr; // Make sure there's a second separator size_t separator2 = databaseIdentifier.reverseFind(separatorCharacter); if (separator2 == notFound) return nullptr; // Ensure there were at least 2 separator characters. Some hostnames on intranets have // underscores in them, so we'll assume that any additional underscores are part of the host. if (separator1 == separator2) return nullptr; // Make sure the port section is a valid port number or doesn't exist bool portOkay; int port = databaseIdentifier.right(databaseIdentifier.length() - separator2 - 1).toInt(&portOkay); bool portAbsent = (separator2 == databaseIdentifier.length() - 1); if (!(portOkay || portAbsent)) return nullptr; if (port < 0 || port > MaxAllowedPort) return nullptr; // Split out the 3 sections of data String protocol = databaseIdentifier.substring(0, separator1); String host = databaseIdentifier.substring(separator1 + 1, separator2 - separator1 - 1); host = decodeURLEscapeSequences(host); auto origin = create(URL(URL(), protocol + "://" + host + "/")); origin->m_port = port; return WTFMove(origin); }
void Page::userStyleSheetLocationChanged() { // FIXME: Eventually we will move to a model of just being handed the sheet // text instead of loading the URL ourselves. KURL url = m_settings->userStyleSheetLocation(); m_didLoadUserStyleSheet = false; m_userStyleSheet = String(); // Data URLs with base64-encoded UTF-8 style sheets are common. We can process them // synchronously and avoid using a loader. if (url.protocolIsData() && url.string().startsWith("data:text/css;charset=utf-8;base64,")) { m_didLoadUserStyleSheet = true; Vector<char> styleSheetAsUTF8; if (base64Decode(decodeURLEscapeSequences(url.string().substring(35)), styleSheetAsUTF8, Base64IgnoreWhitespace)) m_userStyleSheet = String::fromUTF8(styleSheetAsUTF8.data(), styleSheetAsUTF8.size()); } for (Frame* frame = mainFrame(); frame; frame = frame->tree()->traverseNext()) { if (frame->document()) frame->document()->styleEngine()->updatePageUserSheet(); } }
String substituteVariableReferences(const String& reference, Document* document, WMLVariableEscapingMode escapeMode) { ASSERT(document); if (reference.isEmpty()) return reference; WMLPageState* pageState = wmlPageStateForDocument(document); if (!pageState) return reference; bool isValid = true; String remainingInput = reference; String result; while (!remainingInput.isEmpty()) { ASSERT(isValid); int start = remainingInput.find("$"); if (start == -1) { // Consume all remaining characters, as there's nothing more to substitute result += remainingInput; break; } // Consume all characters until the variable reference beginning result += remainingInput.left(start); remainingInput.remove(0, start); // Transform adjacent dollar signs into a single dollar sign as string literal if (remainingInput[1] == '$') { result += "$"; remainingInput.remove(0, 2); continue; } String variableName; String conversionMode; if (remainingInput[1] == '(') { int referenceEndPosition = remainingInput.find(")"); if (referenceEndPosition == -1) { isValid = false; break; } variableName = remainingInput.substring(2, referenceEndPosition - 2); remainingInput.remove(0, referenceEndPosition + 1); // Determine variable conversion mode string int pos = variableName.find(':'); if (pos != -1) { conversionMode = variableName.substring(pos + 1, variableName.length() - (pos + 1)); variableName = variableName.left(pos); } } else { int length = remainingInput.length(); int referenceEndPosition = 1; for (; referenceEndPosition < length; ++referenceEndPosition) { if (!isValidVariableNameCharacter(remainingInput[referenceEndPosition])) break; } variableName = remainingInput.substring(1, referenceEndPosition - 1); remainingInput.remove(0, referenceEndPosition); } isValid = isValidVariableName(variableName); if (!isValid) break; ASSERT(!variableName.isEmpty()); String variableValue = pageState->getVariable(variableName); if (variableValue.isEmpty()) continue; if (containsVariableReference(variableValue, isValid)) { if (!isValid) break; variableValue = substituteVariableReferences(variableValue, document, escapeMode); continue; } if (!conversionMode.isEmpty()) { // Override default escape mode, if desired WMLVariableEscapingMode specifiedEscapeMode = WMLVariableEscapingNone; if ((isValid = isValidVariableEscapingModeString(conversionMode, specifiedEscapeMode))) escapeMode = specifiedEscapeMode; if (!isValid) break; } switch (escapeMode) { case WMLVariableEscapingNone: break; case WMLVariableEscapingEscape: variableValue = encodeWithURLEscapeSequences(variableValue); break; case WMLVariableEscapingUnescape: variableValue = decodeURLEscapeSequences(variableValue); break; } result += variableValue; ASSERT(isValid); } if (!isValid) { reportWMLError(document, WMLErrorInvalidVariableReference); return reference; } return result; }
// The cookie String passed into this method will only contian the name value pairs as well as other related cookie // attributes such as max-age and domain. Set-Cookie should never be part of this string. ParsedCookie* CookieParser::parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime) { ParsedCookie* res = new ParsedCookie(curTime); if (!res) LOG_AND_DELETE("Out of memory"); res->setProtocol(m_defaultCookieURL.protocol()); // Parse [NAME "="] VALUE unsigned tokenEnd = start; // Token end contains the position of the '=' or the end of a token unsigned pairEnd = start; // Pair end contains always the position of the ';' // find the *first* ';' and the '=' (if they exist) bool quoteFound = false; bool foundEqual = false; while (pairEnd < end && (cookie[pairEnd] != ';' || quoteFound)) { if (tokenEnd == start && cookie[pairEnd] == '=') { tokenEnd = pairEnd; foundEqual = true; } if (cookie[pairEnd] == '"') quoteFound = !quoteFound; pairEnd++; } unsigned tokenStart = start; bool hasName = false; // This is a hack to avoid changing too much in this // brutally brittle code. if (tokenEnd != start) { // There is a '=' so parse the NAME unsigned nameEnd = tokenEnd; // The tokenEnd is the position of the '=' so the nameEnd is one less nameEnd--; // Remove lightweight spaces. while (nameEnd && isLightweightSpace(cookie[nameEnd])) nameEnd--; while (tokenStart < nameEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; if (nameEnd + 1 <= tokenStart) LOG_AND_DELETE("Empty name. Rejecting the cookie"); String name = cookie.substring(tokenStart, nameEnd + 1 - start); res->setName(name); hasName = true; } // Now parse the VALUE tokenStart = tokenEnd + 1; if (!hasName) --tokenStart; // Skip lightweight spaces in our token while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1])) tokenEnd--; String value; if (tokenEnd == tokenStart) { // Firefox accepts empty value so we will do the same value = String(); } else value = cookie.substring(tokenStart, tokenEnd - tokenStart); if (hasName) res->setValue(value); else if (foundEqual) { delete res; return 0; } else res->setName(value); // No NAME=VALUE, only NAME while (pairEnd < end) { // Switch to the next pair as pairEnd is on the ';' and fast-forward any lightweight spaces. pairEnd++; while (pairEnd < end && isLightweightSpace(cookie[pairEnd])) pairEnd++; tokenStart = pairEnd; tokenEnd = tokenStart; // initialize token end to catch first '=' while (pairEnd < end && cookie[pairEnd] != ';') { if (tokenEnd == tokenStart && cookie[pairEnd] == '=') tokenEnd = pairEnd; pairEnd++; } // FIXME : should we skip lightweight spaces here ? unsigned length = tokenEnd - tokenStart; unsigned tokenStartSvg = tokenStart; String parsedValue; if (tokenStart != tokenEnd) { // There is an equal sign so remove lightweight spaces in VALUE tokenStart = tokenEnd + 1; while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1])) tokenEnd--; parsedValue = cookie.substring(tokenStart, tokenEnd - tokenStart); } else { // If the parsedValue is empty, initialise it in case we need it parsedValue = String(); // Handle a token without value. length = pairEnd - tokenStart; } // Detect which "cookie-av" is parsed // Look at the first char then parse the whole for performance issue switch (cookie[tokenStartSvg]) { case 'P': case 'p' : { if (length >= 4 && cookie.find("ath", tokenStartSvg + 1, false)) { // We need the path to be decoded to match those returned from KURL::path(). // The path attribute may or may not include percent-encoded characters. Fortunately // if there are no percent-encoded characters, decoding the url is a no-op. res->setPath(decodeURLEscapeSequences(parsedValue)); } else LOG_AND_DELETE("Invalid cookie %s (path)", cookie.ascii().data()); break; } case 'D': case 'd' : { if (length >= 6 && cookie.find("omain", tokenStartSvg + 1, false)) { if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"') parsedValue = parsedValue.substring(1, parsedValue.length() - 2); // If the domain does not start with a dot, add one for security checks, // For example: ab.c.com dose not domain match b.c.com; String realDomain = parsedValue[0] == '.' ? parsedValue : "." + parsedValue; res->setDomain(realDomain); } else LOG_AND_DELETE("Invalid cookie %s (domain)", cookie.ascii().data()); break; } case 'E' : case 'e' : { if (length >= 7 && cookie.find("xpires", tokenStartSvg + 1, false)) res->setExpiry(parsedValue); else LOG_AND_DELETE("Invalid cookie %s (expires)", cookie.ascii().data()); break; } case 'M' : case 'm' : { if (length >= 7 && cookie.find("ax-age", tokenStartSvg + 1, false)) res->setMaxAge(parsedValue); else LOG_AND_DELETE("Invalid cookie %s (max-age)", cookie.ascii().data()); break; } case 'C' : case 'c' : { if (length >= 7 && cookie.find("omment", tokenStartSvg + 1, false)) // We do not have room for the comment part (and so do Mozilla) so just log the comment. LOG(Network, "Comment %s for ParsedCookie : %s\n", parsedValue.ascii().data(), cookie.ascii().data()); else LOG_AND_DELETE("Invalid cookie %s (comment)", cookie.ascii().data()); break; } case 'V' : case 'v' : { if (length >= 7 && cookie.find("ersion", tokenStartSvg + 1, false)) { // Although the out-of-dated Cookie Spec(RFC2965, http://tools.ietf.org/html/rfc2965) defined // the value of version can only contain DIGIT, some random sites, e.g. https://devforums.apple.com // would use double quotation marks to quote the digit. So we need to get rid of them for compliance. if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"') parsedValue = parsedValue.substring(1, parsedValue.length() - 2); if (parsedValue.toInt() != 1) LOG_AND_DELETE("ParsedCookie version %d not supported (only support version=1)", parsedValue.toInt()); } else LOG_AND_DELETE("Invalid cookie %s (version)", cookie.ascii().data()); break; } case 'S' : case 's' : { // Secure is a standalone token ("Secure;") if (length >= 6 && cookie.find("ecure", tokenStartSvg + 1, false)) res->setSecureFlag(true); else LOG_AND_DELETE("Invalid cookie %s (secure)", cookie.ascii().data()); break; } case 'H': case 'h': { // HttpOnly is a standalone token ("HttpOnly;") if (length >= 8 && cookie.find("ttpOnly", tokenStartSvg + 1, false)) res->setIsHttpOnly(true); else LOG_AND_DELETE("Invalid cookie %s (HttpOnly)", cookie.ascii().data()); break; } default : { // If length == 0, we should be at the end of the cookie (case : ";\r") so ignore it if (length) LOG_ERROR("Invalid token for cookie %s", cookie.ascii().data()); } } } // Check if the cookie is valid with respect to the size limit. if (!res->isUnderSizeLimit()) LOG_AND_DELETE("ParsedCookie %s is above the 4kb in length : REJECTED", cookie.ascii().data()); // If some pair was not provided, during parsing then apply some default value // the rest has been done in the constructor. // If no domain was provided, set it to the host if (!res->domain()) res->setDefaultDomain(m_defaultCookieURL); // According to the Cookie Specificaiton (RFC6265, section 4.1.2.4 and 5.2.4, http://tools.ietf.org/html/rfc6265), // If no path was provided or the first character of the path value is not '/', set it to the host's path // // REFERENCE // 4.1.2.4. The Path Attribute // // The scope of each cookie is limited to a set of paths, controlled by // the Path attribute. If the server omits the Path attribute, the user // agent will use the "directory" of the request-uri's path component as // the default value. (See Section 5.1.4 for more details.) // ........... // 5.2.4. The Path Attribute // // If the attribute-name case-insensitively matches the string "Path", // the user agent MUST process the cookie-av as follows. // // If the attribute-value is empty or if the first character of the // attribute-value is not %x2F ("/"): // // Let cookie-path be the default-path. // // Otherwise: // // Let cookie-path be the attribute-value. // // Append an attribute to the cookie-attribute-list with an attribute- // name of Path and an attribute-value of cookie-path. if (!res->path() || !res->path().length() || !res->path().startsWith("/", false)) { String path = m_defaultCookieURL.string().substring(m_defaultCookieURL.pathStart(), m_defaultCookieURL.pathAfterLastSlash() - m_defaultCookieURL.pathStart() - 1); if (path.isEmpty()) path = "/"; // Since this is reading the raw url string, it could contain percent-encoded sequences. We // want it to be comparable to the return value of url.path(), which is not percent-encoded, // so we must remove the escape sequences. res->setPath(decodeURLEscapeSequences(path)); } return res; }
void PluginStream::startStream() { ASSERT(m_streamState == StreamBeforeStarted); const KURL& responseURL = m_resourceResponse.url(); // Some plugins (Flash) expect that javascript URLs are passed back decoded as this is the // format used when requesting the URL. if (protocolIsJavaScript(responseURL)) m_stream.url = fastStrDup(decodeURLEscapeSequences(responseURL.string()).utf8().data()); else m_stream.url = fastStrDup(responseURL.string().utf8().data()); CString mimeTypeStr = m_resourceResponse.mimeType().utf8(); long long expectedContentLength = m_resourceResponse.expectedContentLength(); if (m_resourceResponse.isHTTP()) { Vector<UChar> stringBuilder; String separator(": "); String statusLine = makeString("HTTP ", String::number(m_resourceResponse.httpStatusCode()), " OK\n"); stringBuilder.append(statusLine.characters(), statusLine.length()); HTTPHeaderMap::const_iterator end = m_resourceResponse.httpHeaderFields().end(); for (HTTPHeaderMap::const_iterator it = m_resourceResponse.httpHeaderFields().begin(); it != end; ++it) { stringBuilder.append(it->first.characters(), it->first.length()); stringBuilder.append(separator.characters(), separator.length()); stringBuilder.append(it->second.characters(), it->second.length()); stringBuilder.append('\n'); } m_headers = String::adopt(stringBuilder).utf8(); // If the content is encoded (most likely compressed), then don't send its length to the plugin, // which is only interested in the decoded length, not yet known at the moment. // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic. String contentEncoding = m_resourceResponse.httpHeaderField("Content-Encoding"); if (!contentEncoding.isNull() && contentEncoding != "identity") expectedContentLength = -1; } m_stream.headers = m_headers.data(); m_stream.pdata = 0; m_stream.ndata = this; m_stream.end = max(expectedContentLength, 0LL); m_stream.lastmodified = m_resourceResponse.lastModifiedDate(); m_stream.notifyData = m_notifyData; m_transferMode = NP_NORMAL; m_offset = 0; m_reason = WebReasonNone; // Protect the stream if destroystream is called from within the newstream handler RefPtr<PluginStream> protect(this); // calling into a plug-in could result in re-entrance if the plug-in yields // control to the system (rdar://5744899). prevent this by deferring further // loading while calling into the plug-in. if (m_loader) m_loader->setDefersLoading(true); NPError npErr = m_pluginFuncs->newstream(m_instance, (NPMIMEType)mimeTypeStr.data(), &m_stream, false, &m_transferMode); if (m_loader) m_loader->setDefersLoading(false); // If the stream was destroyed in the call to newstream we return if (m_reason != WebReasonNone) return; if (npErr != NPERR_NO_ERROR) { cancelAndDestroyStream(npErr); return; } m_streamState = StreamStarted; if (m_transferMode == NP_NORMAL) return; m_path = openTemporaryFile("WKP", m_tempFileHandle); // Something went wrong, cancel loading the stream if (!isHandleValid(m_tempFileHandle)) cancelAndDestroyStream(NPRES_NETWORK_ERR); }
// The cookie String passed into this method will only contian the name value pairs as well as other related cookie // attributes such as max-age and domain. Set-Cookie should never be part of this string. PassRefPtr<ParsedCookie> CookieParser::parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime) { RefPtr<ParsedCookie> res = ParsedCookie::create(curTime); if (!res) LOG_ERROR_AND_RETURN("Out of memory"); res->setProtocol(m_defaultCookieURL.protocol()); // Parse [NAME "="] VALUE unsigned tokenEnd = start; // Token end contains the position of the '=' or the end of a token unsigned pairEnd = start; // Pair end contains always the position of the ';' // Find the first ';' which is not double-quoted and the '=' (if they exist). bool foundEqual = false; while (pairEnd < end && cookie[pairEnd] != ';') { if (cookie[pairEnd] == '=') { if (tokenEnd == start) { tokenEnd = pairEnd; foundEqual = true; } } else if (cookie[pairEnd] == '"') { size_t secondQuotePosition = cookie.find('"', pairEnd + 1); if (secondQuotePosition != notFound && secondQuotePosition <= end) { pairEnd = secondQuotePosition + 1; continue; } } pairEnd++; } unsigned tokenStart = start; bool hasName = false; // This is a hack to avoid changing too much in this brutally brittle code. if (tokenEnd != start) { // There is a '=' so parse the NAME unsigned nameEnd = tokenEnd; // The tokenEnd is the position of the '=' so the nameEnd is one less nameEnd--; // Remove lightweight spaces. while (nameEnd && isLightweightSpace(cookie[nameEnd])) nameEnd--; while (tokenStart < nameEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; if (nameEnd + 1 <= tokenStart) LOG_ERROR_AND_RETURN("Empty name. Rejecting the cookie"); String name = cookie.substring(tokenStart, nameEnd + 1 - start); res->setName(name); hasName = true; } // Now parse the VALUE tokenStart = tokenEnd + 1; if (!hasName) --tokenStart; // Skip lightweight spaces in our token while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1])) tokenEnd--; String value; if (tokenEnd == tokenStart) { // Firefox accepts empty value so we will do the same value = String(); } else value = cookie.substring(tokenStart, tokenEnd - tokenStart); if (hasName) res->setValue(value); else if (foundEqual) return 0; else res->setName(value); // No NAME=VALUE, only NAME while (pairEnd < end) { // Switch to the next pair as pairEnd is on the ';' and fast-forward any lightweight spaces. pairEnd++; while (pairEnd < end && isLightweightSpace(cookie[pairEnd])) pairEnd++; tokenStart = pairEnd; tokenEnd = tokenStart; // initialize token end to catch first '=' while (pairEnd < end && cookie[pairEnd] != ';') { if (tokenEnd == tokenStart && cookie[pairEnd] == '=') tokenEnd = pairEnd; pairEnd++; } // FIXME : should we skip lightweight spaces here ? unsigned length = tokenEnd - tokenStart; unsigned tokenStartSvg = tokenStart; String parsedValue; if (tokenStart != tokenEnd) { // There is an equal sign so remove lightweight spaces in VALUE tokenStart = tokenEnd + 1; while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1])) tokenEnd--; parsedValue = cookie.substring(tokenStart, tokenEnd - tokenStart); } else { // If the parsedValue is empty, initialise it in case we need it parsedValue = String(); // Handle a token without value. length = pairEnd - tokenStart; } // Detect which "cookie-av" is parsed // Look at the first char then parse the whole for performance issue switch (cookie[tokenStartSvg]) { case 'P': case 'p' : { if (length >= 4 && ((cookie.find("ath", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) { // We need the path to be decoded to match those returned from URL::path(). // The path attribute may or may not include percent-encoded characters. Fortunately // if there are no percent-encoded characters, decoding the url is a no-op. res->setPath(decodeURLEscapeSequences(parsedValue)); // We have to disable the following check because sites like Facebook and // Gmail currently do not follow the spec. #if 0 // Check if path attribute is a prefix of the request URI. if (!m_defaultCookieURL.path().startsWith(res->path())) LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (path): it does not math the URL", cookie.ascii().data()); #endif } else LOG_ERROR("Invalid cookie attribute %s (path)", cookie.ascii().data()); break; } case 'D': case 'd' : { if (length >= 6 && ((cookie.find("omain", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) { if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"') parsedValue = parsedValue.substring(1, parsedValue.length() - 2); // Check if the domain contains an embedded dot. size_t dotPosition = parsedValue.find(".", 1); if (dotPosition == notFound || dotPosition == parsedValue.length()) LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it does not contain an embedded dot", cookie.ascii().data()); // If the domain does not start with a dot, add one for security checks and to distinguish it from host-only domains // For example: ab.c.com dose not domain match b.c.com; StringBuilder parsedValueBuilder; if (parsedValue[0] != '.') parsedValueBuilder.appendLiteral("."); parsedValueBuilder.append(parsedValue); String realDomain = parsedValueBuilder.toString(); StringBuilder defaultHostBuilder; defaultHostBuilder.appendLiteral("."); defaultHostBuilder.append(m_defaultCookieHost); String defaultHost = defaultHostBuilder.toString(); // Try to return an canonical ip address if the domain is an ip bool isIPAddress = false; // We only check if the current domain is an IP address when the default domain is an IP address // We know if the default domain is not an IP address and the current domain is, it won't suffix match // If it is an IP Address, we should treat it only if it matches the host exactly // We determine the canonical IP format before comparing because IPv6 could be represented in multiple formats if (m_defaultDomainIsIPAddress) { String realDomainCanonical = BlackBerry::Platform::getCanonicalIPFormat(realDomain); if (realDomainCanonical.isEmpty() || realDomainCanonical != defaultHost) LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): domain is IP but does not match host's IP", cookie.ascii().data()); realDomain = realDomainCanonical; isIPAddress = true; } else { // The request host should domain match the Domain attribute. // Domain string starts with a dot, so a.b.com should domain match .a.b.com. // add a "." at beginning of host name, because it can handle many cases such as // a.b.com matches b.com, a.b.com matches .B.com and a.b.com matches .A.b.Com // and so on. if (!defaultHost.endsWith(realDomain, false)) LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it does not domain match the host", cookie.ascii().data()); // We should check for an embedded dot in the portion of string in the host not in the domain // but to match firefox behaviour we do not. // Check whether the domain is a top level domain, if it is throw it out // http://publicsuffix.org/list/ if (BlackBerry::Platform::isTopLevelDomain(realDomain)) LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it did not pass the top level domain check", cookie.ascii().data()); } res->setDomain(realDomain, isIPAddress); } else LOG_ERROR("Invalid cookie attribute %s (domain)", cookie.ascii().data()); break; } case 'E' : case 'e' : { if (length >= 7 && ((cookie.find("xpires", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) res->setExpiry(parsedValue); else LOG_ERROR("Invalid cookie attribute %s (expires)", cookie.ascii().data()); break; } case 'M' : case 'm' : { if (length >= 7 && ((cookie.find("ax-age", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) res->setMaxAge(parsedValue); else LOG_ERROR("Invalid cookie attribute %s (max-age)", cookie.ascii().data()); break; } case 'C' : case 'c' : { if (length >= 7 && ((cookie.find("omment", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) // We do not have room for the comment part (and so do Mozilla) so just log the comment. LOG(Network, "Comment %s for ParsedCookie : %s\n", parsedValue.ascii().data(), cookie.ascii().data()); else LOG_ERROR("Invalid cookie attribute %s (comment)", cookie.ascii().data()); break; } case 'V' : case 'v' : { if (length >= 7 && ((cookie.find("ersion", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) { // Although the out-of-dated Cookie Spec(RFC2965, http://tools.ietf.org/html/rfc2965) defined // the value of version can only contain DIGIT, some random sites, e.g. https://devforums.apple.com // would use double quotation marks to quote the digit. So we need to get rid of them for compliance. if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"') parsedValue = parsedValue.substring(1, parsedValue.length() - 2); if (parsedValue.toInt() != 1) LOG_ERROR_AND_RETURN("ParsedCookie version %d not supported (only support version=1)", parsedValue.toInt()); } else LOG_ERROR("Invalid cookie attribute %s (version)", cookie.ascii().data()); break; } case 'S' : case 's' : { // Secure is a standalone token ("Secure;") if (length >= 6 && ((cookie.find("ecure", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) res->setSecureFlag(true); else LOG_ERROR("Invalid cookie attribute %s (secure)", cookie.ascii().data()); break; } case 'H': case 'h': { // HttpOnly is a standalone token ("HttpOnly;") if (length >= 8 && ((cookie.find("ttpOnly", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) res->setIsHttpOnly(true); else LOG_ERROR("Invalid cookie attribute %s (HttpOnly)", cookie.ascii().data()); break; } default : { // If length == 0, we should be at the end of the cookie (case : ";\r") so ignore it if (length) LOG_ERROR("Invalid token for cookie %s", cookie.ascii().data()); } } } // Check if the cookie is valid with respect to the size limit. if (!res->isUnderSizeLimit()) LOG_ERROR_AND_RETURN("ParsedCookie %s is above the 4kb in length : REJECTED", cookie.ascii().data()); // If some pair was not provided, during parsing then apply some default value // the rest has been done in the constructor. // If no domain was provided, set it to the host if (!res->domain()) res->setDomain(m_defaultCookieHost, m_defaultDomainIsIPAddress); // According to the Cookie Specificaiton (RFC6265, section 4.1.2.4 and 5.2.4, http://tools.ietf.org/html/rfc6265), // If no path was provided or the first character of the path value is not '/', set it to the host's path // // REFERENCE // 4.1.2.4. The Path Attribute // // The scope of each cookie is limited to a set of paths, controlled by // the Path attribute. If the server omits the Path attribute, the user // agent will use the "directory" of the request-uri's path component as // the default value. (See Section 5.1.4 for more details.) // ........... // 5.2.4. The Path Attribute // // If the attribute-name case-insensitively matches the string "Path", // the user agent MUST process the cookie-av as follows. // // If the attribute-value is empty or if the first character of the // attribute-value is not %x2F ("/"): // // Let cookie-path be the default-path. // // Otherwise: // // Let cookie-path be the attribute-value. // // Append an attribute to the cookie-attribute-list with an attribute- // name of Path and an attribute-value of cookie-path. if (!res->path() || !res->path().length() || !res->path().startsWith("/", false)) { String path = m_defaultCookieURL.string().substring(m_defaultCookieURL.pathStart(), m_defaultCookieURL.pathAfterLastSlash() - m_defaultCookieURL.pathStart() - 1); if (path.isEmpty()) path = "/"; // Since this is reading the raw url string, it could contain percent-encoded sequences. We // want it to be comparable to the return value of url.path(), which is not percent-encoded, // so we must remove the escape sequences. res->setPath(decodeURLEscapeSequences(path)); } return res; }
// In general, extracting the inner URL varies by scheme. It just so happens // that all the URL schemes we currently support that use inner URLs for their // security origin can be parsed using this algorithm. static KURL extractInnerURL(const KURL& url) { // FIXME: Update this callsite to use the innerURL member function when // we finish implementing it. return KURL(ParsedURLString, decodeURLEscapeSequences(url.path())); }
// parseDataUrl() is taken from the CURL http backend. static gboolean parseDataUrl(gpointer callback_data) { ResourceHandle* handle = static_cast<ResourceHandle*>(callback_data); ResourceHandleClient* client = handle->client(); ResourceHandleInternal* d = handle->getInternal(); if (d->m_cancelled) return false; d->m_idleHandler = 0; ASSERT(client); if (!client) return false; String url = handle->request().url().string(); ASSERT(url.startsWith("data:", false)); int index = url.find(','); if (index == -1) { client->cannotShowURL(handle); return false; } String mediaType = url.substring(5, index - 5); String data = url.substring(index + 1); bool isBase64 = mediaType.endsWith(";base64", false); if (isBase64) mediaType = mediaType.left(mediaType.length() - 7); if (mediaType.isEmpty()) mediaType = "text/plain;charset=US-ASCII"; String mimeType = extractMIMETypeFromMediaType(mediaType); String charset = extractCharsetFromMediaType(mediaType); ResourceResponse response; response.setMimeType(mimeType); if (isBase64) { data = decodeURLEscapeSequences(data); response.setTextEncodingName(charset); client->didReceiveResponse(handle, response); if (d->m_cancelled) return false; // Use the GLib Base64, since WebCore's decoder isn't // general-purpose and fails on Acid3 test 97 (whitespace). size_t outLength = 0; char* outData = 0; outData = reinterpret_cast<char*>(g_base64_decode(data.utf8().data(), &outLength)); if (outData && outLength > 0) client->didReceiveData(handle, outData, outLength, 0); g_free(outData); } else { // We have to convert to UTF-16 early due to limitations in KURL data = decodeURLEscapeSequences(data, TextEncoding(charset)); response.setTextEncodingName("UTF-16"); client->didReceiveResponse(handle, response); if (d->m_cancelled) return false; if (data.length() > 0) client->didReceiveData(handle, reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0); if (d->m_cancelled) return false; } client->didFinishLoading(handle); return false; }
// FIXME: This function does not deal properly with text encodings. static void parseDataUrl(ResourceHandle* handle) { String data = handle->request().url().string(); ASSERT(data.startsWith("data:", false)); String header; bool base64 = false; int index = data.find(','); if (index != -1) { header = data.substring(5, index - 5).lower(); data = data.substring(index + 1); if (header.endsWith(";base64")) { base64 = true; header = header.left(header.length() - 7); } } else data = String(); data = decodeURLEscapeSequences(data); size_t outLength = 0; char* outData = 0; if (base64 && !data.isEmpty()) { // Use the GLib Base64 if available, since WebCore's decoder isn't // general-purpose and fails on Acid3 test 97 (whitespace). #ifdef USE_GLIB_BASE64 outData = reinterpret_cast<char*>(g_base64_decode(data.utf8().data(), &outLength)); #else Vector<char> out; if (base64Decode(data.latin1().data(), data.length(), out)) data = String(out.data(), out.size()); else data = String(); #endif } if (header.isEmpty()) header = "text/plain;charset=US-ASCII"; ResourceHandleClient* client = handle->getInternal()->client(); ResourceResponse response; response.setMimeType(extractMIMETypeFromMediaType(header)); response.setTextEncodingName(extractCharsetFromMediaType(header)); if (outData) response.setExpectedContentLength(outLength); else response.setExpectedContentLength(data.length()); response.setHTTPStatusCode(200); client->didReceiveResponse(handle, response); if (outData) client->didReceiveData(handle, outData, outLength, 0); else client->didReceiveData(handle, data.latin1().data(), data.length(), 0); #ifdef USE_GLIB_BASE64 g_free(outData); #endif client->didFinishLoading(handle); }
static bool isMainInspectorPage(const WebInspectorProxy* webInspectorProxy, WKURLRequestRef requestRef) { // Use URL so we can compare just the paths. URL inspectorURL(URL(), webInspectorProxy->inspectorPageURL()); URL requestURL(URL(), toImpl(requestRef)->resourceRequest().url()); ASSERT(WebCore::SchemeRegistry::shouldTreatURLSchemeAsLocal(inspectorURL.protocol())); return WebCore::SchemeRegistry::shouldTreatURLSchemeAsLocal(requestURL.protocol()) && decodeURLEscapeSequences(requestURL.path()) == decodeURLEscapeSequences(inspectorURL.path()); }
String decodeURLEscapeSequences(const String& str) { return decodeURLEscapeSequences(str, UTF8Encoding()); }