xmlhttprequest.cpp [plain text]
#include "config.h"
#include "xmlhttprequest.h"
#include "Cache.h"
#include "DOMImplementation.h"
#include "Decoder.h"
#include "Event.h"
#include "EventListener.h"
#include "EventNames.h"
#include "ExceptionCode.h"
#include "FormData.h"
#include "Frame.h"
#include "HTMLDocument.h"
#include "LoaderFunctions.h"
#include "Page.h"
#include "PlatformString.h"
#include "RegularExpression.h"
#include "TextEncoding.h"
#include "TransferJob.h"
#include "kjs_binding.h"
#include <kjs/protect.h>
#include <wtf/Vector.h>
namespace WebCore {
using namespace EventNames;
typedef HashSet<XMLHttpRequest*> RequestsSet;
static HashMap<Document*, RequestsSet*>& requestsByDocument()
{
static HashMap<Document*, RequestsSet*> map;
return map;
}
static void addToRequestsByDocument(Document* doc, XMLHttpRequest* req)
{
ASSERT(doc);
ASSERT(req);
RequestsSet* requests = requestsByDocument().get(doc);
if (!requests) {
requests = new RequestsSet;
requestsByDocument().set(doc, requests);
}
ASSERT(!requests->contains(req));
requests->add(req);
}
static void removeFromRequestsByDocument(Document* doc, XMLHttpRequest* req)
{
ASSERT(doc);
ASSERT(req);
RequestsSet* requests = requestsByDocument().get(doc);
ASSERT(requests);
ASSERT(requests->contains(req));
requests->remove(req);
if (requests->isEmpty()) {
requestsByDocument().remove(doc);
delete requests;
}
}
static inline String getMIMEType(const String& contentTypeString)
{
String mimeType;
unsigned length = contentTypeString.length();
for (unsigned offset = 0; offset < length; offset++) {
UChar c = contentTypeString[offset];
if (c == ';')
break;
else if (DeprecatedChar(c).isSpace()) continue;
mimeType += String(&c, 1);
}
return mimeType;
}
static String getCharset(const String& contentTypeString)
{
int pos = 0;
int length = (int)contentTypeString.length();
while (pos < length) {
pos = contentTypeString.find("charset", pos, false);
if (pos <= 0)
return String();
if (contentTypeString[pos-1] > ' ' && contentTypeString[pos-1] != ';') {
pos += 7;
continue;
}
pos += 7;
while (pos != length && contentTypeString[pos] <= ' ')
++pos;
if (contentTypeString[pos++] != '=') continue;
while (pos != length && (contentTypeString[pos] <= ' ' || contentTypeString[pos] == '"' || contentTypeString[pos] == '\''))
++pos;
int endpos = pos;
while (pos != length && contentTypeString[endpos] > ' ' && contentTypeString[endpos] != '"' && contentTypeString[endpos] != '\'' && contentTypeString[endpos] != ';')
++endpos;
return contentTypeString.substring(pos, endpos-pos);
}
return String();
}
static bool canSetRequestHeader(const String& name)
{
static HashSet<StringImpl*, CaseInsensitiveHash > forbiddenHeaders;
if (forbiddenHeaders.isEmpty()) {
forbiddenHeaders.add(new StringImpl("accept-charset"));
forbiddenHeaders.add(new StringImpl("accept-encoding"));
forbiddenHeaders.add(new StringImpl("content-length"));
forbiddenHeaders.add(new StringImpl("expect"));
forbiddenHeaders.add(new StringImpl("date"));
forbiddenHeaders.add(new StringImpl("host"));
forbiddenHeaders.add(new StringImpl("keep-alive"));
forbiddenHeaders.add(new StringImpl("referer"));
forbiddenHeaders.add(new StringImpl("te"));
forbiddenHeaders.add(new StringImpl("trailer"));
forbiddenHeaders.add(new StringImpl("transfer-encoding"));
forbiddenHeaders.add(new StringImpl("upgrade"));
forbiddenHeaders.add(new StringImpl("via"));
}
return !forbiddenHeaders.contains(name.impl());
}
static bool isValidToken(const String& name)
{
unsigned length = name.length();
for (unsigned i = 0; i < length; i++) {
UniChar 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)
{
return !name.contains('\r') && !name.contains('\n');
}
XMLHttpRequestState XMLHttpRequest::getReadyState() const
{
return m_state;
}
String XMLHttpRequest::getResponseText() const
{
return m_response;
}
Document* XMLHttpRequest::getResponseXML() const
{
if (m_state != Completed)
return 0;
if (!m_createdDocument) {
if (responseIsXML()) {
m_responseXML = m_doc->implementation()->createDocument();
m_responseXML->open();
m_responseXML->write(m_response);
m_responseXML->finishParsing();
m_responseXML->close();
}
m_createdDocument = true;
}
return m_responseXML.get();
}
EventListener* XMLHttpRequest::onReadyStateChangeListener() const
{
return m_onReadyStateChangeListener.get();
}
void XMLHttpRequest::setOnReadyStateChangeListener(EventListener* eventListener)
{
m_onReadyStateChangeListener = eventListener;
}
EventListener* XMLHttpRequest::onLoadListener() const
{
return m_onLoadListener.get();
}
void XMLHttpRequest::setOnLoadListener(EventListener* eventListener)
{
m_onLoadListener = eventListener;
}
XMLHttpRequest::XMLHttpRequest(Document *d)
: m_doc(d)
, m_async(true)
, m_job(0)
, m_state(Uninitialized)
, m_createdDocument(false)
, m_aborted(false)
{
ASSERT(m_doc);
addToRequestsByDocument(m_doc, this);
}
XMLHttpRequest::~XMLHttpRequest()
{
if (m_doc)
removeFromRequestsByDocument(m_doc, this);
}
void XMLHttpRequest::changeState(XMLHttpRequestState newState)
{
if (m_state != newState) {
m_state = newState;
callReadyStateChangeListener();
}
}
void XMLHttpRequest::callReadyStateChangeListener()
{
if (m_doc && m_doc->frame() && m_onReadyStateChangeListener) {
ExceptionCode ec;
RefPtr<Event> ev = m_doc->createEvent("HTMLEvents", ec);
ev->initEvent(readystatechangeEvent, true, true);
m_onReadyStateChangeListener->handleEvent(ev.get(), true);
}
if (m_doc && m_doc->frame() && m_state == Completed && m_onLoadListener) {
ExceptionCode ec;
RefPtr<Event> ev = m_doc->createEvent("HTMLEvents", ec);
ev->initEvent(loadEvent, true, true);
m_onLoadListener->handleEvent(ev.get(), true);
}
}
bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& url) const
{
KURL documentURL(m_doc->URL());
if (documentURL.protocol().lower() == "file")
return true;
if (documentURL.protocol().lower() == url.protocol().lower()
&& documentURL.host().lower() == url.host().lower()
&& documentURL.port() == url.port())
return true;
return false;
}
void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
{
abort();
m_aborted = false;
m_requestHeaders = DeprecatedString();
m_responseHeaders = String();
m_response = DeprecatedString();
m_createdDocument = false;
m_responseXML = 0;
changeState(Uninitialized);
if (m_aborted)
return;
if (!urlMatchesDocumentDomain(url))
return;
if (!isValidToken(method)) {
ec = SYNTAX_ERR;
return;
}
m_url = url;
if (!user.isNull())
m_url.setUser(user.deprecatedString());
if (!password.isNull())
m_url.setPass(password.deprecatedString());
String methodUpper(method.upper());
if (methodUpper == "CONNECT" || methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
|| methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
|| methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT"
|| methodUpper == "TRACE" || methodUpper == "UNLOCK")
m_method = methodUpper.deprecatedString();
else
m_method = method.deprecatedString();
m_async = async;
changeState(Loading);
}
void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
{
if (!m_doc)
return;
if (m_state != Loading)
return;
if (m_job)
return;
m_aborted = false;
if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && (m_url.protocol().lower() == "http" || m_url.protocol().lower() == "https")) {
String contentType = getRequestHeader("Content-Type");
String charset;
if (contentType.isEmpty())
setRequestHeader("Content-Type", "application/xml", ec);
else
charset = getCharset(contentType);
if (charset.isEmpty())
charset = "UTF-8";
TextEncoding m_encoding = TextEncoding(charset.deprecatedString().latin1());
if (!m_encoding.isValid()) m_encoding = TextEncoding(UTF8Encoding);
m_job = new TransferJob(m_async ? this : 0, m_method, m_url, m_encoding.fromUnicode(body.deprecatedString()));
} else {
if (m_method == "HEAD")
m_method = "GET";
m_job = new TransferJob(m_async ? this : 0, m_method, m_url);
}
if (m_requestHeaders.length())
m_job->addMetaData("customHTTPHeader", m_requestHeaders);
if (!m_async) {
Vector<char> data;
KURL finalURL;
DeprecatedString headers;
{
KJS::JSLock::DropAllLocks dropLocks;
data = ServeSynchronousRequest(cache()->loader(), m_doc->docLoader(), m_job, finalURL, headers);
}
m_job = 0;
processSyncLoadResults(data, finalURL, headers);
return;
}
ref();
{
KJS::JSLock lock;
gcProtectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
}
m_job->start(m_doc->docLoader());
}
void XMLHttpRequest::abort()
{
bool hadJob = m_job;
if (hadJob) {
m_job->kill();
m_job = 0;
}
m_decoder = 0;
m_aborted = true;
if (hadJob) {
{
KJS::JSLock lock;
gcUnprotectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
}
deref();
}
}
void XMLHttpRequest::overrideMIMEType(const String& override)
{
m_mimeTypeOverride = override;
}
void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec)
{
if (!isValidToken(name) || !isValidHeaderValue(value)) {
ec = SYNTAX_ERR;
return;
}
if (!canSetRequestHeader(name)) {
if (m_doc && m_doc->frame() && m_doc->frame()->page())
m_doc->frame()->page()->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, "Refused to set unsafe header " + name, 1, String());
return;
}
if (m_requestHeaders.length() > 0)
m_requestHeaders += "\r\n";
m_requestHeaders += name.deprecatedString();
m_requestHeaders += ": ";
m_requestHeaders += value.deprecatedString();
}
DeprecatedString XMLHttpRequest::getRequestHeader(const DeprecatedString& name) const
{
return getSpecificHeader(m_requestHeaders, name);
}
String XMLHttpRequest::getAllResponseHeaders() const
{
if (m_responseHeaders.isEmpty())
return String();
int endOfLine = m_responseHeaders.find("\n");
if (endOfLine == -1)
return String();
return m_responseHeaders.substring(endOfLine + 1) + "\n";
}
String XMLHttpRequest::getResponseHeader(const String& name) const
{
return getSpecificHeader(m_responseHeaders.deprecatedString(), name.deprecatedString());
}
DeprecatedString XMLHttpRequest::getSpecificHeader(const DeprecatedString& headers, const DeprecatedString& name)
{
if (headers.isEmpty())
return DeprecatedString();
RegularExpression headerLinePattern(name + ":", false);
int matchLength;
int headerLinePos = headerLinePattern.match(headers, 0, &matchLength);
while (headerLinePos != -1) {
if (headerLinePos == 0 || headers[headerLinePos-1] == '\n')
break;
headerLinePos = headerLinePattern.match(headers, headerLinePos + 1, &matchLength);
}
if (headerLinePos == -1)
return DeprecatedString();
int endOfLine = headers.find("\n", headerLinePos + matchLength);
return headers.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace();
}
bool XMLHttpRequest::responseIsXML() const
{
String mimeType = getMIMEType(m_mimeTypeOverride);
if (mimeType.isEmpty())
mimeType = getMIMEType(getResponseHeader("Content-Type"));
if (mimeType.isEmpty())
mimeType = "text/xml";
return DOMImplementation::isXMLMIMEType(mimeType);
}
int XMLHttpRequest::getStatus() const
{
if (m_responseHeaders.isEmpty())
return -1;
int endOfLine = m_responseHeaders.find("\n");
String firstLine = endOfLine == -1 ? m_responseHeaders : m_responseHeaders.substring(0, endOfLine);
int codeStart = firstLine.find(" ");
int codeEnd = firstLine.find(" ", codeStart + 1);
if (codeStart == -1 || codeEnd == -1)
return -1;
String number = firstLine.substring(codeStart + 1, codeEnd - (codeStart + 1));
bool ok = false;
int code = number.toInt(&ok);
if (!ok)
return -1;
return code;
}
String XMLHttpRequest::getStatusText() const
{
if (m_responseHeaders.isEmpty())
return String();
int endOfLine = m_responseHeaders.find("\n");
String firstLine = endOfLine == -1 ? m_responseHeaders : m_responseHeaders.substring(0, endOfLine);
int codeStart = firstLine.find(" ");
int codeEnd = firstLine.find(" ", codeStart + 1);
if (codeStart == -1 || codeEnd == -1)
return String();
return firstLine.substring(codeEnd + 1, endOfLine - (codeEnd + 1)).deprecatedString().stripWhiteSpace();
}
void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const KURL& finalURL, const DeprecatedString& headers)
{
if (!urlMatchesDocumentDomain(finalURL)) {
abort();
return;
}
m_responseHeaders = headers;
changeState(Loaded);
if (m_aborted)
return;
const char* bytes = static_cast<const char*>(data.data());
int len = static_cast<int>(data.size());
receivedData(0, bytes, len);
if (m_aborted)
return;
receivedAllData(0);
}
void XMLHttpRequest::receivedAllData(TransferJob*)
{
if (m_responseHeaders.isEmpty() && m_job)
m_responseHeaders = m_job->queryMetaData("HTTP-Headers");
if (m_state < Loaded)
changeState(Loaded);
if (m_decoder)
m_response += m_decoder->flush();
bool hadJob = m_job;
m_job = 0;
changeState(Completed);
m_decoder = 0;
if (hadJob) {
{
KJS::JSLock lock;
gcUnprotectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
}
deref();
}
}
void XMLHttpRequest::receivedRedirect(TransferJob*, const KURL& m_url)
{
if (!urlMatchesDocumentDomain(m_url))
abort();
}
void XMLHttpRequest::receivedData(TransferJob*, const char *data, int len)
{
if (m_responseHeaders.isEmpty() && m_job)
m_responseHeaders = m_job->queryMetaData("HTTP-Headers");
if (m_state < Loaded)
changeState(Loaded);
if (!m_decoder) {
m_encoding = getCharset(m_mimeTypeOverride);
if (m_encoding.isEmpty())
m_encoding = getCharset(getResponseHeader("Content-Type"));
if (m_encoding.isEmpty() && m_job)
m_encoding = m_job->queryMetaData("charset");
m_decoder = new Decoder;
if (!m_encoding.isEmpty())
m_decoder->setEncodingName(m_encoding.deprecatedString().latin1(), Decoder::EncodingFromHTTPHeader);
else
m_decoder->setEncodingName("UTF-8", responseIsXML() ? Decoder::DefaultEncoding : Decoder::EncodingFromHTTPHeader);
}
if (len == 0)
return;
if (len == -1)
len = strlen(data);
DeprecatedString decoded = m_decoder->decode(data, len);
m_response += decoded;
if (!m_aborted) {
if (m_state != Interactive)
changeState(Interactive);
else
callReadyStateChangeListener();
}
}
void XMLHttpRequest::cancelRequests(Document* m_doc)
{
RequestsSet* requests = requestsByDocument().get(m_doc);
if (!requests)
return;
RequestsSet copy = *requests;
RequestsSet::const_iterator end = copy.end();
for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it)
(*it)->abort();
}
void XMLHttpRequest::detachRequests(Document* m_doc)
{
RequestsSet* requests = requestsByDocument().get(m_doc);
if (!requests)
return;
requestsByDocument().remove(m_doc);
RequestsSet::const_iterator end = requests->end();
for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) {
(*it)->m_doc = 0;
(*it)->abort();
}
delete requests;
}
}