00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "xmlhttprequest.h"
00022 #include "xmlhttprequest.lut.h"
00023 #include "kjs_window.h"
00024 #include "kjs_events.h"
00025
00026 #include "dom/dom_doc.h"
00027 #include "dom/dom_exception.h"
00028 #include "dom/dom_string.h"
00029 #include "misc/loader.h"
00030 #include "html/html_documentimpl.h"
00031 #include "xml/dom2_eventsimpl.h"
00032
00033 #include "khtml_part.h"
00034 #include "khtmlview.h"
00035
00036 #include <kio/scheduler.h>
00037 #include <kio/job.h>
00038 #include <qobject.h>
00039 #include <kdebug.h>
00040
00041 #ifdef APPLE_CHANGES
00042 #include "KWQLoader.h"
00043 #else
00044 #include <kio/netaccess.h>
00045 using KIO::NetAccess;
00046 #endif
00047
00048 using namespace KJS;
00049 using khtml::Decoder;
00050
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063 DEFINE_PROTOTYPE("XMLHttpRequest",XMLHttpRequestProto)
00064 IMPLEMENT_PROTOFUNC_DOM(XMLHttpRequestProtoFunc)
00065 IMPLEMENT_PROTOTYPE(XMLHttpRequestProto,XMLHttpRequestProtoFunc)
00066
00067 namespace KJS {
00068
00069 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
00070 {
00071 jsObject = _jsObject;
00072 }
00073
00074 #ifdef APPLE_CHANGES
00075 void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
00076 {
00077 jsObject->slotData(job, data, size);
00078 }
00079 #else
00080 void XMLHttpRequestQObject::slotData( KIO::Job* job, const QByteArray &data )
00081 {
00082 jsObject->slotData(job, data);
00083 }
00084 #endif
00085
00086 void XMLHttpRequestQObject::slotFinished( KIO::Job* job )
00087 {
00088 jsObject->slotFinished(job);
00089 }
00090
00091 void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KURL& url)
00092 {
00093 jsObject->slotRedirection( job, url );
00094 }
00095
00096 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *, const DOM::Document &d)
00097 : ObjectImp(), doc(d)
00098 {
00099 }
00100
00101 bool XMLHttpRequestConstructorImp::implementsConstruct() const
00102 {
00103 return true;
00104 }
00105
00106 Object XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
00107 {
00108 return Object(new XMLHttpRequest(exec, doc));
00109 }
00110
00111 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125 Value XMLHttpRequest::tryGet(ExecState *exec, const Identifier &propertyName) const
00126 {
00127 return DOMObjectLookupGetValue<XMLHttpRequest,DOMObject>(exec, propertyName, &XMLHttpRequestTable, this);
00128 }
00129
00130 Value XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
00131 {
00132 switch (token) {
00133 case ReadyState:
00134 return Number(state);
00135 case ResponseText:
00136 return getString(DOM::DOMString(response));
00137 case ResponseXML:
00138 if (state != Completed) {
00139 return Undefined();
00140 }
00141 if (!createdDocument) {
00142 QString mimeType = "text/xml";
00143
00144 Value header = getResponseHeader("Content-Type");
00145 if (header.type() != UndefinedType) {
00146 mimeType = QStringList::split(";", header.toString(exec).qstring())[0].stripWhiteSpace();
00147 }
00148
00149 if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
00150 responseXML = DOM::Document(doc->implementation()->createDocument());
00151
00152 DOM::DocumentImpl *docImpl = static_cast<DOM::DocumentImpl *>(responseXML.handle());
00153
00154 docImpl->open();
00155 docImpl->write(response);
00156 docImpl->finishParsing();
00157 docImpl->close();
00158
00159 typeIsXML = true;
00160 } else {
00161 typeIsXML = false;
00162 }
00163 createdDocument = true;
00164 }
00165
00166 if (!typeIsXML) {
00167 return Undefined();
00168 }
00169
00170 return getDOMNode(exec,responseXML);
00171 case Status:
00172 return getStatus();
00173 case StatusText:
00174 return getStatusText();
00175 case Onreadystatechange:
00176 if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObjImp()) {
00177 return onReadyStateChangeListener->listenerObj();
00178 } else {
00179 return Null();
00180 }
00181 case Onload:
00182 if (onLoadListener && onLoadListener->listenerObjImp()) {
00183 return onLoadListener->listenerObj();
00184 } else {
00185 return Null();
00186 }
00187 default:
00188 kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
00189 return Value();
00190 }
00191 }
00192
00193 void XMLHttpRequest::tryPut(ExecState *exec, const Identifier &propertyName, const Value& value, int attr)
00194 {
00195 DOMObjectLookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
00196 }
00197
00198 void XMLHttpRequest::putValueProperty(ExecState *exec, int token, const Value& value, int )
00199 {
00200 switch(token) {
00201 case Onreadystatechange:
00202 onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
00203 if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
00204 break;
00205 case Onload:
00206 onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
00207 if (onLoadListener) onLoadListener->ref();
00208 break;
00209 default:
00210 kdWarning() << "XMLHttpRequest::putValue unhandled token " << token << endl;
00211 }
00212 }
00213
00214 XMLHttpRequest::XMLHttpRequest(ExecState *exec, const DOM::Document &d)
00215 : DOMObject(XMLHttpRequestProto::self(exec)),
00216 qObject(new XMLHttpRequestQObject(this)),
00217 doc(static_cast<DOM::DocumentImpl*>(d.handle())),
00218 contentType(QString::null),
00219 async(true),
00220 job(0),
00221 state(Uninitialized),
00222 onReadyStateChangeListener(0),
00223 onLoadListener(0),
00224 decoder(0),
00225 createdDocument(false),
00226 aborted(false)
00227 {
00228 }
00229
00230 XMLHttpRequest::~XMLHttpRequest()
00231 {
00232 delete qObject;
00233 qObject = 0;
00234 delete decoder;
00235 decoder = 0;
00236 }
00237
00238 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
00239 {
00240 if (state != newState) {
00241 state = newState;
00242
00243 if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) {
00244 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
00245 ev.initEvent("readystatechange", true, true);
00246 onReadyStateChangeListener->handleEvent(ev);
00247 }
00248
00249 if (state == Completed && onLoadListener != 0 && doc->view() && doc->view()->part()) {
00250 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
00251 ev.initEvent("load", true, true);
00252 onLoadListener->handleEvent(ev);
00253 }
00254 }
00255 }
00256
00257 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
00258 {
00259 KURL documentURL(doc->URL());
00260
00261
00262 if (documentURL.protocol().lower() == "file") {
00263 return true;
00264 }
00265
00266
00267 if (documentURL.protocol().lower() == _url.protocol().lower() &&
00268 documentURL.host().lower() == _url.host().lower() &&
00269 documentURL.port() == _url.port()) {
00270 return true;
00271 }
00272
00273 return false;
00274 }
00275
00276 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
00277 {
00278 abort();
00279 aborted = false;
00280
00281
00282 requestHeaders = QString();
00283 responseHeaders = QString();
00284 response = QString();
00285 createdDocument = false;
00286 responseXML = DOM::Document();
00287
00288 changeState(Uninitialized);
00289
00290 if (aborted) {
00291 return;
00292 }
00293
00294 if (!urlMatchesDocumentDomain(_url)) {
00295 return;
00296 }
00297
00298
00299 method = _method;
00300 url = _url;
00301 async = _async;
00302
00303 changeState(Loading);
00304 }
00305
00306 void XMLHttpRequest::send(const QString& _body)
00307 {
00308 aborted = false;
00309 if (method.lower() == "post" && (url.protocol().lower() == "http" || url.protocol().lower() == "https") ) {
00310
00311 job = KIO::http_post( url, QCString(_body.utf8()), false );
00312 if(contentType.isNull())
00313 job->addMetaData( "content-type", "Content-type: text/plain" );
00314 else
00315 job->addMetaData( "content-type", contentType );
00316 }
00317 else
00318 {
00319 job = KIO::get( url, false, false );
00320 }
00321 if (requestHeaders.length() > 0) {
00322 job->addMetaData("customHTTPHeader", requestHeaders);
00323 }
00324 job->addMetaData( "PropagateHttpHeader", "true" );
00325
00326 if (!async) {
00327 QByteArray data;
00328 KURL finalURL;
00329 QString headers;
00330
00331 #ifdef APPLE_CHANGES
00332 data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
00333 #else
00334 QMap<QString, QString> metaData;
00335 if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
00336 headers = metaData[ "HTTP-Headers" ];
00337 }
00338 #endif
00339 job = 0;
00340 processSyncLoadResults(data, finalURL, headers);
00341 return;
00342 }
00343
00344 qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
00345 SLOT( slotFinished( KIO::Job* ) ) );
00346 #ifdef APPLE_CHANGES
00347 qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
00348 SLOT( slotData( KIO::Job*, const char*, int ) ) );
00349 #else
00350 qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
00351 SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
00352 #endif
00353 qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
00354 SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
00355
00356 #ifdef APPLE_CHANGES
00357 KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
00358 #else
00359 KIO::Scheduler::scheduleJob( job );
00360 #endif
00361 }
00362
00363 void XMLHttpRequest::abort()
00364 {
00365 if (job) {
00366 job->kill();
00367 job = 0;
00368 }
00369 delete decoder;
00370 decoder = 0;
00371 aborted = true;
00372 }
00373
00374 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value)
00375 {
00376
00377 if(name.lower() == "content-type") {
00378 contentType = "Content-type: " + value;
00379 return;
00380 }
00381 if (requestHeaders.length() > 0) {
00382 requestHeaders += "\r\n";
00383 }
00384 requestHeaders += name;
00385 requestHeaders += ": ";
00386 requestHeaders += value;
00387 }
00388
00389 Value XMLHttpRequest::getAllResponseHeaders() const
00390 {
00391 if (responseHeaders.isEmpty()) {
00392 return Undefined();
00393 }
00394
00395 int endOfLine = responseHeaders.find("\n");
00396
00397 if (endOfLine == -1) {
00398 return Undefined();
00399 }
00400
00401 return String(responseHeaders.mid(endOfLine + 1) + "\n");
00402 }
00403
00404 Value XMLHttpRequest::getResponseHeader(const QString& name) const
00405 {
00406 if (responseHeaders.isEmpty()) {
00407 return Undefined();
00408 }
00409
00410 QRegExp headerLinePattern(name + ":", false);
00411
00412 int matchLength;
00413 int headerLinePos = headerLinePattern.search(responseHeaders, 0);
00414 matchLength = headerLinePattern.matchedLength();
00415 while (headerLinePos != -1) {
00416 if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
00417 break;
00418 }
00419
00420 headerLinePos = headerLinePattern.search(responseHeaders, headerLinePos + 1);
00421 matchLength = headerLinePattern.matchedLength();
00422 }
00423
00424
00425 if (headerLinePos == -1) {
00426 return Undefined();
00427 }
00428
00429 int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
00430
00431 return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
00432 }
00433
00434 Value XMLHttpRequest::getStatus() const
00435 {
00436 if (responseHeaders.isEmpty()) {
00437 return Undefined();
00438 }
00439
00440 int endOfLine = responseHeaders.find("\n");
00441 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
00442 int codeStart = firstLine.find(" ");
00443 int codeEnd = firstLine.find(" ", codeStart + 1);
00444
00445 if (codeStart == -1 || codeEnd == -1) {
00446 return Undefined();
00447 }
00448
00449 QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
00450
00451 bool ok = false;
00452 int code = number.toInt(&ok);
00453 if (!ok) {
00454 return Undefined();
00455 }
00456
00457 return Number(code);
00458 }
00459
00460 Value XMLHttpRequest::getStatusText() const
00461 {
00462 if (responseHeaders.isEmpty()) {
00463 return Undefined();
00464 }
00465
00466 int endOfLine = responseHeaders.find("\n");
00467 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
00468 int codeStart = firstLine.find(" ");
00469 int codeEnd = firstLine.find(" ", codeStart + 1);
00470
00471 if (codeStart == -1 || codeEnd == -1) {
00472 return Undefined();
00473 }
00474
00475 QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
00476
00477 return String(statusText);
00478 }
00479
00480 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers)
00481 {
00482 if (!urlMatchesDocumentDomain(finalURL)) {
00483 abort();
00484 return;
00485 }
00486
00487 responseHeaders = headers;
00488 changeState(Loaded);
00489 if (aborted) {
00490 return;
00491 }
00492
00493 #ifdef APPLE_CHANGES
00494 const char *bytes = (const char *)data.data();
00495 int len = (int)data.size();
00496
00497 slotData(0, bytes, len);
00498 #else
00499 slotData(0, data);
00500 #endif
00501
00502 if (aborted) {
00503 return;
00504 }
00505
00506 slotFinished(0);
00507 }
00508
00509 void XMLHttpRequest::slotFinished(KIO::Job *)
00510 {
00511 if (decoder) {
00512 response += decoder->flush();
00513 }
00514
00515
00516
00517 job = 0;
00518 changeState(Completed);
00519
00520 delete decoder;
00521 decoder = 0;
00522 }
00523
00524 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
00525 {
00526 if (!urlMatchesDocumentDomain(url)) {
00527 abort();
00528 }
00529 }
00530
00531 #ifdef APPLE_CHANGES
00532 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
00533 #else
00534 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
00535 #endif
00536 {
00537 if (state < Loaded ) {
00538 responseHeaders = job->queryMetaData("HTTP-Headers");
00539 changeState(Loaded);
00540 }
00541
00542 #ifndef APPLE_CHANGES
00543 const char *data = (const char *)_data.data();
00544 int len = (int)_data.size();
00545 #endif
00546
00547 if ( decoder == NULL ) {
00548 decoder = new Decoder;
00549 if (!encoding.isNull())
00550 decoder->setEncoding(encoding.latin1());
00551 else {
00552
00553 }
00554 }
00555 if (len == 0)
00556 return;
00557
00558 if (len == -1)
00559 len = strlen(data);
00560
00561 QString decoded = decoder->decode(data, len);
00562
00563 response += decoded;
00564
00565 if (!aborted) {
00566 changeState(Interactive);
00567 }
00568 }
00569
00570 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
00571 {
00572 if (!thisObj.inherits(&XMLHttpRequest::info)) {
00573 Object err = Error::create(exec,TypeError);
00574 exec->setException(err);
00575 return err;
00576 }
00577
00578 XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
00579 switch (id) {
00580 case XMLHttpRequest::Abort:
00581 request->abort();
00582 return Undefined();
00583 case XMLHttpRequest::GetAllResponseHeaders:
00584 if (args.size() != 0) {
00585 return Undefined();
00586 }
00587
00588 return request->getAllResponseHeaders();
00589 case XMLHttpRequest::GetResponseHeader:
00590 if (args.size() != 1) {
00591 return Undefined();
00592 }
00593
00594 return request->getResponseHeader(args[0].toString(exec).qstring());
00595 case XMLHttpRequest::Open:
00596 {
00597 if (args.size() < 2 || args.size() > 5) {
00598 return Undefined();
00599 }
00600
00601 QString method = args[0].toString(exec).qstring();
00602 KHTMLPart *part = ::qt_cast<KHTMLPart *>(Window::retrieveActive(exec)->part());
00603 if (!part)
00604 return Undefined();
00605 KURL url = KURL(part->document().completeURL(args[1].toString(exec).qstring()).string());
00606
00607 bool async = true;
00608 if (args.size() >= 3) {
00609 async = args[2].toBoolean(exec);
00610 }
00611
00612 if (args.size() >= 4) {
00613 url.setUser(args[3].toString(exec).qstring());
00614 }
00615
00616 if (args.size() >= 5) {
00617 url.setPass(args[4].toString(exec).qstring());
00618 }
00619
00620 request->open(method, url, async);
00621
00622 return Undefined();
00623 }
00624 case XMLHttpRequest::Send:
00625 {
00626 if (args.size() > 1) {
00627 return Undefined();
00628 }
00629
00630 if (request->state != Loading) {
00631 return Undefined();
00632 }
00633
00634 QString body;
00635
00636 if (args.size() >= 1) {
00637 Object obj = Object::dynamicCast(args[0]);
00638 if (!obj.isNull() && obj.inherits(&DOMDocument::info)) {
00639 DOM::Node docNode = static_cast<KJS::DOMDocument *>(obj.imp())->toNode();
00640 DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
00641
00642 try {
00643 body = doc->toString().string();
00644
00645
00646 } catch(DOM::DOMException& e) {
00647 Object err = Error::create(exec, GeneralError, "Exception serializing document");
00648 exec->setException(err);
00649 }
00650 } else {
00651 body = args[0].toString(exec).qstring();
00652 }
00653 }
00654
00655 request->send(body);
00656
00657 return Undefined();
00658 }
00659 case XMLHttpRequest::SetRequestHeader:
00660 if (args.size() != 2) {
00661 return Undefined();
00662 }
00663
00664 request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());
00665
00666 return Undefined();
00667 }
00668
00669 return Undefined();
00670 }
00671
00672 }
00673
00674 #include "xmlhttprequest.moc"