khtml Library API Documentation

xmlhttprequest.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 /*
00003  *  This file is part of the KDE libraries
00004  *  Copyright (C) 2003 Apple Computer, Inc.
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU Lesser General Public
00017  *  License along with this library; if not, write to the Free Software
00018  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 /* Source for XMLHttpRequestProtoTable.
00054 @begin XMLHttpRequestProtoTable 7
00055   abort         XMLHttpRequest::Abort           DontDelete|Function 0
00056   getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders   DontDelete|Function 0
00057   getResponseHeader XMLHttpRequest::GetResponseHeader   DontDelete|Function 1
00058   open          XMLHttpRequest::Open            DontDelete|Function 5
00059   send          XMLHttpRequest::Send            DontDelete|Function 1
00060   setRequestHeader  XMLHttpRequest::SetRequestHeader    DontDelete|Function 2
00061 @end
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 /* Source for XMLHttpRequestTable.
00115 @begin XMLHttpRequestTable 7
00116   readyState        XMLHttpRequest::ReadyState      DontDelete|ReadOnly
00117   responseText      XMLHttpRequest::ResponseText        DontDelete|ReadOnly
00118   responseXML       XMLHttpRequest::ResponseXML     DontDelete|ReadOnly
00119   status        XMLHttpRequest::Status          DontDelete|ReadOnly
00120   statusText        XMLHttpRequest::StatusText      DontDelete|ReadOnly
00121   onreadystatechange    XMLHttpRequest::Onreadystatechange  DontDelete
00122   onload        XMLHttpRequest::Onload          DontDelete
00123 @end
00124 */
00125 
00126 Value XMLHttpRequest::tryGet(ExecState *exec, const Identifier &propertyName) const
00127 {
00128   return DOMObjectLookupGetValue<XMLHttpRequest,DOMObject>(exec, propertyName, &XMLHttpRequestTable, this);
00129 }
00130 
00131 Value XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
00132 {
00133   switch (token) {
00134   case ReadyState:
00135     return Number(state);
00136   case ResponseText:
00137     return getString(DOM::DOMString(response));
00138   case ResponseXML:
00139     if (state != Completed) {
00140       return Undefined();
00141     }
00142     if (!createdDocument) {
00143       QString mimeType = "text/xml";
00144 
00145       Value header = getResponseHeader("Content-Type");
00146       if (header.type() != UndefinedType) {
00147     mimeType = QStringList::split(";", header.toString(exec).qstring())[0].stripWhiteSpace();
00148       }
00149 
00150       if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
00151     responseXML = DOM::Document(doc->implementation()->createDocument());
00152 
00153     DOM::DocumentImpl *docImpl = static_cast<DOM::DocumentImpl *>(responseXML.handle());
00154 
00155     docImpl->open();
00156     docImpl->write(response);
00157     docImpl->finishParsing();
00158     docImpl->close();
00159 
00160     typeIsXML = true;
00161       } else {
00162     typeIsXML = false;
00163       }
00164       createdDocument = true;
00165     }
00166 
00167     if (!typeIsXML) {
00168       return Undefined();
00169     }
00170 
00171     return getDOMNode(exec,responseXML);
00172   case Status:
00173     return getStatus();
00174   case StatusText:
00175     return getStatusText();
00176   case Onreadystatechange:
00177    if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObjImp()) {
00178      return onReadyStateChangeListener->listenerObj();
00179    } else {
00180      return Null();
00181    }
00182   case Onload:
00183    if (onLoadListener && onLoadListener->listenerObjImp()) {
00184      return onLoadListener->listenerObj();
00185    } else {
00186     return Null();
00187    }
00188   default:
00189     kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
00190     return Value();
00191   }
00192 }
00193 
00194 void XMLHttpRequest::tryPut(ExecState *exec, const Identifier &propertyName, const Value& value, int attr)
00195 {
00196   DOMObjectLookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
00197 }
00198 
00199 void XMLHttpRequest::putValueProperty(ExecState *exec, int token, const Value& value, int /*attr*/)
00200 {
00201   switch(token) {
00202   case Onreadystatechange:
00203     onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
00204     if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
00205     break;
00206   case Onload:
00207     onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
00208     if (onLoadListener) onLoadListener->ref();
00209     break;
00210   default:
00211     kdWarning() << "XMLHttpRequest::putValue unhandled token " << token << endl;
00212   }
00213 }
00214 
00215 XMLHttpRequest::XMLHttpRequest(ExecState *exec, const DOM::Document &d)
00216   : DOMObject(XMLHttpRequestProto::self(exec)),
00217     qObject(new XMLHttpRequestQObject(this)),
00218     doc(static_cast<DOM::DocumentImpl*>(d.handle())),
00219     async(true),
00220     contentType(QString::null),
00221     job(0),
00222     state(Uninitialized),
00223     onReadyStateChangeListener(0),
00224     onLoadListener(0),
00225     decoder(0),
00226     createdDocument(false),
00227     aborted(false)
00228 {
00229 }
00230 
00231 XMLHttpRequest::~XMLHttpRequest()
00232 {
00233   delete qObject;
00234   qObject = 0;
00235   delete decoder;
00236   decoder = 0;
00237 }
00238 
00239 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
00240 {
00241   if (state != newState) {
00242     state = newState;
00243 
00244     if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) {
00245       DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
00246       ev.initEvent("readystatechange", true, true);
00247       onReadyStateChangeListener->handleEvent(ev);
00248     }
00249 
00250     if (state == Completed && onLoadListener != 0 && doc->view() && doc->view()->part()) {
00251       DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
00252       ev.initEvent("load", true, true);
00253       onLoadListener->handleEvent(ev);
00254     }
00255   }
00256 }
00257 
00258 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
00259 {
00260   KURL documentURL(doc->URL());
00261 
00262   // a local file can load anything
00263   if (documentURL.protocol().lower() == "file") {
00264     return true;
00265   }
00266 
00267   // but a remote document can only load from the same port on the server
00268   if (documentURL.protocol().lower() == _url.protocol().lower() &&
00269       documentURL.host().lower() == _url.host().lower() &&
00270       documentURL.port() == _url.port()) {
00271     return true;
00272   }
00273 
00274   return false;
00275 }
00276 
00277 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
00278 {
00279   abort();
00280   aborted = false;
00281 
00282   // clear stuff from possible previous load
00283   requestHeaders = QString();
00284   responseHeaders = QString();
00285   response = QString();
00286   createdDocument = false;
00287   responseXML = DOM::Document();
00288 
00289   changeState(Uninitialized);
00290 
00291   if (aborted) {
00292     return;
00293   }
00294 
00295   if (!urlMatchesDocumentDomain(_url)) {
00296     return;
00297   }
00298 
00299 
00300   method = _method;
00301   url = _url;
00302   async = _async;
00303 
00304   changeState(Loading);
00305 }
00306 
00307 void XMLHttpRequest::send(const QString& _body)
00308 {
00309   aborted = false;
00310   if (method.lower() == "post" && (url.protocol().lower() == "http" || url.protocol().lower() == "https") ) {
00311       // FIXME: determine post encoding correctly by looking in headers for charset
00312       job = KIO::http_post( url, QCString(_body.utf8()), false );
00313       if(contentType.isNull())
00314     job->addMetaData( "content-type", "Content-type: text/plain" );
00315       else
00316     job->addMetaData( "content-type", contentType );
00317   }
00318   else
00319   {
00320      job = KIO::get( url, false, false );
00321   }
00322   if (requestHeaders.length() > 0) {
00323     job->addMetaData("customHTTPHeader", requestHeaders);
00324   }
00325   job->addMetaData( "PropagateHttpHeader", "true" );
00326 
00327   if (!async) {
00328     QByteArray data;
00329     KURL finalURL;
00330     QString headers;
00331 
00332 #ifdef APPLE_CHANGES
00333     data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
00334 #else
00335     QMap<QString, QString> metaData;
00336     if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
00337       headers = metaData[ "HTTP-Headers" ];
00338     }
00339 #endif
00340     job = 0;
00341     processSyncLoadResults(data, finalURL, headers);
00342     return;
00343   }
00344 
00345   qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
00346             SLOT( slotFinished( KIO::Job* ) ) );
00347 #ifdef APPLE_CHANGES
00348   qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
00349             SLOT( slotData( KIO::Job*, const char*, int ) ) );
00350 #else
00351   qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
00352             SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
00353 #endif
00354   qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
00355             SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
00356 
00357 #ifdef APPLE_CHANGES
00358   KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
00359 #else
00360   KIO::Scheduler::scheduleJob( job );
00361 #endif
00362 }
00363 
00364 void XMLHttpRequest::abort()
00365 {
00366   if (job) {
00367     job->kill();
00368     job = 0;
00369   }
00370   delete decoder;
00371   decoder = 0;
00372   aborted = true;
00373 }
00374 
00375 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value)
00376 {
00377   // Content-type needs to be set seperately from the other headers
00378   if(name.lower() == "content-type") {
00379     contentType = "Content-type: " + value;
00380     return;
00381   }
00382   if (requestHeaders.length() > 0) {
00383     requestHeaders += "\r\n";
00384   }
00385   requestHeaders += name;
00386   requestHeaders += ": ";
00387   requestHeaders += value;
00388 }
00389 
00390 Value XMLHttpRequest::getAllResponseHeaders() const
00391 {
00392   if (responseHeaders.isEmpty()) {
00393     return Undefined();
00394   }
00395 
00396   int endOfLine = responseHeaders.find("\n");
00397 
00398   if (endOfLine == -1) {
00399     return Undefined();
00400   }
00401 
00402   return String(responseHeaders.mid(endOfLine + 1) + "\n");
00403 }
00404 
00405 Value XMLHttpRequest::getResponseHeader(const QString& name) const
00406 {
00407   if (responseHeaders.isEmpty()) {
00408     return Undefined();
00409   }
00410 
00411   QRegExp headerLinePattern(name + ":", false);
00412 
00413   int matchLength;
00414   int headerLinePos = headerLinePattern.search(responseHeaders, 0);
00415   matchLength = headerLinePattern.matchedLength();
00416   while (headerLinePos != -1) {
00417     if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
00418       break;
00419     }
00420 
00421     headerLinePos = headerLinePattern.search(responseHeaders, headerLinePos + 1);
00422     matchLength = headerLinePattern.matchedLength();
00423   }
00424 
00425 
00426   if (headerLinePos == -1) {
00427     return Undefined();
00428   }
00429 
00430   int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
00431 
00432   return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
00433 }
00434 
00435 Value XMLHttpRequest::getStatus() const
00436 {
00437   if (responseHeaders.isEmpty()) {
00438     return Undefined();
00439   }
00440 
00441   int endOfLine = responseHeaders.find("\n");
00442   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
00443   int codeStart = firstLine.find(" ");
00444   int codeEnd = firstLine.find(" ", codeStart + 1);
00445 
00446   if (codeStart == -1 || codeEnd == -1) {
00447     return Undefined();
00448   }
00449 
00450   QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
00451 
00452   bool ok = false;
00453   int code = number.toInt(&ok);
00454   if (!ok) {
00455     return Undefined();
00456   }
00457 
00458   return Number(code);
00459 }
00460 
00461 Value XMLHttpRequest::getStatusText() const
00462 {
00463   if (responseHeaders.isEmpty()) {
00464     return Undefined();
00465   }
00466 
00467   int endOfLine = responseHeaders.find("\n");
00468   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
00469   int codeStart = firstLine.find(" ");
00470   int codeEnd = firstLine.find(" ", codeStart + 1);
00471 
00472   if (codeStart == -1 || codeEnd == -1) {
00473     return Undefined();
00474   }
00475 
00476   QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
00477 
00478   return String(statusText);
00479 }
00480 
00481 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers)
00482 {
00483   if (!urlMatchesDocumentDomain(finalURL)) {
00484     abort();
00485     return;
00486   }
00487 
00488   responseHeaders = headers;
00489   changeState(Loaded);
00490   if (aborted) {
00491     return;
00492   }
00493 
00494 #ifdef APPLE_CHANGES
00495   const char *bytes = (const char *)data.data();
00496   int len = (int)data.size();
00497 
00498   slotData(0, bytes, len);
00499 #else
00500   slotData(0, data);
00501 #endif
00502 
00503   if (aborted) {
00504     return;
00505   }
00506 
00507   slotFinished(0);
00508 }
00509 
00510 void XMLHttpRequest::slotFinished(KIO::Job *)
00511 {
00512   if (decoder) {
00513     response += decoder->flush();
00514   }
00515 
00516   // make sure to forget about the job before emitting completed,
00517   // since changeState triggers JS code, which might e.g. call abort.
00518   job = 0;
00519   changeState(Completed);
00520 
00521   delete decoder;
00522   decoder = 0;
00523 }
00524 
00525 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
00526 {
00527   if (!urlMatchesDocumentDomain(url)) {
00528     abort();
00529   }
00530 }
00531 
00532 #ifdef APPLE_CHANGES
00533 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
00534 #else
00535 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
00536 #endif
00537 {
00538   if (state < Loaded ) {
00539     responseHeaders = job->queryMetaData("HTTP-Headers");
00540     changeState(Loaded);
00541   }
00542 
00543 #ifndef APPLE_CHANGES
00544   const char *data = (const char *)_data.data();
00545   int len = (int)_data.size();
00546 #endif
00547 
00548   if ( decoder == NULL ) {
00549     decoder = new Decoder;
00550     if (!encoding.isNull())
00551       decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
00552     else {
00553       // FIXME: Inherit the default encoding from the parent document?
00554     }
00555   }
00556   if (len == 0)
00557     return;
00558 
00559   if (len == -1)
00560     len = strlen(data);
00561 
00562   QString decoded = decoder->decode(data, len);
00563 
00564   response += decoded;
00565 
00566   if (!aborted) {
00567     changeState(Interactive);
00568   }
00569 }
00570 
00571 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
00572 {
00573   if (!thisObj.inherits(&XMLHttpRequest::info)) {
00574     Object err = Error::create(exec,TypeError);
00575     exec->setException(err);
00576     return err;
00577   }
00578 
00579   XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
00580   switch (id) {
00581   case XMLHttpRequest::Abort:
00582     request->abort();
00583     return Undefined();
00584   case XMLHttpRequest::GetAllResponseHeaders:
00585     if (args.size() != 0) {
00586     return Undefined();
00587     }
00588 
00589     return request->getAllResponseHeaders();
00590   case XMLHttpRequest::GetResponseHeader:
00591     if (args.size() != 1) {
00592     return Undefined();
00593     }
00594 
00595     return request->getResponseHeader(args[0].toString(exec).qstring());
00596   case XMLHttpRequest::Open:
00597     {
00598       if (args.size() < 2 || args.size() > 5) {
00599         return Undefined();
00600       }
00601 
00602       QString method = args[0].toString(exec).qstring();
00603       KHTMLPart *part = ::qt_cast<KHTMLPart *>(Window::retrieveActive(exec)->part());
00604       if (!part)
00605         return Undefined();
00606       KURL url = KURL(part->document().completeURL(args[1].toString(exec).qstring()).string());
00607 
00608       bool async = true;
00609       if (args.size() >= 3) {
00610     async = args[2].toBoolean(exec);
00611       }
00612 
00613       if (args.size() >= 4) {
00614     url.setUser(args[3].toString(exec).qstring());
00615       }
00616 
00617       if (args.size() >= 5) {
00618     url.setPass(args[4].toString(exec).qstring());
00619       }
00620 
00621       request->open(method, url, async);
00622 
00623       return Undefined();
00624     }
00625   case XMLHttpRequest::Send:
00626     {
00627       if (args.size() > 1) {
00628         return Undefined();
00629       }
00630 
00631       if (request->state != Loading) {
00632     return Undefined();
00633       }
00634 
00635       QString body;
00636 
00637       if (args.size() >= 1) {
00638     Object obj = Object::dynamicCast(args[0]);
00639     if (!obj.isNull() && obj.inherits(&DOMDocument::info)) {
00640       DOM::Node docNode = static_cast<KJS::DOMDocument *>(obj.imp())->toNode();
00641       DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
00642 
00643       try {
00644         body = doc->toString().string();
00645         // FIXME: also need to set content type, including encoding!
00646 
00647       } catch(DOM::DOMException& e) {
00648          Object err = Error::create(exec, GeneralError, "Exception serializing document");
00649          exec->setException(err);
00650       }
00651     } else {
00652       body = args[0].toString(exec).qstring();
00653     }
00654       }
00655 
00656       request->send(body);
00657 
00658       return Undefined();
00659     }
00660   case XMLHttpRequest::SetRequestHeader:
00661     if (args.size() != 2) {
00662       return Undefined();
00663     }
00664 
00665     request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());
00666 
00667     return Undefined();
00668   }
00669 
00670   return Undefined();
00671 }
00672 
00673 } // end namespace
00674 
00675 #include "xmlhttprequest.moc"
KDE Logo
This file is part of the documentation for khtml Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 7 22:11:51 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003