File indexing completed on 2024-04-28 15:23:20

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"