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 /* Source for XMLHttpRequestTable.
00114 @begin XMLHttpRequestTable 7
00115   readyState        XMLHttpRequest::ReadyState      DontDelete|ReadOnly
00116   responseText      XMLHttpRequest::ResponseText        DontDelete|ReadOnly
00117   responseXML       XMLHttpRequest::ResponseXML     DontDelete|ReadOnly
00118   status        XMLHttpRequest::Status          DontDelete|ReadOnly
00119   statusText        XMLHttpRequest::StatusText      DontDelete|ReadOnly
00120   onreadystatechange    XMLHttpRequest::Onreadystatechange  DontDelete
00121   onload        XMLHttpRequest::Onload          DontDelete
00122 @end
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 /*attr*/)
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   // a local file can load anything
00262   if (documentURL.protocol().lower() == "file") {
00263     return true;
00264   }
00265 
00266   // but a remote document can only load from the same port on the server
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   // clear stuff from possible previous load
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       // FIXME: determine post encoding correctly by looking in headers for charset
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   // Content-type needs to be set seperately from the other headers
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   // make sure to forget about the job before emitting completed,
00516   // since changeState triggers JS code, which might e.g. call abort.
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       // FIXME: Inherit the default encoding from the parent document?
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         // FIXME: also need to set content type, including encoding!
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 } // end namespace
00673 
00674 #include "xmlhttprequest.moc"
KDE Logo
This file is part of the documentation for khtml Library Version 3.3.90.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Mar 30 10:22:22 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003