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() << "HTMLDocument::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 async(true), 00219 job(0), 00220 state(Uninitialized), 00221 onReadyStateChangeListener(0), 00222 onLoadListener(0), 00223 decoder(0), 00224 createdDocument(false), 00225 aborted(false) 00226 { 00227 } 00228 00229 XMLHttpRequest::~XMLHttpRequest() 00230 { 00231 delete qObject; 00232 qObject = 0; 00233 delete decoder; 00234 decoder = 0; 00235 } 00236 00237 void XMLHttpRequest::changeState(XMLHttpRequestState newState) 00238 { 00239 if (state != newState) { 00240 state = newState; 00241 00242 if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) { 00243 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents"); 00244 ev.initEvent("readystatechange", true, true); 00245 onReadyStateChangeListener->handleEvent(ev); 00246 } 00247 00248 if (state == Completed && onLoadListener != 0 && doc->view() && doc->view()->part()) { 00249 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents"); 00250 ev.initEvent("load", true, true); 00251 onLoadListener->handleEvent(ev); 00252 } 00253 } 00254 } 00255 00256 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const 00257 { 00258 KURL documentURL(doc->URL()); 00259 00260 // a local file can load anything 00261 if (documentURL.protocol().lower() == "file") { 00262 return true; 00263 } 00264 00265 // but a remote document can only load from the same port on the server 00266 if (documentURL.protocol().lower() == _url.protocol().lower() && 00267 documentURL.host().lower() == _url.host().lower() && 00268 documentURL.port() == _url.port()) { 00269 return true; 00270 } 00271 00272 return false; 00273 } 00274 00275 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async) 00276 { 00277 abort(); 00278 aborted = false; 00279 00280 // clear stuff from possible previous load 00281 requestHeaders = QString(); 00282 responseHeaders = QString(); 00283 response = QString(); 00284 createdDocument = false; 00285 responseXML = DOM::Document(); 00286 00287 changeState(Uninitialized); 00288 00289 if (aborted) { 00290 return; 00291 } 00292 00293 if (!urlMatchesDocumentDomain(_url)) { 00294 return; 00295 } 00296 00297 00298 method = _method; 00299 url = _url; 00300 async = _async; 00301 00302 changeState(Loading); 00303 } 00304 00305 void XMLHttpRequest::send(const QString& _body) 00306 { 00307 aborted = false; 00308 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 } 00313 else 00314 { 00315 job = KIO::get( url, false, false ); 00316 } 00317 if (requestHeaders.length() > 0) { 00318 job->addMetaData("customHTTPHeader", requestHeaders); 00319 } 00320 job->addMetaData( "PropagateHttpHeader", "true" ); 00321 00322 if (!async) { 00323 QByteArray data; 00324 KURL finalURL; 00325 QString headers; 00326 00327 #ifdef APPLE_CHANGES 00328 data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers); 00329 #else 00330 QMap<QString, QString> metaData; 00331 if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) { 00332 headers = metaData[ "HTTP-Headers" ]; 00333 } 00334 #endif 00335 job = 0; 00336 processSyncLoadResults(data, finalURL, headers); 00337 return; 00338 } 00339 00340 qObject->connect( job, SIGNAL( result( KIO::Job* ) ), 00341 SLOT( slotFinished( KIO::Job* ) ) ); 00342 #ifdef APPLE_CHANGES 00343 qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ), 00344 SLOT( slotData( KIO::Job*, const char*, int ) ) ); 00345 #else 00346 qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), 00347 SLOT( slotData( KIO::Job*, const QByteArray& ) ) ); 00348 #endif 00349 qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ), 00350 SLOT( slotRedirection(KIO::Job*, const KURL&) ) ); 00351 00352 #ifdef APPLE_CHANGES 00353 KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job); 00354 #else 00355 KIO::Scheduler::scheduleJob( job ); 00356 #endif 00357 } 00358 00359 void XMLHttpRequest::abort() 00360 { 00361 if (job) { 00362 job->kill(); 00363 job = 0; 00364 } 00365 delete decoder; 00366 decoder = 0; 00367 aborted = true; 00368 } 00369 00370 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value) 00371 { 00372 if (requestHeaders.length() > 0) { 00373 requestHeaders += "\r\n"; 00374 } 00375 requestHeaders += name; 00376 requestHeaders += ": "; 00377 requestHeaders += value; 00378 } 00379 00380 Value XMLHttpRequest::getAllResponseHeaders() const 00381 { 00382 if (responseHeaders.isEmpty()) { 00383 return Undefined(); 00384 } 00385 00386 int endOfLine = responseHeaders.find("\n"); 00387 00388 if (endOfLine == -1) { 00389 return Undefined(); 00390 } 00391 00392 return String(responseHeaders.mid(endOfLine + 1) + "\n"); 00393 } 00394 00395 Value XMLHttpRequest::getResponseHeader(const QString& name) const 00396 { 00397 if (responseHeaders.isEmpty()) { 00398 return Undefined(); 00399 } 00400 00401 QRegExp headerLinePattern(name + ":", false); 00402 00403 int matchLength; 00404 int headerLinePos = headerLinePattern.search(responseHeaders, 0); 00405 matchLength = headerLinePattern.matchedLength(); 00406 while (headerLinePos != -1) { 00407 if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') { 00408 break; 00409 } 00410 00411 headerLinePos = headerLinePattern.search(responseHeaders, headerLinePos + 1); 00412 matchLength = headerLinePattern.matchedLength(); 00413 } 00414 00415 00416 if (headerLinePos == -1) { 00417 return Undefined(); 00418 } 00419 00420 int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength); 00421 00422 return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace()); 00423 } 00424 00425 Value XMLHttpRequest::getStatus() const 00426 { 00427 if (responseHeaders.isEmpty()) { 00428 return Undefined(); 00429 } 00430 00431 int endOfLine = responseHeaders.find("\n"); 00432 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine); 00433 int codeStart = firstLine.find(" "); 00434 int codeEnd = firstLine.find(" ", codeStart + 1); 00435 00436 if (codeStart == -1 || codeEnd == -1) { 00437 return Undefined(); 00438 } 00439 00440 QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1)); 00441 00442 bool ok = false; 00443 int code = number.toInt(&ok); 00444 if (!ok) { 00445 return Undefined(); 00446 } 00447 00448 return Number(code); 00449 } 00450 00451 Value XMLHttpRequest::getStatusText() const 00452 { 00453 if (responseHeaders.isEmpty()) { 00454 return Undefined(); 00455 } 00456 00457 int endOfLine = responseHeaders.find("\n"); 00458 QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine); 00459 int codeStart = firstLine.find(" "); 00460 int codeEnd = firstLine.find(" ", codeStart + 1); 00461 00462 if (codeStart == -1 || codeEnd == -1) { 00463 return Undefined(); 00464 } 00465 00466 QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace(); 00467 00468 return String(statusText); 00469 } 00470 00471 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers) 00472 { 00473 if (!urlMatchesDocumentDomain(finalURL)) { 00474 abort(); 00475 return; 00476 } 00477 00478 responseHeaders = headers; 00479 changeState(Loaded); 00480 if (aborted) { 00481 return; 00482 } 00483 00484 #ifdef APPLE_CHANGES 00485 const char *bytes = (const char *)data.data(); 00486 int len = (int)data.size(); 00487 00488 slotData(0, bytes, len); 00489 #else 00490 slotData(0, data); 00491 #endif 00492 00493 if (aborted) { 00494 return; 00495 } 00496 00497 slotFinished(0); 00498 } 00499 00500 void XMLHttpRequest::slotFinished(KIO::Job *job) 00501 { 00502 if (decoder) { 00503 response += decoder->flush(); 00504 } 00505 00506 changeState(Completed); 00507 job = 0; 00508 00509 delete decoder; 00510 decoder = 0; 00511 } 00512 00513 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url) 00514 { 00515 if (!urlMatchesDocumentDomain(url)) { 00516 abort(); 00517 } 00518 } 00519 00520 #ifdef APPLE_CHANGES 00521 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len ) 00522 #else 00523 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data) 00524 #endif 00525 { 00526 if (state < Loaded ) { 00527 responseHeaders = job->queryMetaData("HTTP-Headers"); 00528 changeState(Loaded); 00529 } 00530 00531 #ifndef APPLE_CHANGES 00532 const char *data = (const char *)_data.data(); 00533 int len = (int)_data.size(); 00534 #endif 00535 00536 if ( decoder == NULL ) { 00537 decoder = new Decoder; 00538 if (!encoding.isNull()) 00539 decoder->setEncoding(encoding.latin1()); 00540 else { 00541 // FIXME: Inherit the default encoding from the parent document? 00542 } 00543 } 00544 if (len == 0) 00545 return; 00546 00547 if (len == -1) 00548 len = strlen(data); 00549 00550 QString decoded = decoder->decode(data, len); 00551 00552 response += decoded; 00553 00554 if (!aborted) { 00555 changeState(Interactive); 00556 } 00557 } 00558 00559 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args) 00560 { 00561 if (!thisObj.inherits(&XMLHttpRequest::info)) { 00562 Object err = Error::create(exec,TypeError); 00563 exec->setException(err); 00564 return err; 00565 } 00566 00567 XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp()); 00568 00569 switch (id) { 00570 case XMLHttpRequest::Abort: 00571 request->abort(); 00572 return Undefined(); 00573 case XMLHttpRequest::GetAllResponseHeaders: 00574 if (args.size() != 0) { 00575 return Undefined(); 00576 } 00577 00578 return request->getAllResponseHeaders(); 00579 case XMLHttpRequest::GetResponseHeader: 00580 if (args.size() != 1) { 00581 return Undefined(); 00582 } 00583 00584 return request->getResponseHeader(args[0].toString(exec).qstring()); 00585 case XMLHttpRequest::Open: 00586 { 00587 if (args.size() < 2 || args.size() > 5) { 00588 return Undefined(); 00589 } 00590 00591 QString method = args[0].toString(exec).qstring(); 00592 KHTMLPart *part = ::qt_cast<KHTMLPart *>(Window::retrieveActive(exec)->part()); 00593 if (!part) 00594 return Undefined(); 00595 KURL url = KURL(part->document().completeURL(args[1].toString(exec).qstring()).string()); 00596 00597 bool async = true; 00598 if (args.size() >= 3) { 00599 async = args[2].toBoolean(exec); 00600 } 00601 00602 if (args.size() >= 4) { 00603 url.setUser(args[3].toString(exec).qstring()); 00604 } 00605 00606 if (args.size() >= 5) { 00607 url.setPass(args[4].toString(exec).qstring()); 00608 } 00609 00610 request->open(method, url, async); 00611 00612 return Undefined(); 00613 } 00614 case XMLHttpRequest::Send: 00615 { 00616 if (args.size() > 1) { 00617 return Undefined(); 00618 } 00619 00620 if (request->state != Loading) { 00621 return Undefined(); 00622 } 00623 00624 QString body; 00625 00626 if (args.size() >= 1) { 00627 if (args[0].toObject(exec).inherits(&DOMDocument::info)) { 00628 DOM::Node docNode = static_cast<KJS::DOMDocument *>(args[0].toObject(exec).imp())->toNode(); 00629 DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle()); 00630 00631 try { 00632 body = doc->toString().string(); 00633 // FIXME: also need to set content type, including encoding! 00634 00635 } catch(DOM::DOMException& e) { 00636 Object err = Error::create(exec, GeneralError, "Exception serializing document"); 00637 exec->setException(err); 00638 } 00639 } else { 00640 // converting certain values (like null) to object can set an exception 00641 exec->clearException(); 00642 body = args[0].toString(exec).qstring(); 00643 } 00644 } 00645 00646 request->send(body); 00647 00648 return Undefined(); 00649 } 00650 case XMLHttpRequest::SetRequestHeader: 00651 if (args.size() != 2) { 00652 return Undefined(); 00653 } 00654 00655 request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring()); 00656 00657 return Undefined(); 00658 } 00659 00660 return Undefined(); 00661 } 00662 00663 } // end namespace 00664 00665 #include "xmlhttprequest.moc"
KDE Logo
This file is part of the documentation for khtml Library Version 3.3.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun Oct 17 11:34:06 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003