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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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"
41 #include "KWQLoader.h"
47 ////////////////////// XMLHttpRequest Object ////////////////////////
49 /* Source for XMLHttpRequestProtoTable.
50 @begin XMLHttpRequestProtoTable 7
51 abort XMLHttpRequest::Abort DontDelete|Function 0
52 getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders DontDelete|Function 0
53 getResponseHeader XMLHttpRequest::GetResponseHeader DontDelete|Function 1
54 open XMLHttpRequest::Open DontDelete|Function 5
55 send XMLHttpRequest::Send DontDelete|Function 1
56 setRequestHeader XMLHttpRequest::SetRequestHeader DontDelete|Function 2
59 DEFINE_PROTOTYPE("XMLHttpRequest",XMLHttpRequestProto)
60 IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
61 IMPLEMENT_PROTOTYPE(XMLHttpRequestProto,XMLHttpRequestProtoFunc)
65 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
71 void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
73 jsObject->slotData(job, data, size);
76 void XMLHttpRequestQObject::slotData( KIO::Job* job, const QByteArray &data )
78 jsObject->slotData(job, data);
82 void XMLHttpRequestQObject::slotFinished( KIO::Job* job )
84 jsObject->slotFinished(job);
87 void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KURL& url)
89 jsObject->slotRedirection( job, url );
92 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *, const DOM::Document &d)
97 bool XMLHttpRequestConstructorImp::implementsConstruct() const
102 Object XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
104 return Object(new XMLHttpRequest(exec, doc));
107 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
109 /* Source for XMLHttpRequestTable.
110 @begin XMLHttpRequestTable 7
111 readyState XMLHttpRequest::ReadyState DontDelete|ReadOnly
112 responseText XMLHttpRequest::ResponseText DontDelete|ReadOnly
113 responseXML XMLHttpRequest::ResponseXML DontDelete|ReadOnly
114 status XMLHttpRequest::Status DontDelete|ReadOnly
115 statusText XMLHttpRequest::StatusText DontDelete|ReadOnly
116 onreadystatechange XMLHttpRequest::Onreadystatechange DontDelete
117 onload XMLHttpRequest::Onload DontDelete
121 Value XMLHttpRequest::tryGet(ExecState *exec, const Identifier &propertyName) const
123 return DOMObjectLookupGetValue<XMLHttpRequest,DOMObject>(exec, propertyName, &XMLHttpRequestTable, this);
126 Value XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
130 return Number(state);
132 return getStringOrNull(DOM::DOMString(response));
134 if (state != Completed) {
137 if (!createdDocument) {
138 QString mimeType = "text/xml";
140 Value header = getResponseHeader("Content-Type");
141 if (header.type() != UndefinedType) {
142 mimeType = QStringList::split(";", header.toString(exec).qstring())[0].stripWhiteSpace();
145 if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
146 responseXML = DOM::Document(doc->implementation()->createDocument());
148 responseXML = DOM::Document(doc->implementation()->createHTMLDocument());
151 DOM::DocumentImpl *docImpl = static_cast<DOM::DocumentImpl *>(responseXML.handle());
154 docImpl->write(response);
155 docImpl->finishParsing();
157 createdDocument = true;
160 return getDOMNode(exec,responseXML);
164 return getStatusText();
165 case Onreadystatechange:
166 if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObjImp()) {
167 return onReadyStateChangeListener->listenerObj();
172 if (onLoadListener && onLoadListener->listenerObjImp()) {
173 return onLoadListener->listenerObj();
178 kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
183 void XMLHttpRequest::tryPut(ExecState *exec, const Identifier &propertyName, const Value& value, int attr)
185 DOMObjectLookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
188 void XMLHttpRequest::putValue(ExecState *exec, int token, const Value& value, int /*attr*/)
191 case Onreadystatechange:
192 onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
193 if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
196 onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
197 if (onLoadListener) onLoadListener->ref();
200 kdWarning() << "HTMLDocument::putValue unhandled token " << token << endl;
204 XMLHttpRequest::XMLHttpRequest(ExecState *exec, const DOM::Document &d)
205 : DOMObject(XMLHttpRequestProto::self(exec)),
206 qObject(new XMLHttpRequestQObject(this)),
207 doc(static_cast<DOM::DocumentImpl*>(d.handle())),
210 state(Uninitialized),
211 onReadyStateChangeListener(0),
214 createdDocument(false)
218 XMLHttpRequest::~XMLHttpRequest()
225 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
227 if (state != newState) {
230 if (onReadyStateChangeListener != 0 && doc->part()) {
231 DOM::Event ev = doc->part()->document().createEvent("HTMLEvents");
232 ev.initEvent("readystatechange", true, true);
233 onReadyStateChangeListener->handleEvent(ev, true);
236 if (state == Completed && onLoadListener != 0 && doc->part()) {
237 DOM::Event ev = doc->part()->document().createEvent("HTMLEvents");
238 ev.initEvent("load", true, true);
239 onLoadListener->handleEvent(ev, true);
244 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
246 KURL documentURL(doc->URL());
248 // a local file can load anything
249 if (documentURL.protocol().lower() == "file") {
253 // but a remote document can only load from the same port on the server
254 if (documentURL.protocol().lower() == _url.protocol().lower() &&
255 documentURL.host().lower() == _url.host().lower() &&
256 documentURL.port() == _url.port()) {
263 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
265 if (!urlMatchesDocumentDomain(_url)) {
273 changeState(Loading);
276 void XMLHttpRequest::send(const QString& _body)
278 if (method.lower() == "post" && (url.protocol().lower() == "http" || url.protocol().lower() == "https") ) {
279 // FIXME: determine post encoding correctly by looking in headers for charset
280 job = KIO::http_post( url, QCString(_body.utf8()), false );
284 job = KIO::get( url, false, false );
286 if (requestHeaders.length() > 0) {
287 job->addMetaData("customHTTPHeader", requestHeaders);
290 qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
291 SLOT( slotFinished( KIO::Job* ) ) );
293 qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
294 SLOT( slotData( KIO::Job*, const char*, int ) ) );
296 qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
297 SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
299 qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
300 SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
303 KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
305 KIO::Scheduler::scheduleJob( job );
309 void XMLHttpRequest::abort()
317 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value)
319 if (requestHeaders.length() > 0) {
320 requestHeaders += "\r\n";
322 requestHeaders += name;
323 requestHeaders += ": ";
324 requestHeaders += value;
327 Value XMLHttpRequest::getAllResponseHeaders() const
329 if (responseHeaders.isEmpty()) {
333 int endOfLine = responseHeaders.find("\n");
335 if (endOfLine == -1) {
339 return String(responseHeaders.mid(endOfLine + 1) + "\n");
342 Value XMLHttpRequest::getResponseHeader(const QString& name) const
344 if (responseHeaders.isEmpty()) {
348 QRegExp headerLinePattern(name + ":", false);
351 int headerLinePos = headerLinePattern.match(responseHeaders, 0, &matchLength);
352 while (headerLinePos != -1) {
353 if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
357 headerLinePos = headerLinePattern.match(responseHeaders, headerLinePos + 1, &matchLength);
361 if (headerLinePos == -1) {
365 int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
367 return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
370 Value XMLHttpRequest::getStatus() const
372 if (responseHeaders.isEmpty()) {
376 int endOfLine = responseHeaders.find("\n");
377 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
378 int codeStart = firstLine.find(" ");
379 int codeEnd = firstLine.find(" ", codeStart + 1);
381 if (codeStart == -1 || codeEnd == -1) {
385 QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
388 int code = number.toInt(&ok);
396 Value XMLHttpRequest::getStatusText() const
398 if (responseHeaders.isEmpty()) {
402 int endOfLine = responseHeaders.find("\n");
403 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
405 return String(firstLine);
408 void XMLHttpRequest::slotFinished(KIO::Job *)
411 response += decoder->flush();
414 changeState(Completed);
423 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
425 if (!urlMatchesDocumentDomain(url)) {
432 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
434 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
437 if (state < Loaded) {
438 responseHeaders = job->queryMetaData("HTTP-Headers");
443 const char *data = (const char *)_data.data();
444 int len = (int)_data.size();
447 if ( decoder == NULL ) {
448 decoder = new Decoder;
449 if (!encoding.isNull())
450 decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
452 // FIXME: Inherit the default encoding from the parent document?
461 QString decoded = decoder->decode(data, len);
466 changeState(Interactive);
470 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
472 if (!thisObj.inherits(&XMLHttpRequest::info)) {
473 Object err = Error::create(exec,TypeError);
474 exec->setException(err);
478 XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
481 case XMLHttpRequest::Abort:
484 case XMLHttpRequest::GetAllResponseHeaders:
485 if (args.size() != 0) {
489 return request->getAllResponseHeaders();
490 case XMLHttpRequest::GetResponseHeader:
491 if (args.size() != 1) {
495 return request->getResponseHeader(args[0].toString(exec).qstring());
496 case XMLHttpRequest::Open:
498 if (args.size() < 2 || args.size() > 5) {
502 if (request->state != Uninitialized) {
506 QString method = args[0].toString(exec).qstring();
507 KURL url = KURL(Window::retrieveActive(exec)->part()->htmlDocument().completeURL(args[1].toString(exec).qstring()).string());
510 if (args.size() >= 3) {
511 async = args[2].toBoolean(exec);
514 if (args.size() >= 4) {
515 url.setUser(args[3].toString(exec).qstring());
518 if (args.size() >= 5) {
519 url.setPass(args[4].toString(exec).qstring());
522 request->open(method, url, async);
526 case XMLHttpRequest::Send:
528 if (args.size() > 1) {
532 if (request->state != Loading) {
538 if (args.size() >= 1) {
539 if (args[0].toObject(exec).inherits(&DOMDocument::info)) {
540 DOM::Node docNode = static_cast<KJS::DOMDocument *>(args[0].toObject(exec).imp())->toNode();
541 DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
544 body = doc->toString().string();
545 // FIXME: also need to set content type, including encoding!
547 } catch(DOM::DOMException& e) {
548 Object err = Error::create(exec, GeneralError, "Exception serializing document");
549 exec->setException(err);
552 body = args[0].toString(exec).qstring();
560 case XMLHttpRequest::SetRequestHeader:
561 if (args.size() != 2) {
565 request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());