📄 xmlhttprequest.cpp
字号:
/* * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. * Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org> * Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org> * Copyright (C) 2008 David Levin <levin@chromium.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */#include "config.h"#include "XMLHttpRequest.h"#include "CString.h"#include "DOMImplementation.h"#include "Document.h"#include "Event.h"#include "EventException.h"#include "EventListener.h"#include "EventNames.h"#include "File.h"#include "HTTPParsers.h"#include "KURL.h"#include "KURLHash.h"#include "ResourceError.h"#include "ResourceRequest.h"#include "ScriptExecutionContext.h"#include "SecurityOrigin.h"#include "Settings.h"#include "SystemTime.h"#include "TextResourceDecoder.h"#include "ThreadableLoader.h"#include "XMLHttpRequestException.h"#include "XMLHttpRequestProgressEvent.h"#include "XMLHttpRequestUpload.h"#include "markup.h"#include <wtf/CurrentTime.h>#include <wtf/Noncopyable.h>#include <wtf/StdLibExtras.h>#include <wtf/Threading.h>#if USE(JSC)#include "JSDOMWindow.h"#endifnamespace WebCore {typedef HashSet<String, CaseFoldingHash> HeadersSet;struct XMLHttpRequestStaticData { XMLHttpRequestStaticData(); String m_proxyHeaderPrefix; String m_secHeaderPrefix; HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders; HashSet<String, CaseFoldingHash> m_allowedCrossSiteResponseHeaders;};XMLHttpRequestStaticData::XMLHttpRequestStaticData() : m_proxyHeaderPrefix("proxy-") , m_secHeaderPrefix("sec-"){ m_forbiddenRequestHeaders.add("accept-charset"); m_forbiddenRequestHeaders.add("accept-encoding"); m_forbiddenRequestHeaders.add("connection"); m_forbiddenRequestHeaders.add("content-length"); m_forbiddenRequestHeaders.add("content-transfer-encoding"); m_forbiddenRequestHeaders.add("date"); m_forbiddenRequestHeaders.add("expect"); m_forbiddenRequestHeaders.add("host"); m_forbiddenRequestHeaders.add("keep-alive"); m_forbiddenRequestHeaders.add("referer"); m_forbiddenRequestHeaders.add("te"); m_forbiddenRequestHeaders.add("trailer"); m_forbiddenRequestHeaders.add("transfer-encoding"); m_forbiddenRequestHeaders.add("upgrade"); m_forbiddenRequestHeaders.add("via"); m_allowedCrossSiteResponseHeaders.add("cache-control"); m_allowedCrossSiteResponseHeaders.add("content-language"); m_allowedCrossSiteResponseHeaders.add("content-type"); m_allowedCrossSiteResponseHeaders.add("expires"); m_allowedCrossSiteResponseHeaders.add("last-modified"); m_allowedCrossSiteResponseHeaders.add("pragma");}class PreflightResultCacheItem : Noncopyable {public: PreflightResultCacheItem(bool credentials) : m_absoluteExpiryTime(0) , m_credentials(credentials) { } bool parse(const ResourceResponse&); bool allowsCrossSiteMethod(const String&) const; bool allowsCrossSiteHeaders(const HTTPHeaderMap&) const; bool allowsRequest(bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) const;private: template<class HashType> static void addToAccessControlAllowList(const String& string, unsigned start, unsigned end, HashSet<String, HashType>&); template<class HashType> static bool parseAccessControlAllowList(const String& string, HashSet<String, HashType>&); static bool parseAccessControlMaxAge(const String& string, unsigned& expiryDelta); // FIXME: A better solution to holding onto the absolute expiration time might be // to start a timer for the expiration delta, that removes this from the cache when // it fires. double m_absoluteExpiryTime; bool m_credentials; HashSet<String> m_methods; HeadersSet m_headers;};class PreflightResultCache : Noncopyable {public: static PreflightResultCache& shared(); void appendEntry(const String& origin, const KURL&, PreflightResultCacheItem*); bool canSkipPreflight(const String& origin, const KURL&, bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders);private: PreflightResultCache() { } typedef HashMap<std::pair<String, KURL>, PreflightResultCacheItem*> PreflightResultHashMap; PreflightResultHashMap m_preflightHashMap; Mutex m_mutex;};static bool isOnAccessControlSimpleRequestHeaderWhitelist(const String& name){ return equalIgnoringCase(name, "accept") || equalIgnoringCase(name, "accept-language") || equalIgnoringCase(name, "content-type");}// Determines if a string is a valid token, as defined by// "token" in section 2.2 of RFC 2616.static bool isValidToken(const String& name){ unsigned length = name.length(); for (unsigned i = 0; i < length; i++) { UChar c = name[i]; if (c >= 127 || c <= 32) return false; if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' || c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || c == '{' || c == '}') return false; } return true;} static bool isValidHeaderValue(const String& name){ // FIXME: This should really match name against // field-value in section 4.2 of RFC 2616. return !name.contains('\r') && !name.contains('\n');}static bool isSetCookieHeader(const AtomicString& name){ return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");}template<class HashType>void PreflightResultCacheItem::addToAccessControlAllowList(const String& string, unsigned start, unsigned end, HashSet<String, HashType>& set){ StringImpl* stringImpl = string.impl(); if (!stringImpl) return; // Skip white space from start. while (start <= end && isSpaceOrNewline((*stringImpl)[start])) start++; // only white space if (start > end) return; // Skip white space from end. while (end && isSpaceOrNewline((*stringImpl)[end])) end--; // substringCopy() is called on the strings because the cache is accessed on multiple threads. set.add(string.substringCopy(start, end - start + 1));}template<class HashType>bool PreflightResultCacheItem::parseAccessControlAllowList(const String& string, HashSet<String, HashType>& set){ int start = 0; int end; while ((end = string.find(',', start)) != -1) { if (start == end) return false; addToAccessControlAllowList(string, start, end - 1, set); start = end + 1; } if (start != static_cast<int>(string.length())) addToAccessControlAllowList(string, start, string.length() - 1, set); return true;}bool PreflightResultCacheItem::parseAccessControlMaxAge(const String& string, unsigned& expiryDelta){ // FIXME: this will not do the correct thing for a number starting with a '+' bool ok = false; expiryDelta = string.toUIntStrict(&ok); return ok;}bool PreflightResultCacheItem::parse(const ResourceResponse& response){ m_methods.clear(); if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Methods"), m_methods)) return false; m_headers.clear(); if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Headers"), m_headers)) return false; unsigned expiryDelta = 0; if (!parseAccessControlMaxAge(response.httpHeaderField("Access-Control-Max-Age"), expiryDelta)) expiryDelta = 5; m_absoluteExpiryTime = currentTime() + expiryDelta; return true;}bool PreflightResultCacheItem::allowsCrossSiteMethod(const String& method) const{ return m_methods.contains(method) || method == "GET" || method == "POST";}bool PreflightResultCacheItem::allowsCrossSiteHeaders(const HTTPHeaderMap& requestHeaders) const{ HTTPHeaderMap::const_iterator end = requestHeaders.end(); for (HTTPHeaderMap::const_iterator it = requestHeaders.begin(); it != end; ++it) { if (!m_headers.contains(it->first) && !isOnAccessControlSimpleRequestHeaderWhitelist(it->first)) return false; } return true;}bool PreflightResultCacheItem::allowsRequest(bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) const{ if (m_absoluteExpiryTime < currentTime()) return false; if (includeCredentials && !m_credentials) return false; if (!allowsCrossSiteMethod(method)) return false; if (!allowsCrossSiteHeaders(requestHeaders)) return false; return true;}PreflightResultCache& PreflightResultCache::shared(){ AtomicallyInitializedStatic(PreflightResultCache&, cache = *new PreflightResultCache); return cache;}void PreflightResultCache::appendEntry(const String& origin, const KURL& url, PreflightResultCacheItem* preflightResult){ MutexLocker lock(m_mutex); // Note that the entry may already be present in the HashMap if another thread is accessing the same location. m_preflightHashMap.set(std::make_pair(origin.copy(), url.copy()), preflightResult);}bool PreflightResultCache::canSkipPreflight(const String& origin, const KURL& url, bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders){ MutexLocker lock(m_mutex); PreflightResultHashMap::iterator cacheIt = m_preflightHashMap.find(std::make_pair(origin, url)); if (cacheIt == m_preflightHashMap.end()) return false; if (cacheIt->second->allowsRequest(includeCredentials, method, requestHeaders)) return true; delete cacheIt->second; m_preflightHashMap.remove(cacheIt); return false;}static const XMLHttpRequestStaticData* staticData = 0;static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData(){ staticData = new XMLHttpRequestStaticData; return staticData;}static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData(){ // Uses dummy to avoid warnings about an unused variable. AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData()); return dummy;}XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext* context) : ActiveDOMObject(context, this) , m_async(true) , m_includeCredentials(false) , m_state(UNSENT) , m_responseText("") , m_createdDocument(false) , m_error(false) , m_uploadComplete(false) , m_sameOriginRequest(true) , m_inPreflight(false) , m_receivedLength(0) , m_lastSendLineNumber(0) , m_exceptionCode(0){ initializeXMLHttpRequestStaticData();}XMLHttpRequest::~XMLHttpRequest(){ if (m_upload) m_upload->disconnectXMLHttpRequest();}Document* XMLHttpRequest::document() const{ ASSERT(scriptExecutionContext()->isDocument()); return static_cast<Document*>(scriptExecutionContext());}#if ENABLE(DASHBOARD_SUPPORT)bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const{ if (scriptExecutionContext()->isWorkerContext()) return false; Settings* settings = document()->settings(); return settings && settings->usesDashboardBackwardCompatibilityMode();}#endifXMLHttpRequest::State XMLHttpRequest::readyState() const{ return m_state;}const ScriptString& XMLHttpRequest::responseText() const{ return m_responseText;}Document* XMLHttpRequest::responseXML() const{ if (m_state != DONE) return 0; if (!m_createdDocument) { if ((m_response.isHTTP() && !responseIsXML()) || scriptExecutionContext()->isWorkerContext()) { // The W3C spec requires this. m_responseXML = 0; } else { m_responseXML = document()->implementation()->createDocument(0); m_responseXML->open(); m_responseXML->setURL(m_url); // FIXME: set Last-Modified and cookies (currently, those are only available for HTMLDocuments). m_responseXML->write(String(m_responseText)); m_responseXML->finishParsing(); m_responseXML->close(); if (!m_responseXML->wellFormed()) m_responseXML = 0; } m_createdDocument = true; } return m_responseXML.get();}XMLHttpRequestUpload* XMLHttpRequest::upload(){ if (!m_upload) m_upload = XMLHttpRequestUpload::create(this); return m_upload.get();}void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool){ EventListenersMap::iterator iter = m_eventListeners.find(eventType); if (iter == m_eventListeners.end()) { ListenerVector listeners; listeners.append(eventListener); m_eventListeners.add(eventType, listeners); } else { ListenerVector& listeners = iter->second; for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) if (*listenerIter == eventListener) return; listeners.append(eventListener); m_eventListeners.add(eventType, listeners); }}void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool){ EventListenersMap::iterator iter = m_eventListeners.find(eventType); if (iter == m_eventListeners.end()) return; ListenerVector& listeners = iter->second; for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) if (*listenerIter == eventListener) { listeners.remove(listenerIter - listeners.begin()); return; }}bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec){ // FIXME: check for other error conditions enumerated in the spec. if (!evt || evt->type().isEmpty()) { ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR; return true; } ListenerVector listenersCopy = m_eventListeners.get(evt->type()); for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) { evt->setTarget(this); evt->setCurrentTarget(this); listenerIter->get()->handleEvent(evt.get(), false); } return !evt->defaultPrevented();}void XMLHttpRequest::changeState(State newState){ if (m_state != newState) { m_state = newState; callReadyStateChangeListener(); }}void XMLHttpRequest::callReadyStateChangeListener(){ if (!scriptExecutionContext()) return;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -