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