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