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 Thu Apr 14 00:38:38 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003