1
// -*- c-basic-offset: 2 -*-
3
* This file is part of the KDE libraries
4
* Copyright (C) 2003 Apple Computer, Inc.
6
* This library is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU Lesser General Public
8
* License as published by the Free Software Foundation; either
9
* version 2 of the License, or (at your option) any later version.
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* Lesser General Public License for more details.
16
* You should have received a copy of the GNU Lesser General Public
17
* License along with this library; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
#include "xmlhttprequest.h"
22
#include "xmlhttprequest.lut.h"
23
#include "kjs_window.h"
24
#include "kjs_events.h"
26
#include "dom/dom_doc.h"
27
#include "dom/dom_exception.h"
28
#include "dom/dom_string.h"
29
#include "misc/loader.h"
30
#include "html/html_documentimpl.h"
31
#include "xml/dom2_eventsimpl.h"
33
#include "khtml_part.h"
34
#include "khtmlview.h"
36
#include <kio/scheduler.h>
42
#include "KWQLoader.h"
44
#include <kio/netaccess.h>
48
#define BANNED_HTTP_HEADERS "authorization,proxy-authorization,"\
49
"content-length,host,connect,copy,move,"\
50
"delete,head,trace,put,propfind,proppatch,"\
51
"mkcol,lock,unlock,options,via,"\
52
"accept-charset,accept-encoding,expect,date,"\
53
"keep-alive,te,trailer,"\
54
"transfer-encoding,upgrade"
60
////////////////////// XMLHttpRequest Object ////////////////////////
62
/* Source for XMLHttpRequestProtoTable.
63
@begin XMLHttpRequestProtoTable 7
64
abort XMLHttpRequest::Abort DontDelete|Function 0
65
getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders DontDelete|Function 0
66
getResponseHeader XMLHttpRequest::GetResponseHeader DontDelete|Function 1
67
open XMLHttpRequest::Open DontDelete|Function 5
68
overrideMimeType XMLHttpRequest::OverrideMIMEType DontDelete|Function 1
69
send XMLHttpRequest::Send DontDelete|Function 1
70
setRequestHeader XMLHttpRequest::SetRequestHeader DontDelete|Function 2
73
KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto)
74
IMPLEMENT_PROTOFUNC_DOM(XMLHttpRequestProtoFunc)
75
KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest", XMLHttpRequestProto,XMLHttpRequestProtoFunc)
78
XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
84
void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
86
jsObject->slotData(job, data, size);
89
void XMLHttpRequestQObject::slotData( KIO::Job* job, const QByteArray &data )
91
jsObject->slotData(job, data);
95
void XMLHttpRequestQObject::slotFinished( KIO::Job* job )
97
jsObject->slotFinished(job);
100
void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KURL& url)
102
jsObject->slotRedirection( job, url );
105
XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *, const DOM::Document &d)
106
: ObjectImp(), doc(d)
110
bool XMLHttpRequestConstructorImp::implementsConstruct() const
115
Object XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
117
return Object(new XMLHttpRequest(exec, doc));
120
const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
123
/* Source for XMLHttpRequestTable.
124
@begin XMLHttpRequestTable 7
125
readyState XMLHttpRequest::ReadyState DontDelete|ReadOnly
126
responseText XMLHttpRequest::ResponseText DontDelete|ReadOnly
127
responseXML XMLHttpRequest::ResponseXML DontDelete|ReadOnly
128
status XMLHttpRequest::Status DontDelete|ReadOnly
129
statusText XMLHttpRequest::StatusText DontDelete|ReadOnly
130
onreadystatechange XMLHttpRequest::Onreadystatechange DontDelete
131
onload XMLHttpRequest::Onload DontDelete
135
Value XMLHttpRequest::tryGet(ExecState *exec, const Identifier &propertyName) const
137
return DOMObjectLookupGetValue<XMLHttpRequest,DOMObject>(exec, propertyName, &XMLHttpRequestTable, this);
140
Value XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
144
return Number(state);
146
return getString(DOM::DOMString(response));
148
if (state != Completed) {
151
if (!createdDocument) {
152
QString mimeType = "text/xml";
154
if (!m_mimeTypeOverride.isEmpty()) {
155
mimeType = m_mimeTypeOverride;
157
Value header = getResponseHeader("Content-Type");
158
if (header.type() != UndefinedType) {
159
mimeType = QStringList::split(";", header.toString(exec).qstring())[0].stripWhiteSpace();
163
if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
164
responseXML = DOM::Document(doc->implementation()->createDocument());
166
DOM::DocumentImpl *docImpl = static_cast<DOM::DocumentImpl *>(responseXML.handle());
169
docImpl->write(response);
170
docImpl->finishParsing();
177
createdDocument = true;
184
return getDOMNode(exec,responseXML);
188
return getStatusText();
189
case Onreadystatechange:
190
if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObjImp()) {
191
return onReadyStateChangeListener->listenerObj();
196
if (onLoadListener && onLoadListener->listenerObjImp()) {
197
return onLoadListener->listenerObj();
202
kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
207
void XMLHttpRequest::tryPut(ExecState *exec, const Identifier &propertyName, const Value& value, int attr)
209
DOMObjectLookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
212
void XMLHttpRequest::putValueProperty(ExecState *exec, int token, const Value& value, int /*attr*/)
214
JSEventListener* newListener;
216
case Onreadystatechange:
217
newListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
218
if (newListener != onReadyStateChangeListener) {
219
if (onReadyStateChangeListener) onReadyStateChangeListener->deref();
220
onReadyStateChangeListener = newListener;
221
if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
225
newListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
226
if (newListener != onLoadListener) {
227
if (onLoadListener) onLoadListener->deref();
228
onLoadListener = newListener;
229
if (onLoadListener) onLoadListener->ref();
233
kdWarning() << "XMLHttpRequest::putValue unhandled token " << token << endl;
237
XMLHttpRequest::XMLHttpRequest(ExecState *exec, const DOM::Document &d)
238
: DOMObject(XMLHttpRequestProto::self(exec)),
239
qObject(new XMLHttpRequestQObject(this)),
240
doc(static_cast<DOM::DocumentImpl*>(d.handle())),
242
contentType(QString::null),
244
state(Uninitialized),
245
onReadyStateChangeListener(0),
248
createdDocument(false),
253
XMLHttpRequest::~XMLHttpRequest()
255
if (onReadyStateChangeListener)
256
onReadyStateChangeListener->deref();
258
onLoadListener->deref();
265
void XMLHttpRequest::changeState(XMLHttpRequestState newState)
267
if (state != newState) {
272
if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) {
273
DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
274
ev.initEvent("readystatechange", true, true);
275
onReadyStateChangeListener->handleEvent(ev);
278
if (state == Completed && onLoadListener != 0 && doc->view() && doc->view()->part()) {
279
DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
280
ev.initEvent("load", true, true);
281
onLoadListener->handleEvent(ev);
288
bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
290
// No need to do work if _url is not valid...
294
KURL documentURL(doc->URL());
296
// a local file can load anything
297
if (documentURL.protocol().lower() == "file") {
301
// but a remote document can only load from the same port on the server
302
if (documentURL.protocol().lower() == _url.protocol().lower() &&
303
documentURL.host().lower() == _url.host().lower() &&
304
documentURL.port() == _url.port()) {
311
void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
316
// clear stuff from possible previous load
317
requestHeaders.clear();
318
responseHeaders = QString();
319
response = QString();
320
createdDocument = false;
321
responseXML = DOM::Document();
323
changeState(Uninitialized);
329
if (!urlMatchesDocumentDomain(_url)) {
334
method = _method.lower();
338
changeState(Loading);
341
void XMLHttpRequest::send(const QString& _body)
345
if (method == "post") {
346
QString protocol = url.protocol().lower();
348
// Abondon the request when the protocol is other than "http",
349
// instead of blindly changing it to a "get" request.
350
if (!protocol.startsWith("http") && !protocol.startsWith("webdav"))
356
// FIXME: determine post encoding correctly by looking in headers
359
QCString str = _body.utf8();
360
buf.duplicate(str.data(), str.size() - 1);
362
job = KIO::http_post( url, buf, false );
363
if(contentType.isNull())
364
job->addMetaData( "content-type", "Content-type: text/plain" );
366
job->addMetaData( "content-type", contentType );
369
job = KIO::get( url, false, false );
372
if (!requestHeaders.isEmpty()) {
374
QMap<QString, QString>::ConstIterator begin = requestHeaders.begin();
375
QMap<QString, QString>::ConstIterator end = requestHeaders.end();
376
for (QMap<QString, QString>::ConstIterator i = begin; i != end; ++i) {
377
QString key = i.key();
378
QString value = i.data();
379
if (key == "accept") {
380
// The HTTP KIO slave supports an override this way
381
job->addMetaData("accept", value);
385
rh += key + ": " + value;
389
job->addMetaData("customHTTPHeader", rh);
392
job->addMetaData("PropagateHttpHeader", "true");
394
// Set the default referrer if one is not already supplied
395
// through setRequestHeader. NOTE: the user can still disable
396
// this feature at the protocol level (kio_http).
397
// ### does find() ever succeed? the headers are stored in lower case!
398
if (requestHeaders.find("Referer") == requestHeaders.end()) {
399
KURL documentURL(doc->URL());
400
documentURL.setPass(QString::null);
401
documentURL.setUser(QString::null);
402
job->addMetaData("referrer", documentURL.url());
403
// kdDebug() << "Adding referrer: " << documentURL << endl;
412
data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
414
QMap<QString, QString> metaData;
415
if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
416
headers = metaData[ "HTTP-Headers" ];
420
processSyncLoadResults(data, finalURL, headers);
424
qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
425
SLOT( slotFinished( KIO::Job* ) ) );
427
qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
428
SLOT( slotData( KIO::Job*, const char*, int ) ) );
430
qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
431
SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
433
qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
434
SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
437
KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
439
KIO::Scheduler::scheduleJob( job );
443
void XMLHttpRequest::abort()
454
void XMLHttpRequest::overrideMIMEType(const QString& override)
456
m_mimeTypeOverride = override;
459
void XMLHttpRequest::setRequestHeader(const QString& _name, const QString &value)
461
QString name = _name.lower().stripWhiteSpace();
463
// Content-type needs to be set seperately from the other headers
464
if(name == "content-type") {
465
contentType = "Content-type: " + value;
469
// Sanitize the referrer header to protect against spoofing...
470
if(name == "referer") {
471
KURL referrerURL(value);
472
if (urlMatchesDocumentDomain(referrerURL))
473
requestHeaders[name] = referrerURL.url();
477
// Sanitize the request headers below and handle them as if they are
478
// calls to open. Otherwise, we will end up ignoring them all together!
479
// TODO: Do something about "put" which kio_http sort of supports and
480
// the webDAV headers such as PROPFIND etc...
481
if (name == "get" || name == "post") {
482
KURL reqURL (doc->URL(), value.stripWhiteSpace());
483
open(name, reqURL, async);
487
// Reject all banned headers. See BANNED_HTTP_HEADERS above.
488
// kdDebug() << "Banned HTTP Headers: " << BANNED_HTTP_HEADERS << endl;
489
QStringList bannedHeaders = QStringList::split(',',
490
QString::fromLatin1(BANNED_HTTP_HEADERS));
492
if (bannedHeaders.contains(name))
495
requestHeaders[name] = value.stripWhiteSpace();
498
Value XMLHttpRequest::getAllResponseHeaders() const
500
if (responseHeaders.isEmpty()) {
504
int endOfLine = responseHeaders.find("\n");
506
if (endOfLine == -1) {
510
return String(responseHeaders.mid(endOfLine + 1) + "\n");
513
Value XMLHttpRequest::getResponseHeader(const QString& name) const
515
if (responseHeaders.isEmpty()) {
519
QRegExp headerLinePattern(name + ":", false);
522
int headerLinePos = headerLinePattern.search(responseHeaders, 0);
523
matchLength = headerLinePattern.matchedLength();
524
while (headerLinePos != -1) {
525
if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
529
headerLinePos = headerLinePattern.search(responseHeaders, headerLinePos + 1);
530
matchLength = headerLinePattern.matchedLength();
534
if (headerLinePos == -1) {
538
int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
540
return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
543
static Value httpStatus(const QString& response, bool textStatus = false)
545
if (response.isEmpty()) {
549
int endOfLine = response.find("\n");
550
QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine);
551
int codeStart = firstLine.find(" ");
552
int codeEnd = firstLine.find(" ", codeStart + 1);
554
if (codeStart == -1 || codeEnd == -1) {
559
QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
560
return String(statusText);
563
QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
566
int code = number.toInt(&ok);
574
Value XMLHttpRequest::getStatus() const
576
return httpStatus(responseHeaders);
579
Value XMLHttpRequest::getStatusText() const
581
return httpStatus(responseHeaders, true);
584
void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers)
586
if (!urlMatchesDocumentDomain(finalURL)) {
591
responseHeaders = headers;
598
const char *bytes = (const char *)data.data();
599
int len = (int)data.size();
601
slotData(0, bytes, len);
613
void XMLHttpRequest::slotFinished(KIO::Job *)
616
response += decoder->flush();
619
// make sure to forget about the job before emitting completed,
620
// since changeState triggers JS code, which might e.g. call abort.
622
changeState(Completed);
628
void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
630
if (!urlMatchesDocumentDomain(url)) {
636
void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
638
void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
641
if (state < Loaded ) {
642
responseHeaders = job->queryMetaData("HTTP-Headers");
644
// NOTE: Replace a 304 response with a 200! Both IE and Mozilla do this.
645
// Problem first reported through bug# 110272.
646
int codeStart = responseHeaders.find("304");
647
if ( codeStart != -1) {
648
int codeEnd = responseHeaders.find("\n", codeStart+3);
650
responseHeaders.replace(codeStart, (codeEnd-codeStart), "200 OK");
656
#ifndef APPLE_CHANGES
657
const char *data = (const char *)_data.data();
658
int len = (int)_data.size();
661
if ( decoder == NULL ) {
662
int pos = responseHeaders.find("content-type:", 0, false);
666
int index = responseHeaders.find('\n', pos);
667
QString type = responseHeaders.mid(pos, (index-pos));
668
index = type.find (';');
670
encoding = type.mid( index+1 ).remove(QRegExp("charset[ ]*=[ ]*", false)).stripWhiteSpace();
673
decoder = new Decoder;
674
if (!encoding.isNull())
675
decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
677
// Per section 2 of W3C working draft spec, fall back to "UTF-8".
678
decoder->setEncoding("UTF-8", Decoder::DefaultEncoding);
687
QString decoded = decoder->decode(data, len);
692
changeState(Interactive);
696
Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
698
if (!thisObj.inherits(&XMLHttpRequest::info)) {
699
Object err = Error::create(exec,TypeError);
700
exec->setException(err);
704
XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
706
case XMLHttpRequest::Abort:
709
case XMLHttpRequest::GetAllResponseHeaders:
710
if (args.size() != 0) {
714
return request->getAllResponseHeaders();
715
case XMLHttpRequest::GetResponseHeader:
716
if (args.size() != 1) {
720
return request->getResponseHeader(args[0].toString(exec).qstring());
721
case XMLHttpRequest::Open:
723
if (args.size() < 2 || args.size() > 5) {
727
QString method = args[0].toString(exec).qstring();
728
KHTMLPart *part = ::qt_cast<KHTMLPart *>(Window::retrieveActive(exec)->part());
731
KURL url = KURL(part->document().completeURL(args[1].toString(exec).qstring()).string());
734
if (args.size() >= 3) {
735
async = args[2].toBoolean(exec);
738
if (args.size() >= 4) {
739
url.setUser(args[3].toString(exec).qstring());
742
if (args.size() >= 5) {
743
url.setPass(args[4].toString(exec).qstring());
746
request->open(method, url, async);
750
case XMLHttpRequest::Send:
752
if (args.size() > 1) {
756
if (request->state != Loading) {
761
if (args.size() >= 1) {
762
Object obj = Object::dynamicCast(args[0]);
763
if (obj.isValid() && obj.inherits(&DOMDocument::info)) {
764
DOM::Node docNode = static_cast<KJS::DOMDocument *>(obj.imp())->toNode();
765
DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
768
body = doc->toString().string();
769
// FIXME: also need to set content type, including encoding!
771
} catch(DOM::DOMException& e) {
772
Object err = Error::create(exec, GeneralError, "Exception serializing document");
773
exec->setException(err);
776
body = args[0].toString(exec).qstring();
784
case XMLHttpRequest::SetRequestHeader:
785
if (args.size() != 2) {
789
request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());
793
case XMLHttpRequest::OverrideMIMEType:
794
if (args.size() < 1) {
795
Object err = Error::create(exec, SyntaxError, "Not enough arguments");
796
exec->setException(err);
800
request->overrideMIMEType(args[0].toString(exec).qstring());
810
#include "xmlhttprequest.moc"