File indexing completed on 2023-10-03 03:17:48
0001 /* 0002 * This file is part of the KDE libraries 0003 * Copyright (C) 2003 Apple Computer, Inc. 0004 * 0005 * This library is free software; you can redistribute it and/or 0006 * modify it under the terms of the GNU Lesser General Public 0007 * License as published by the Free Software Foundation; either 0008 * version 2 of the License, or (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Lesser General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Lesser General Public 0016 * License along with this library; if not, write to the Free Software 0017 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0018 */ 0019 0020 #include "xmlhttprequest.h" 0021 #include "xmlhttprequest.lut.h" 0022 #include "kjs_window.h" 0023 #include "kjs_events.h" 0024 0025 #include "dom/dom_doc.h" 0026 #include "dom/dom_exception.h" 0027 #include "dom/dom_string.h" 0028 #include "misc/loader.h" 0029 #include "misc/translator.h" 0030 #include "html/html_documentimpl.h" 0031 #include "xml/dom2_eventsimpl.h" 0032 0033 #include "khtml_part.h" 0034 #include "khtmlview.h" 0035 0036 #include <kio/scheduler.h> 0037 #include <kio/job.h> 0038 #include <QObject> 0039 #include "khtml_debug.h" 0040 0041 using namespace KJS; 0042 using namespace DOM; 0043 // 0044 ////////////////////// XMLHttpRequest Object //////////////////////// 0045 0046 /* Source for XMLHttpRequestProtoTable. 0047 @begin XMLHttpRequestProtoTable 7 0048 abort XMLHttpRequest::Abort DontDelete|Function 0 0049 getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders DontDelete|Function 0 0050 getResponseHeader XMLHttpRequest::GetResponseHeader DontDelete|Function 1 0051 open XMLHttpRequest::Open DontDelete|Function 5 0052 overrideMimeType XMLHttpRequest::OverrideMIMEType DontDelete|Function 1 0053 send XMLHttpRequest::Send DontDelete|Function 1 0054 setRequestHeader XMLHttpRequest::SetRequestHeader DontDelete|Function 2 0055 @end 0056 */ 0057 0058 namespace KJS 0059 { 0060 0061 KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto) 0062 KJS_IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc) 0063 KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest", XMLHttpRequestProto, XMLHttpRequestProtoFunc, ObjectPrototype) 0064 0065 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject) 0066 { 0067 jsObject = _jsObject; 0068 } 0069 0070 #ifdef APPLE_CHANGES 0071 void XMLHttpRequestQObject::slotData(KIO::Job *job, const char *data, int size) 0072 { 0073 jsObject->slotData(job, data, size); 0074 } 0075 #else 0076 void XMLHttpRequestQObject::slotData(KIO::Job *job, const QByteArray &data) 0077 { 0078 jsObject->slotData(job, data); 0079 } 0080 #endif 0081 0082 void XMLHttpRequestQObject::slotFinished(KJob *job) 0083 { 0084 jsObject->slotFinished(job); 0085 } 0086 0087 void XMLHttpRequestQObject::slotRedirection(KIO::Job *job, const QUrl &url) 0088 { 0089 jsObject->slotRedirection(job, url); 0090 } 0091 0092 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *exec, DOM::DocumentImpl *d) 0093 : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d) 0094 { 0095 JSObject *proto = XMLHttpRequestProto::self(exec); 0096 putDirect(exec->propertyNames().prototype, proto, DontDelete | ReadOnly); 0097 } 0098 0099 bool XMLHttpRequestConstructorImp::implementsConstruct() const 0100 { 0101 return true; 0102 } 0103 0104 JSObject *XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &) 0105 { 0106 return new XMLHttpRequest(exec, doc.get()); 0107 } 0108 0109 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", nullptr, &XMLHttpRequestTable, nullptr }; 0110 0111 /* Source for XMLHttpRequestTable. 0112 @begin XMLHttpRequestTable 7 0113 readyState XMLHttpRequest::ReadyState DontDelete|ReadOnly 0114 responseText XMLHttpRequest::ResponseText DontDelete|ReadOnly 0115 responseXML XMLHttpRequest::ResponseXML DontDelete|ReadOnly 0116 status XMLHttpRequest::Status DontDelete|ReadOnly 0117 statusText XMLHttpRequest::StatusText DontDelete|ReadOnly 0118 onreadystatechange XMLHttpRequest::Onreadystatechange DontDelete 0119 onload XMLHttpRequest::Onload DontDelete 0120 @end 0121 */ 0122 0123 bool XMLHttpRequest::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot) 0124 { 0125 return getStaticValueSlot<XMLHttpRequest, DOMObject>(exec, &XMLHttpRequestTable, this, propertyName, slot); 0126 } 0127 0128 JSValue *XMLHttpRequest::getValueProperty(ExecState *exec, int token) const 0129 { 0130 switch (token) { 0131 case ReadyState: 0132 return jsNumber(m_state); 0133 case ResponseText: 0134 return ::getStringOrNull(DOM::DOMString(response)); 0135 case ResponseXML: 0136 if (m_state != XHRS_Loaded) { 0137 return jsNull(); 0138 } 0139 if (!createdDocument) { 0140 QString mimeType = "text/xml"; 0141 0142 if (!m_mimeTypeOverride.isEmpty()) { 0143 mimeType = m_mimeTypeOverride; 0144 } else { 0145 int dummy; 0146 JSValue *header = getResponseHeader("Content-Type", dummy); 0147 if (!header->isUndefinedOrNull()) { 0148 mimeType = header->toString(exec).qstring().split(";")[0].trimmed(); 0149 } 0150 } 0151 0152 if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") { 0153 responseXML = doc->implementation()->createDocument(); 0154 0155 responseXML->open(); 0156 responseXML->setURL(url.url()); 0157 responseXML->write(response); 0158 responseXML->finishParsing(); 0159 responseXML->close(); 0160 0161 typeIsXML = true; 0162 } else { 0163 typeIsXML = false; 0164 } 0165 createdDocument = true; 0166 } 0167 0168 if (!typeIsXML) { 0169 return jsNull(); 0170 } 0171 0172 return getDOMNode(exec, responseXML.get()); 0173 case Status: 0174 return getStatus(); 0175 case StatusText: 0176 return getStatusText(); 0177 case Onreadystatechange: 0178 if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObj()) { 0179 return onReadyStateChangeListener->listenerObj(); 0180 } else { 0181 return jsNull(); 0182 } 0183 case Onload: 0184 if (onLoadListener && onLoadListener->listenerObj()) { 0185 return onLoadListener->listenerObj(); 0186 } else { 0187 return jsNull(); 0188 } 0189 default: 0190 qCWarning(KHTML_LOG) << "XMLHttpRequest::getValueProperty unhandled token " << token; 0191 return nullptr; 0192 } 0193 } 0194 0195 void XMLHttpRequest::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) 0196 { 0197 lookupPut<XMLHttpRequest, DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this); 0198 } 0199 0200 void XMLHttpRequest::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/) 0201 { 0202 switch (token) { 0203 case Onreadystatechange: 0204 if (onReadyStateChangeListener) { 0205 onReadyStateChangeListener->deref(); 0206 } 0207 onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true); 0208 if (onReadyStateChangeListener) { 0209 onReadyStateChangeListener->ref(); 0210 } 0211 break; 0212 case Onload: 0213 if (onLoadListener) { 0214 onLoadListener->deref(); 0215 } 0216 onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true); 0217 if (onLoadListener) { 0218 onLoadListener->ref(); 0219 } 0220 break; 0221 default: 0222 qCWarning(KHTML_LOG) << "XMLHttpRequest::putValue unhandled token " << token; 0223 } 0224 } 0225 0226 // Token according to RFC 2616 0227 static bool isValidFieldName(const QString &name) 0228 { 0229 const int l = name.length(); 0230 if (l == 0) { 0231 return false; 0232 } 0233 0234 const QChar *c = name.constData(); 0235 for (int i = 0; i < l; ++i, ++c) { 0236 ushort u = c->unicode(); 0237 if (u < 32 || u > 126) { 0238 return false; 0239 } 0240 switch (u) { 0241 case '(': case ')': case '<': case '>': 0242 case '@': case ',': case ';': case ':': 0243 case '\\': case '"': case '/': 0244 case '[': case ']': case '?': case '=': 0245 case '{': case '}': case '\t': case ' ': 0246 return false; 0247 default: 0248 break; 0249 } 0250 } 0251 return true; 0252 } 0253 0254 static bool isValidFieldValue(const QString &name) 0255 { 0256 const int l = name.length(); 0257 if (l == 0) { 0258 return true; 0259 } 0260 0261 const QChar *c = name.constData(); 0262 for (int i = 0; i < l; ++i, ++c) { 0263 ushort u = c->unicode(); 0264 if (u == '\n' || u == '\r') { 0265 return false; 0266 } 0267 } 0268 0269 // ### what is invalid? 0270 return true; 0271 } 0272 0273 static bool canSetRequestHeader(const QString &name) 0274 { 0275 if (name.startsWith(QLatin1String("sec-"), Qt::CaseInsensitive) || 0276 name.startsWith(QLatin1String("proxy-"), Qt::CaseInsensitive)) { 0277 return false; 0278 } 0279 0280 static QSet<CaseInsensitiveString> forbiddenHeaders; 0281 if (forbiddenHeaders.isEmpty()) { 0282 static const char *const hdrs[] = { 0283 "accept-charset", 0284 "accept-encoding", 0285 "access-control-request-headers", 0286 "access-control-request-method", 0287 "connection", 0288 "content-length", 0289 "content-transfer-encoding", 0290 "cookie", 0291 "cookie2", 0292 "date", 0293 "dnt", 0294 "expect", 0295 "host", 0296 "keep-alive", 0297 "origin", 0298 "referer", 0299 "te", 0300 "trailer", 0301 "transfer-encoding", 0302 "upgrade", 0303 "user-agent", 0304 "via" 0305 }; 0306 for (size_t i = 0; i < sizeof(hdrs) / sizeof(char *); ++i) { 0307 forbiddenHeaders.insert(CaseInsensitiveString(hdrs[i])); 0308 } 0309 } 0310 0311 return !forbiddenHeaders.contains(name); 0312 } 0313 0314 XMLHttpRequest::XMLHttpRequest(ExecState *exec, DOM::DocumentImpl *d) 0315 : qObject(new XMLHttpRequestQObject(this)), 0316 doc(d), 0317 async(true), 0318 contentType(QString()), 0319 job(nullptr), 0320 m_state(XHRS_Uninitialized), 0321 onReadyStateChangeListener(nullptr), 0322 onLoadListener(nullptr), 0323 decoder(nullptr), 0324 binaryMode(false), 0325 response(QString::fromLatin1("")), 0326 createdDocument(false), 0327 aborted(false) 0328 { 0329 ref(); // we're a GC point, so refcount pin. 0330 setPrototype(XMLHttpRequestProto::self(exec)); 0331 } 0332 0333 XMLHttpRequest::~XMLHttpRequest() 0334 { 0335 if (job && m_method != QLatin1String("POST")) { 0336 job->kill(); 0337 job = nullptr; 0338 } 0339 if (onLoadListener) { 0340 onLoadListener->deref(); 0341 } 0342 if (onReadyStateChangeListener) { 0343 onReadyStateChangeListener->deref(); 0344 } 0345 delete qObject; 0346 qObject = nullptr; 0347 delete decoder; 0348 decoder = nullptr; 0349 } 0350 0351 void XMLHttpRequest::changeState(XMLHttpRequestState newState) 0352 { 0353 // Other engines cancel transfer if the controlling document doesn't 0354 // exist anymore. Match that, though being paranoid about post 0355 // (And don't emit any events w/o a doc, if we're kept alive otherwise). 0356 if (!doc) { 0357 if (job && m_method != QLatin1String("POST")) { 0358 job->kill(); 0359 job = nullptr; 0360 } 0361 return; 0362 } 0363 0364 if (m_state != newState) { 0365 m_state = newState; 0366 ProtectedPtr<JSObject> ref(this); 0367 0368 if (onReadyStateChangeListener != nullptr && doc->view() && doc->view()->part()) { 0369 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents"); 0370 ev.initEvent("readystatechange", true, true); 0371 ev.handle()->setTarget(this); 0372 ev.handle()->setCurrentTarget(this); 0373 onReadyStateChangeListener->handleEvent(ev); 0374 0375 // Make sure the event doesn't point to us, since it can't prevent 0376 // us from being collecte. 0377 ev.handle()->setTarget(nullptr); 0378 ev.handle()->setCurrentTarget(nullptr); 0379 } 0380 0381 if (m_state == XHRS_Loaded && onLoadListener != nullptr && doc->view() && doc->view()->part()) { 0382 DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents"); 0383 ev.initEvent("load", true, true); 0384 ev.handle()->setTarget(this); 0385 ev.handle()->setCurrentTarget(this); 0386 onLoadListener->handleEvent(ev); 0387 ev.handle()->setTarget(nullptr); 0388 ev.handle()->setCurrentTarget(nullptr); 0389 } 0390 } 0391 } 0392 0393 bool XMLHttpRequest::urlMatchesDocumentDomain(const QUrl &_url) const 0394 { 0395 // No need to do work if _url is not valid... 0396 if (!_url.isValid()) { 0397 return false; 0398 } 0399 0400 QUrl documentURL(doc->URL()); 0401 0402 // a local file can load anything 0403 if (documentURL.isLocalFile()) { 0404 return true; 0405 } 0406 0407 // but a remote document can only load from the same port on the server 0408 if (documentURL.scheme() == _url.scheme() && 0409 documentURL.host().toLower() == _url.host().toLower() && 0410 documentURL.port() == _url.port()) { 0411 return true; 0412 } 0413 0414 return false; 0415 } 0416 0417 // Methods we're to recognize per the XHR spec (3.6.1, #3). 0418 // We map it to whether the method should be permitted or not (#4) 0419 static const IDTranslator<QByteArray, bool, const char *>::Info methodsTable[] = { 0420 {"CONNECT", false}, 0421 {"DELETE", true}, 0422 {"GET", true}, 0423 {"HEAD", true}, 0424 {"OPTIONS", true}, 0425 {"POST", true}, 0426 {"PUT", true}, 0427 {"TRACE", false}, 0428 {"TRACK", false}, 0429 {nullptr, false} 0430 }; 0431 0432 MAKE_TRANSLATOR(methodsLookup, QByteArray, bool, const char *, methodsTable) 0433 0434 void XMLHttpRequest::open(const QString &_method, const QUrl &_url, bool _async, int &ec) 0435 { 0436 abort(); 0437 aborted = false; 0438 0439 // clear stuff from possible previous load 0440 m_requestHeaders.clear(); 0441 responseHeaders.clear(); 0442 response = QString::fromLatin1(""); 0443 createdDocument = false; 0444 responseXML = nullptr; 0445 0446 if (!urlMatchesDocumentDomain(_url)) { 0447 ec = DOMException::SECURITY_ERR; 0448 return; 0449 } 0450 0451 // ### potentially raise a SYNTAX_ERR 0452 0453 // Lookup if the method is well-known, and if so check if it's OK 0454 QByteArray methodNormalized = _method.toUpper().toUtf8(); 0455 if (methodsLookup()->hasLeft(methodNormalized)) { 0456 if (methodsLookup()->toRight(methodNormalized)) { 0457 // OK, replace with the canonical version... 0458 m_method = _method.toUpper(); 0459 } else { 0460 // Scary stuff like CONNECT 0461 ec = DOMException::SECURITY_ERR; 0462 return; 0463 } 0464 } else { 0465 // Unknown -> pass through unchanged 0466 m_method = _method; 0467 } 0468 0469 url = _url; 0470 async = _async; 0471 0472 changeState(XHRS_Open); 0473 } 0474 0475 void XMLHttpRequest::send(const QString &_body, int &ec) 0476 { 0477 aborted = false; 0478 0479 if (m_state != XHRS_Open) { 0480 ec = DOMException::INVALID_STATE_ERR; 0481 return; 0482 } 0483 0484 const QString protocol = url.scheme(); 0485 // Abandon the request when the protocol is other than "http", 0486 // instead of blindly doing a KIO::get on other protocols like file:/. 0487 if (!protocol.startsWith(QLatin1String("http")) && 0488 !protocol.startsWith(QLatin1String("webdav"))) { 0489 ec = DOMException::INVALID_ACCESS_ERR; 0490 abort(); 0491 return; 0492 } 0493 0494 // We need to use a POST-like setup even for non-post whenever we 0495 // have a payload. 0496 const bool havePayload = !_body.isEmpty(); 0497 if (m_method == QLatin1String("POST") || havePayload) { 0498 // FIXME: determine post encoding correctly by looking in headers 0499 // for charset. 0500 QByteArray buf = _body.toUtf8(); 0501 job = KIO::storedHttpPost(buf, url, KIO::HideProgressInfo); 0502 } else { 0503 job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); 0504 } 0505 0506 // Regardless of job type, make sure the method is set 0507 job->addMetaData("CustomHTTPMethod", m_method); 0508 0509 // Set headers 0510 0511 if (!contentType.isNull()) { 0512 job->addMetaData("content-type", contentType); 0513 } else if (havePayload) { 0514 job->addMetaData("content-type", "Content-type: text/plain"); 0515 } 0516 0517 if (!m_requestHeaders.isEmpty()) { 0518 QString rh; 0519 HTTPHeaderMap::ConstIterator begin = m_requestHeaders.constBegin(); 0520 HTTPHeaderMap::ConstIterator end = m_requestHeaders.constEnd(); 0521 for (HTTPHeaderMap::ConstIterator i = begin; i != end; ++i) { 0522 QString key = i.key().original(); 0523 QString value = i.value(); 0524 if (key.toLower() == "accept") { 0525 // The HTTP KIO worker supports an override this way 0526 job->addMetaData("accept", value); 0527 } else { 0528 if (!rh.isEmpty()) { 0529 rh += "\r\n"; 0530 } 0531 rh += key + ": " + value; 0532 } 0533 } 0534 0535 job->addMetaData("customHTTPHeader", rh); 0536 } 0537 0538 job->addMetaData("PropagateHttpHeader", "true"); 0539 0540 // Set the default referrer. NOTE: the user can still disable 0541 // this feature at the protocol level (kio_http). 0542 QUrl documentURL(doc->URL()); 0543 documentURL.setPassword(QString()); 0544 documentURL.setUserName(QString()); 0545 job->addMetaData("referrer", documentURL.url()); 0546 // qCDebug(KHTML_LOG) << "Adding referrer: " << documentURL; 0547 0548 if (!async) { 0549 QByteArray data; 0550 QUrl finalURL; 0551 QString headers; 0552 0553 #ifdef APPLE_CHANGES 0554 data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers); 0555 #else 0556 QMap<QString, QString> metaData; 0557 0558 if (job->exec()) { 0559 data = job->data(); 0560 finalURL = job->redirectUrl().isEmpty() ? job->url() : job->redirectUrl(); 0561 headers = metaData[ "HTTP-Headers" ]; 0562 } 0563 #endif 0564 job = nullptr; 0565 processSyncLoadResults(data, finalURL, headers); 0566 return; 0567 } 0568 0569 qObject->connect(job, SIGNAL(result(KJob*)), 0570 SLOT(slotFinished(KJob*))); 0571 #ifdef APPLE_CHANGES 0572 qObject->connect(job, SIGNAL(data(KIO::Job*,const char*,int)), 0573 SLOT(slotData(KIO::Job*,const char*,int))); 0574 #else 0575 qObject->connect(job, SIGNAL(data(KIO::Job*,QByteArray)), 0576 SLOT(slotData(KIO::Job*,QByteArray))); 0577 #endif 0578 qObject->connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), 0579 SLOT(slotRedirection(KIO::Job*,QUrl))); 0580 0581 #ifdef APPLE_CHANGES 0582 KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job); 0583 #else 0584 KIO::Scheduler::setJobPriority(job, 1); 0585 #endif 0586 } 0587 0588 void XMLHttpRequest::clearDecoder() 0589 { 0590 delete decoder; 0591 decoder = nullptr; 0592 binaryMode = false; 0593 } 0594 0595 void XMLHttpRequest::abort() 0596 { 0597 if (job) { 0598 job->kill(); 0599 job = nullptr; 0600 } 0601 aborted = true; 0602 clearDecoder(); 0603 changeState(XHRS_Uninitialized); 0604 } 0605 0606 void XMLHttpRequest::overrideMIMEType(const QString &override) 0607 { 0608 m_mimeTypeOverride = override; 0609 } 0610 0611 void XMLHttpRequest::setRequestHeader(const QString &_name, const QString &_value, int &ec) 0612 { 0613 // throw exception if connection is not open or the send flag is set 0614 if (m_state != XHRS_Open || job != nullptr) { 0615 ec = DOMException::INVALID_STATE_ERR; 0616 return; 0617 } 0618 0619 if (!isValidFieldName(_name) || !isValidFieldValue(_value)) { 0620 ec = DOMException::SYNTAX_ERR; 0621 return; 0622 } 0623 0624 QString value = _value.trimmed(); 0625 0626 // Content-type needs to be set separately from the other headers 0627 if (_name.compare(QLatin1String("content-type"), Qt::CaseInsensitive) == 0) { 0628 contentType = "Content-type: " + value; 0629 return; 0630 } 0631 0632 // Reject all banned headers. 0633 if (!canSetRequestHeader(_name)) { 0634 qCWarning(KHTML_LOG) << "Refusing to set unsafe XMLHttpRequest header" << _name; 0635 return; 0636 } 0637 0638 if (m_requestHeaders.contains(_name)) { 0639 m_requestHeaders[_name] += (QLatin1String(", ") + value); 0640 } else { 0641 m_requestHeaders[_name] = value; 0642 } 0643 } 0644 0645 JSValue *XMLHttpRequest::getAllResponseHeaders(int &ec) const 0646 { 0647 if (m_state < XHRS_Receiving) { 0648 ec = DOMException::INVALID_STATE_ERR; 0649 return jsString(""); 0650 } 0651 0652 // ### test error flag, return jsNull 0653 0654 if (responseHeaders.isEmpty()) { 0655 return jsUndefined(); 0656 } 0657 0658 int endOfLine = responseHeaders.indexOf("\n"); 0659 0660 if (endOfLine == -1) { 0661 return jsUndefined(); 0662 } 0663 0664 return jsString(responseHeaders.mid(endOfLine + 1) + '\n'); 0665 } 0666 0667 JSValue *XMLHttpRequest::getResponseHeader(const QString &name, int &ec) const 0668 { 0669 if (m_state < XHRS_Receiving) { 0670 ec = DOMException::INVALID_STATE_ERR; 0671 return jsString(""); 0672 } 0673 0674 if (!isValidFieldName(name)) { 0675 return jsString(""); 0676 } 0677 0678 // ### test error flag, return jsNull 0679 0680 if (responseHeaders.isEmpty()) { 0681 return jsUndefined(); 0682 } 0683 0684 QRegExp headerLinePattern(name + ':', Qt::CaseInsensitive); 0685 0686 int matchLength; 0687 int headerLinePos = headerLinePattern.indexIn(responseHeaders, 0); 0688 matchLength = headerLinePattern.matchedLength(); 0689 while (headerLinePos != -1) { 0690 if (headerLinePos == 0 || responseHeaders[headerLinePos - 1] == '\n') { 0691 break; 0692 } 0693 0694 headerLinePos = headerLinePattern.indexIn(responseHeaders, headerLinePos + 1); 0695 matchLength = headerLinePattern.matchedLength(); 0696 } 0697 0698 if (headerLinePos == -1) { 0699 return jsNull(); 0700 } 0701 0702 int endOfLine = responseHeaders.indexOf("\n", headerLinePos + matchLength); 0703 0704 return jsString(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).trimmed()); 0705 } 0706 0707 static JSValue *httpStatus(const QString &response, bool textStatus = false) 0708 { 0709 if (response.isEmpty()) { 0710 return jsUndefined(); 0711 } 0712 0713 int endOfLine = response.indexOf("\n"); 0714 QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine); 0715 int codeStart = firstLine.indexOf(" "); 0716 int codeEnd = firstLine.indexOf(" ", codeStart + 1); 0717 0718 if (codeStart == -1 || codeEnd == -1) { 0719 return jsUndefined(); 0720 } 0721 0722 if (textStatus) { 0723 QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).trimmed(); 0724 return jsString(statusText); 0725 } 0726 0727 QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1)); 0728 0729 bool ok = false; 0730 int code = number.toInt(&ok); 0731 if (!ok) { 0732 return jsUndefined(); 0733 } 0734 0735 return jsNumber(code); 0736 } 0737 0738 JSValue *XMLHttpRequest::getStatus() const 0739 { 0740 return httpStatus(responseHeaders); 0741 } 0742 0743 JSValue *XMLHttpRequest::getStatusText() const 0744 { 0745 return httpStatus(responseHeaders, true); 0746 } 0747 0748 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const QUrl &finalURL, const QString &headers) 0749 { 0750 if (!urlMatchesDocumentDomain(finalURL)) { 0751 abort(); 0752 return; 0753 } 0754 0755 responseHeaders = headers; 0756 changeState(XHRS_Sent); 0757 if (aborted) { 0758 return; 0759 } 0760 0761 #ifdef APPLE_CHANGES 0762 const char *bytes = (const char *)data.data(); 0763 int len = (int)data.size(); 0764 0765 slotData(0, bytes, len); 0766 #else 0767 slotData(nullptr, data); 0768 #endif 0769 0770 if (aborted) { 0771 return; 0772 } 0773 0774 slotFinished(nullptr); 0775 } 0776 0777 void XMLHttpRequest::slotFinished(KJob *) 0778 { 0779 if (decoder) { 0780 response += decoder->flush(); 0781 } 0782 0783 // make sure to forget about the job before emitting completed, 0784 // since changeState triggers JS code, which might e.g. call abort. 0785 job = nullptr; 0786 changeState(XHRS_Loaded); 0787 0788 clearDecoder(); 0789 } 0790 0791 void XMLHttpRequest::slotRedirection(KIO::Job *, const QUrl &url) 0792 { 0793 if (!urlMatchesDocumentDomain(url)) { 0794 abort(); 0795 } 0796 } 0797 0798 static QString encodingFromContentType(const QString &type) 0799 { 0800 QString encoding; 0801 int index = type.indexOf(';'); 0802 if (index > -1) { 0803 encoding = type.mid(index + 1).remove(QRegExp("charset[ ]*=[ ]*", Qt::CaseInsensitive)).trimmed(); 0804 } 0805 return encoding; 0806 } 0807 0808 #ifdef APPLE_CHANGES 0809 void XMLHttpRequest::slotData(KIO::Job *, const char *data, int len) 0810 #else 0811 void XMLHttpRequest::slotData(KIO::Job *, const QByteArray &_data) 0812 #endif 0813 { 0814 if (m_state < XHRS_Sent) { 0815 responseHeaders = job->queryMetaData("HTTP-Headers"); 0816 0817 // NOTE: Replace a 304 response with a 200! Both IE and Mozilla do this. 0818 // Problem first reported through bug# 110272. 0819 int codeStart = responseHeaders.indexOf("304"); 0820 if (codeStart != -1) { 0821 int codeEnd = responseHeaders.indexOf("\n", codeStart + 3); 0822 if (codeEnd != -1) { 0823 responseHeaders.replace(codeStart, (codeEnd - codeStart), "200 OK"); 0824 } 0825 } 0826 0827 changeState(XHRS_Sent); 0828 } 0829 0830 #ifndef APPLE_CHANGES 0831 const char *data = (const char *)_data.data(); 0832 int len = (int)_data.size(); 0833 #endif 0834 0835 if (!decoder && !binaryMode) { 0836 if (!m_mimeTypeOverride.isEmpty()) { 0837 encoding = encodingFromContentType(m_mimeTypeOverride); 0838 } 0839 0840 if (encoding.isEmpty()) { 0841 int pos = responseHeaders.indexOf(QLatin1String("content-type:"), 0, Qt::CaseInsensitive); 0842 if (pos > -1) { 0843 pos += 13; 0844 int index = responseHeaders.indexOf('\n', pos); 0845 QString type = responseHeaders.mid(pos, (index - pos)); 0846 encoding = encodingFromContentType(type); 0847 } 0848 } 0849 0850 if (encoding == QLatin1String("x-user-defined")) { 0851 binaryMode = true; 0852 } else { 0853 decoder = new KEncodingDetector; 0854 if (!encoding.isEmpty()) { 0855 decoder->setEncoding(encoding.toLatin1().constData(), KEncodingDetector::EncodingFromHTTPHeader); 0856 } else { 0857 decoder->setEncoding("UTF-8", KEncodingDetector::DefaultEncoding); 0858 } 0859 } 0860 } 0861 0862 if (len == 0) { 0863 return; 0864 } 0865 0866 if (len == -1) { 0867 len = strlen(data); 0868 } 0869 0870 QString decoded; 0871 if (binaryMode) { 0872 decoded = QString::fromLatin1(data, len); 0873 } else { 0874 decoded = decoder->decodeWithBuffering(data, len); 0875 } 0876 0877 response += decoded; 0878 0879 if (!aborted) { 0880 changeState(XHRS_Receiving); 0881 } 0882 } 0883 0884 JSValue *XMLHttpRequestProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) 0885 { 0886 if (!thisObj->inherits(&XMLHttpRequest::info)) { 0887 return throwError(exec, TypeError); 0888 } 0889 0890 XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj); 0891 0892 if (!request->doc) { 0893 setDOMException(exec, DOMException::INVALID_STATE_ERR); 0894 return jsUndefined(); 0895 } 0896 0897 int ec = 0; 0898 0899 switch (id) { 0900 case XMLHttpRequest::Abort: 0901 request->abort(); 0902 return jsUndefined(); 0903 case XMLHttpRequest::GetAllResponseHeaders: { 0904 JSValue *ret = request->getAllResponseHeaders(ec); 0905 setDOMException(exec, ec); 0906 return ret; 0907 } 0908 case XMLHttpRequest::GetResponseHeader: { 0909 if (args.size() < 1) { 0910 return throwError(exec, SyntaxError, "Not enough arguments"); 0911 } 0912 JSValue *ret = request->getResponseHeader(args[0]->toString(exec).qstring(), ec); 0913 setDOMException(exec, ec); 0914 return ret; 0915 } 0916 case XMLHttpRequest::Open: { 0917 if (args.size() < 2) { 0918 return throwError(exec, SyntaxError, "Not enough arguments"); 0919 } 0920 0921 QString method = args[0]->toString(exec).qstring(); 0922 DOMString urlArg = args[1]->toString(exec).domString().trimSpaces(); 0923 QUrl url = QUrl(request->doc->completeURL(urlArg.string())); 0924 0925 bool async = true; 0926 if (args.size() >= 3) { 0927 async = args[2]->toBoolean(exec); 0928 } 0929 0930 // Set url userinfo 0931 if (args.size() >= 4 && !args[3]->isUndefinedOrNull()) { 0932 QString user = args[3]->toString(exec).qstring(); 0933 if (!user.isEmpty()) { 0934 url.setUserName(user); 0935 if (args.size() >= 5 && !args[4]->isUndefinedOrNull()) { 0936 url.setPassword(args[4]->toString(exec).qstring()); 0937 } 0938 } 0939 } 0940 0941 request->open(method, url, async, ec); 0942 setDOMException(exec, ec); 0943 return jsUndefined(); 0944 } 0945 case XMLHttpRequest::Send: { 0946 QString body; 0947 if (!args[0]->isUndefinedOrNull() 0948 // make sure we don't marshal "undefined" or such; 0949 && request->m_method != QLatin1String("GET") 0950 && request->m_method != QLatin1String("HEAD")) { 0951 // ... or methods that don't have payload 0952 0953 DOM::NodeImpl *docNode = toNode(args[0]); 0954 if (docNode && docNode->isDocumentNode()) { 0955 DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode); 0956 body = doc->toString().string(); 0957 // FIXME: also need to set content type, including encoding! 0958 } else { 0959 body = args[0]->toString(exec).qstring(); 0960 } 0961 } 0962 0963 request->send(body, ec); 0964 setDOMException(exec, ec); 0965 return jsUndefined(); 0966 } 0967 case XMLHttpRequest::SetRequestHeader: { 0968 if (args.size() < 2) { 0969 return throwError(exec, SyntaxError, "Not enough arguments"); 0970 } 0971 0972 QString headerFieldName = args[0]->toString(exec).qstring(); 0973 QString headerFieldValue = args[1]->toString(exec).qstring(); 0974 request->setRequestHeader(headerFieldName, headerFieldValue, ec); 0975 setDOMException(exec, ec); 0976 return jsUndefined(); 0977 } 0978 0979 case XMLHttpRequest::OverrideMIMEType: 0980 if (args.size() < 1) { 0981 return throwError(exec, SyntaxError, "Not enough arguments"); 0982 } 0983 0984 request->overrideMIMEType(args[0]->toString(exec).qstring()); 0985 return jsUndefined(); 0986 } 0987 0988 return jsUndefined(); 0989 } 0990 0991 } // end namespace 0992 0993 #include "moc_xmlhttprequest.cpp"