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 QCString str = _body.utf8();
00339 buf.duplicate(str.data(), str.size() - 1);
00340
00341 job = KIO::http_post( url, buf, false );
00342 if(contentType.isNull())
00343 job->addMetaData( "content-type", "Content-type: text/plain" );
00344 else
00345 job->addMetaData( "content-type", contentType );
00346 }
00347 else {
00348 job = KIO::get( url, false, false );
00349 }
00350
00351 if (!requestHeaders.isEmpty()) {
00352 QString rh;
00353 QMap<QString, QString>::ConstIterator begin = requestHeaders.begin();
00354 QMap<QString, QString>::ConstIterator end = requestHeaders.end();
00355 for (QMap<QString, QString>::ConstIterator i = begin; i != end; ++i) {
00356 if (i != begin)
00357 rh += "\r\n";
00358 rh += i.key() + ": " + i.data();
00359 }
00360
00361 job->addMetaData("customHTTPHeader", rh);
00362 }
00363
00364 job->addMetaData("PropagateHttpHeader", "true");
00365
00366
00367
00368
00369 if (requestHeaders.find("Referer") == requestHeaders.end()) {
00370 KURL documentURL(doc->URL());
00371 documentURL.setPass(QString::null);
00372 documentURL.setUser(QString::null);
00373 job->addMetaData("referrer", documentURL.url());
00374
00375 }
00376
00377 if (!async) {
00378 QByteArray data;
00379 KURL finalURL;
00380 QString headers;
00381
00382 #ifdef APPLE_CHANGES
00383 data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
00384 #else
00385 QMap<QString, QString> metaData;
00386 if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
00387 headers = metaData[ "HTTP-Headers" ];
00388 }
00389 #endif
00390 job = 0;
00391 processSyncLoadResults(data, finalURL, headers);
00392 return;
00393 }
00394
00395 qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
00396 SLOT( slotFinished( KIO::Job* ) ) );
00397 #ifdef APPLE_CHANGES
00398 qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
00399 SLOT( slotData( KIO::Job*, const char*, int ) ) );
00400 #else
00401 qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
00402 SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
00403 #endif
00404 qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
00405 SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
00406
00407 #ifdef APPLE_CHANGES
00408 KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
00409 #else
00410 KIO::Scheduler::scheduleJob( job );
00411 #endif
00412 }
00413
00414 void XMLHttpRequest::abort()
00415 {
00416 if (job) {
00417 job->kill();
00418 job = 0;
00419 }
00420 delete decoder;
00421 decoder = 0;
00422 aborted = true;
00423 }
00424
00425 void XMLHttpRequest::setRequestHeader(const QString& _name, const QString &value)
00426 {
00427 QString name = _name.lower().stripWhiteSpace();
00428
00429
00430 if(name == "content-type") {
00431 contentType = "Content-type: " + value;
00432 return;
00433 }
00434
00435
00436 if(name == "referer") {
00437 KURL referrerURL(value);
00438 if (urlMatchesDocumentDomain(referrerURL))
00439 requestHeaders[name] = referrerURL.url();
00440 return;
00441 }
00442
00443
00444
00445
00446
00447 if (name == "get" || name == "post") {
00448 KURL reqURL (doc->URL(), value.stripWhiteSpace());
00449 open(name, reqURL, async);
00450 return;
00451 }
00452
00453
00454
00455 QStringList bannedHeaders = QStringList::split(',',
00456 QString::fromLatin1(BANNED_HTTP_HEADERS));
00457
00458 if (bannedHeaders.contains(name))
00459 return;
00460
00461 requestHeaders[name] = value.stripWhiteSpace();
00462 }
00463
00464 Value XMLHttpRequest::getAllResponseHeaders() const
00465 {
00466 if (responseHeaders.isEmpty()) {
00467 return Undefined();
00468 }
00469
00470 int endOfLine = responseHeaders.find("\n");
00471
00472 if (endOfLine == -1) {
00473 return Undefined();
00474 }
00475
00476 return String(responseHeaders.mid(endOfLine + 1) + "\n");
00477 }
00478
00479 Value XMLHttpRequest::getResponseHeader(const QString& name) const
00480 {
00481 if (responseHeaders.isEmpty()) {
00482 return Undefined();
00483 }
00484
00485 QRegExp headerLinePattern(name + ":", false);
00486
00487 int matchLength;
00488 int headerLinePos = headerLinePattern.search(responseHeaders, 0);
00489 matchLength = headerLinePattern.matchedLength();
00490 while (headerLinePos != -1) {
00491 if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
00492 break;
00493 }
00494
00495 headerLinePos = headerLinePattern.search(responseHeaders, headerLinePos + 1);
00496 matchLength = headerLinePattern.matchedLength();
00497 }
00498
00499
00500 if (headerLinePos == -1) {
00501 return Undefined();
00502 }
00503
00504 int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
00505
00506 return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
00507 }
00508
00509 static Value httpStatus(const QString& response, bool textStatus = false)
00510 {
00511 if (response.isEmpty()) {
00512 return Undefined();
00513 }
00514
00515 int endOfLine = response.find("\n");
00516 QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine);
00517 int codeStart = firstLine.find(" ");
00518 int codeEnd = firstLine.find(" ", codeStart + 1);
00519
00520 if (codeStart == -1 || codeEnd == -1) {
00521 return Undefined();
00522 }
00523
00524 if (textStatus) {
00525 QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
00526 return String(statusText);
00527 }
00528
00529 QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
00530
00531 bool ok = false;
00532 int code = number.toInt(&ok);
00533 if (!ok) {
00534 return Undefined();
00535 }
00536
00537 return Number(code);
00538 }
00539
00540 Value XMLHttpRequest::getStatus() const
00541 {
00542 return httpStatus(responseHeaders);
00543 }
00544
00545 Value XMLHttpRequest::getStatusText() const
00546 {
00547 return httpStatus(responseHeaders, true);
00548 }
00549
00550 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers)
00551 {
00552 if (!urlMatchesDocumentDomain(finalURL)) {
00553 abort();
00554 return;
00555 }
00556
00557 responseHeaders = headers;
00558 changeState(Loaded);
00559 if (aborted) {
00560 return;
00561 }
00562
00563 #ifdef APPLE_CHANGES
00564 const char *bytes = (const char *)data.data();
00565 int len = (int)data.size();
00566
00567 slotData(0, bytes, len);
00568 #else
00569 slotData(0, data);
00570 #endif
00571
00572 if (aborted) {
00573 return;
00574 }
00575
00576 slotFinished(0);
00577 }
00578
00579 void XMLHttpRequest::slotFinished(KIO::Job *)
00580 {
00581 if (decoder) {
00582 response += decoder->flush();
00583 }
00584
00585
00586
00587 job = 0;
00588 changeState(Completed);
00589
00590 delete decoder;
00591 decoder = 0;
00592 }
00593
00594 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
00595 {
00596 if (!urlMatchesDocumentDomain(url)) {
00597 abort();
00598 }
00599 }
00600
00601 #ifdef APPLE_CHANGES
00602 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
00603 #else
00604 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
00605 #endif
00606 {
00607 if (state < Loaded ) {
00608 responseHeaders = job->queryMetaData("HTTP-Headers");
00609
00610
00611
00612 int codeStart = responseHeaders.find("304");
00613 if ( codeStart != -1) {
00614 int codeEnd = responseHeaders.find("\n", codeStart+3);
00615 if (codeEnd != -1)
00616 responseHeaders.replace(codeStart, (codeEnd-codeStart), "200 OK");
00617 }
00618
00619 changeState(Loaded);
00620 }
00621
00622 #ifndef APPLE_CHANGES
00623 const char *data = (const char *)_data.data();
00624 int len = (int)_data.size();
00625 #endif
00626
00627 if ( decoder == NULL ) {
00628 int pos = responseHeaders.find("content-type:", 0, false);
00629
00630 if ( pos > -1 ) {
00631 pos += 13;
00632 int index = responseHeaders.find('\n', pos);
00633 QString type = responseHeaders.mid(pos, (index-pos));
00634 index = type.find (';');
00635 if (index > -1)
00636 encoding = type.mid( index+1 ).remove(QRegExp("charset[ ]*=[ ]*", false)).stripWhiteSpace();
00637 }
00638
00639 decoder = new Decoder;
00640 if (!encoding.isNull())
00641 decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
00642 else {
00643
00644 }
00645 }
00646 if (len == 0)
00647 return;
00648
00649 if (len == -1)
00650 len = strlen(data);
00651
00652 QString decoded = decoder->decode(data, len);
00653
00654 response += decoded;
00655
00656 if (!aborted) {
00657 changeState(Interactive);
00658 }
00659 }
00660
00661 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
00662 {
00663 if (!thisObj.inherits(&XMLHttpRequest::info)) {
00664 Object err = Error::create(exec,TypeError);
00665 exec->setException(err);
00666 return err;
00667 }
00668
00669 XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
00670 switch (id) {
00671 case XMLHttpRequest::Abort:
00672 request->abort();
00673 return Undefined();
00674 case XMLHttpRequest::GetAllResponseHeaders:
00675 if (args.size() != 0) {
00676 return Undefined();
00677 }
00678
00679 return request->getAllResponseHeaders();
00680 case XMLHttpRequest::GetResponseHeader:
00681 if (args.size() != 1) {
00682 return Undefined();
00683 }
00684
00685 return request->getResponseHeader(args[0].toString(exec).qstring());
00686 case XMLHttpRequest::Open:
00687 {
00688 if (args.size() < 2 || args.size() > 5) {
00689 return Undefined();
00690 }
00691
00692 QString method = args[0].toString(exec).qstring();
00693 KHTMLPart *part = ::qt_cast<KHTMLPart *>(Window::retrieveActive(exec)->part());
00694 if (!part)
00695 return Undefined();
00696 KURL url = KURL(part->document().completeURL(args[1].toString(exec).qstring()).string());
00697
00698 bool async = true;
00699 if (args.size() >= 3) {
00700 async = args[2].toBoolean(exec);
00701 }
00702
00703 if (args.size() >= 4) {
00704 url.setUser(args[3].toString(exec).qstring());
00705 }
00706
00707 if (args.size() >= 5) {
00708 url.setPass(args[4].toString(exec).qstring());
00709 }
00710
00711 request->open(method, url, async);
00712
00713 return Undefined();
00714 }
00715 case XMLHttpRequest::Send:
00716 {
00717 if (args.size() > 1) {
00718 return Undefined();
00719 }
00720
00721 if (request->state != Loading) {
00722 return Undefined();
00723 }
00724
00725 QString body;
00726 if (args.size() >= 1) {
00727 Object obj = Object::dynamicCast(args[0]);
00728 if (obj.isValid() && obj.inherits(&DOMDocument::info)) {
00729 DOM::Node docNode = static_cast<KJS::DOMDocument *>(obj.imp())->toNode();
00730 DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
00731
00732 try {
00733 body = doc->toString().string();
00734
00735
00736 } catch(DOM::DOMException& e) {
00737 Object err = Error::create(exec, GeneralError, "Exception serializing document");
00738 exec->setException(err);
00739 }
00740 } else {
00741 body = args[0].toString(exec).qstring();
00742 }
00743 }
00744
00745 request->send(body);
00746
00747 return Undefined();
00748 }
00749 case XMLHttpRequest::SetRequestHeader:
00750 if (args.size() != 2) {
00751 return Undefined();
00752 }
00753
00754 request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());
00755
00756 return Undefined();
00757 }
00758
00759 return Undefined();
00760 }
00761
00762 }
00763
00764 #include "xmlhttprequest.moc"