KHtml

xmlhttprequest.cpp
1 /*
2  * This file is part of the KDE libraries
3  * Copyright (C) 2003 Apple Computer, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include "xmlhttprequest.h"
21 #include "xmlhttprequest.lut.h"
22 #include "kjs_window.h"
23 #include "kjs_events.h"
24 
25 #include "dom/dom_doc.h"
26 #include "dom/dom_exception.h"
27 #include "dom/dom_string.h"
28 #include "misc/loader.h"
29 #include "misc/translator.h"
30 #include "html/html_documentimpl.h"
31 #include "xml/dom2_eventsimpl.h"
32 
33 #include "khtml_part.h"
34 #include "khtmlview.h"
35 
36 #include <kio/scheduler.h>
37 #include <kio/job.h>
38 #include <QObject>
39 #include "khtml_debug.h"
40 
41 using namespace KJS;
42 using namespace DOM;
43 //
44 ////////////////////// XMLHttpRequest Object ////////////////////////
45 
46 /* Source for XMLHttpRequestProtoTable.
47 @begin XMLHttpRequestProtoTable 7
48  abort XMLHttpRequest::Abort DontDelete|Function 0
49  getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders DontDelete|Function 0
50  getResponseHeader XMLHttpRequest::GetResponseHeader DontDelete|Function 1
51  open XMLHttpRequest::Open DontDelete|Function 5
52  overrideMimeType XMLHttpRequest::OverrideMIMEType DontDelete|Function 1
53  send XMLHttpRequest::Send DontDelete|Function 1
54  setRequestHeader XMLHttpRequest::SetRequestHeader DontDelete|Function 2
55 @end
56 */
57 
58 namespace KJS
59 {
60 
61 KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto)
62 KJS_IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
63 KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest", XMLHttpRequestProto, XMLHttpRequestProtoFunc, ObjectPrototype)
64 
65 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
66 {
67  jsObject = _jsObject;
68 }
69 
70 #ifdef APPLE_CHANGES
71 void XMLHttpRequestQObject::slotData(KIO::Job *job, const char *data, int size)
72 {
73  jsObject->slotData(job, data, size);
74 }
75 #else
76 void XMLHttpRequestQObject::slotData(KIO::Job *job, const QByteArray &data)
77 {
78  jsObject->slotData(job, data);
79 }
80 #endif
81 
82 void XMLHttpRequestQObject::slotFinished(KJob *job)
83 {
84  jsObject->slotFinished(job);
85 }
86 
87 void XMLHttpRequestQObject::slotRedirection(KIO::Job *job, const QUrl &url)
88 {
89  jsObject->slotRedirection(job, url);
90 }
91 
92 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *exec, DOM::DocumentImpl *d)
93  : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d)
94 {
95  JSObject *proto = XMLHttpRequestProto::self(exec);
96  putDirect(exec->propertyNames().prototype, proto, DontDelete | ReadOnly);
97 }
98 
99 bool XMLHttpRequestConstructorImp::implementsConstruct() const
100 {
101  return true;
102 }
103 
104 JSObject *XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
105 {
106  return new XMLHttpRequest(exec, doc.get());
107 }
108 
109 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", nullptr, &XMLHttpRequestTable, nullptr };
110 
111 /* Source for XMLHttpRequestTable.
112 @begin XMLHttpRequestTable 7
113  readyState XMLHttpRequest::ReadyState DontDelete|ReadOnly
114  responseText XMLHttpRequest::ResponseText DontDelete|ReadOnly
115  responseXML XMLHttpRequest::ResponseXML DontDelete|ReadOnly
116  status XMLHttpRequest::Status DontDelete|ReadOnly
117  statusText XMLHttpRequest::StatusText DontDelete|ReadOnly
118  onreadystatechange XMLHttpRequest::Onreadystatechange DontDelete
119  onload XMLHttpRequest::Onload DontDelete
120 @end
121 */
122 
123 bool XMLHttpRequest::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
124 {
125  return getStaticValueSlot<XMLHttpRequest, DOMObject>(exec, &XMLHttpRequestTable, this, propertyName, slot);
126 }
127 
128 JSValue *XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
129 {
130  switch (token) {
131  case ReadyState:
132  return jsNumber(m_state);
133  case ResponseText:
134  return ::getStringOrNull(DOM::DOMString(response));
135  case ResponseXML:
136  if (m_state != XHRS_Loaded) {
137  return jsNull();
138  }
139  if (!createdDocument) {
140  QString mimeType = "text/xml";
141 
142  if (!m_mimeTypeOverride.isEmpty()) {
143  mimeType = m_mimeTypeOverride;
144  } else {
145  int dummy;
146  JSValue *header = getResponseHeader("Content-Type", dummy);
147  if (!header->isUndefinedOrNull()) {
148  mimeType = header->toString(exec).qstring().split(";")[0].trimmed();
149  }
150  }
151 
152  if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
153  responseXML = doc->implementation()->createDocument();
154 
155  responseXML->open();
156  responseXML->setURL(url.url());
157  responseXML->write(response);
158  responseXML->finishParsing();
159  responseXML->close();
160 
161  typeIsXML = true;
162  } else {
163  typeIsXML = false;
164  }
165  createdDocument = true;
166  }
167 
168  if (!typeIsXML) {
169  return jsNull();
170  }
171 
172  return getDOMNode(exec, responseXML.get());
173  case Status:
174  return getStatus();
175  case StatusText:
176  return getStatusText();
177  case Onreadystatechange:
178  if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObj()) {
179  return onReadyStateChangeListener->listenerObj();
180  } else {
181  return jsNull();
182  }
183  case Onload:
184  if (onLoadListener && onLoadListener->listenerObj()) {
185  return onLoadListener->listenerObj();
186  } else {
187  return jsNull();
188  }
189  default:
190  qCWarning(KHTML_LOG) << "XMLHttpRequest::getValueProperty unhandled token " << token;
191  return nullptr;
192  }
193 }
194 
195 void XMLHttpRequest::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
196 {
197  lookupPut<XMLHttpRequest, DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this);
198 }
199 
200 void XMLHttpRequest::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/)
201 {
202  switch (token) {
203  case Onreadystatechange:
204  if (onReadyStateChangeListener) {
205  onReadyStateChangeListener->deref();
206  }
207  onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
208  if (onReadyStateChangeListener) {
209  onReadyStateChangeListener->ref();
210  }
211  break;
212  case Onload:
213  if (onLoadListener) {
214  onLoadListener->deref();
215  }
216  onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
217  if (onLoadListener) {
218  onLoadListener->ref();
219  }
220  break;
221  default:
222  qCWarning(KHTML_LOG) << "XMLHttpRequest::putValue unhandled token " << token;
223  }
224 }
225 
226 // Token according to RFC 2616
227 static bool isValidFieldName(const QString &name)
228 {
229  const int l = name.length();
230  if (l == 0) {
231  return false;
232  }
233 
234  const QChar *c = name.constData();
235  for (int i = 0; i < l; ++i, ++c) {
236  ushort u = c->unicode();
237  if (u < 32 || u > 126) {
238  return false;
239  }
240  switch (u) {
241  case '(': case ')': case '<': case '>':
242  case '@': case ',': case ';': case ':':
243  case '\\': case '"': case '/':
244  case '[': case ']': case '?': case '=':
245  case '{': case '}': case '\t': case ' ':
246  return false;
247  default:
248  break;
249  }
250  }
251  return true;
252 }
253 
254 static bool isValidFieldValue(const QString &name)
255 {
256  const int l = name.length();
257  if (l == 0) {
258  return true;
259  }
260 
261  const QChar *c = name.constData();
262  for (int i = 0; i < l; ++i, ++c) {
263  ushort u = c->unicode();
264  if (u == '\n' || u == '\r') {
265  return false;
266  }
267  }
268 
269  // ### what is invalid?
270  return true;
271 }
272 
273 static bool canSetRequestHeader(const QString &name)
274 {
275  if (name.startsWith(QLatin1String("sec-"), Qt::CaseInsensitive) ||
276  name.startsWith(QLatin1String("proxy-"), Qt::CaseInsensitive)) {
277  return false;
278  }
279 
280  static QSet<CaseInsensitiveString> forbiddenHeaders;
281  if (forbiddenHeaders.isEmpty()) {
282  static const char *const hdrs[] = {
283  "accept-charset",
284  "accept-encoding",
285  "access-control-request-headers",
286  "access-control-request-method",
287  "connection",
288  "content-length",
289  "content-transfer-encoding",
290  "cookie",
291  "cookie2",
292  "date",
293  "dnt",
294  "expect",
295  "host",
296  "keep-alive",
297  "origin",
298  "referer",
299  "te",
300  "trailer",
301  "transfer-encoding",
302  "upgrade",
303  "user-agent",
304  "via"
305  };
306  for (size_t i = 0; i < sizeof(hdrs) / sizeof(char *); ++i) {
307  forbiddenHeaders.insert(CaseInsensitiveString(hdrs[i]));
308  }
309  }
310 
311  return !forbiddenHeaders.contains(name);
312 }
313 
314 XMLHttpRequest::XMLHttpRequest(ExecState *exec, DOM::DocumentImpl *d)
315  : qObject(new XMLHttpRequestQObject(this)),
316  doc(d),
317  async(true),
318  contentType(QString()),
319  job(nullptr),
320  m_state(XHRS_Uninitialized),
321  onReadyStateChangeListener(nullptr),
322  onLoadListener(nullptr),
323  decoder(nullptr),
324  binaryMode(false),
325  response(QString::fromLatin1("")),
326  createdDocument(false),
327  aborted(false)
328 {
329  ref(); // we're a GC point, so refcount pin.
330  setPrototype(XMLHttpRequestProto::self(exec));
331 }
332 
333 XMLHttpRequest::~XMLHttpRequest()
334 {
335  if (job && m_method != QLatin1String("POST")) {
336  job->kill();
337  job = nullptr;
338  }
339  if (onLoadListener) {
340  onLoadListener->deref();
341  }
342  if (onReadyStateChangeListener) {
343  onReadyStateChangeListener->deref();
344  }
345  delete qObject;
346  qObject = nullptr;
347  delete decoder;
348  decoder = nullptr;
349 }
350 
351 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
352 {
353  // Other engines cancel transfer if the controlling document doesn't
354  // exist anymore. Match that, though being paranoid about post
355  // (And don't emit any events w/o a doc, if we're kept alive otherwise).
356  if (!doc) {
357  if (job && m_method != QLatin1String("POST")) {
358  job->kill();
359  job = nullptr;
360  }
361  return;
362  }
363 
364  if (m_state != newState) {
365  m_state = newState;
366  ProtectedPtr<JSObject> ref(this);
367 
368  if (onReadyStateChangeListener != nullptr && doc->view() && doc->view()->part()) {
369  DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
370  ev.initEvent("readystatechange", true, true);
371  ev.handle()->setTarget(this);
372  ev.handle()->setCurrentTarget(this);
373  onReadyStateChangeListener->handleEvent(ev);
374 
375  // Make sure the event doesn't point to us, since it can't prevent
376  // us from being collecte.
377  ev.handle()->setTarget(nullptr);
378  ev.handle()->setCurrentTarget(nullptr);
379  }
380 
381  if (m_state == XHRS_Loaded && onLoadListener != nullptr && doc->view() && doc->view()->part()) {
382  DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
383  ev.initEvent("load", true, true);
384  ev.handle()->setTarget(this);
385  ev.handle()->setCurrentTarget(this);
386  onLoadListener->handleEvent(ev);
387  ev.handle()->setTarget(nullptr);
388  ev.handle()->setCurrentTarget(nullptr);
389  }
390  }
391 }
392 
393 bool XMLHttpRequest::urlMatchesDocumentDomain(const QUrl &_url) const
394 {
395  // No need to do work if _url is not valid...
396  if (!_url.isValid()) {
397  return false;
398  }
399 
400  QUrl documentURL(doc->URL());
401 
402  // a local file can load anything
403  if (documentURL.isLocalFile()) {
404  return true;
405  }
406 
407  // but a remote document can only load from the same port on the server
408  if (documentURL.scheme() == _url.scheme() &&
409  documentURL.host().toLower() == _url.host().toLower() &&
410  documentURL.port() == _url.port()) {
411  return true;
412  }
413 
414  return false;
415 }
416 
417 // Methods we're to recognize per the XHR spec (3.6.1, #3).
418 // We map it to whether the method should be permitted or not (#4)
419 static const IDTranslator<QByteArray, bool, const char *>::Info methodsTable[] = {
420  {"CONNECT", false},
421  {"DELETE", true},
422  {"GET", true},
423  {"HEAD", true},
424  {"OPTIONS", true},
425  {"POST", true},
426  {"PUT", true},
427  {"TRACE", false},
428  {"TRACK", false},
429  {nullptr, false}
430 };
431 
432 MAKE_TRANSLATOR(methodsLookup, QByteArray, bool, const char *, methodsTable)
433 
434 void XMLHttpRequest::open(const QString &_method, const QUrl &_url, bool _async, int &ec)
435 {
436  abort();
437  aborted = false;
438 
439  // clear stuff from possible previous load
440  m_requestHeaders.clear();
441  responseHeaders.clear();
442  response = QString::fromLatin1("");
443  createdDocument = false;
444  responseXML = nullptr;
445 
446  if (!urlMatchesDocumentDomain(_url)) {
447  ec = DOMException::SECURITY_ERR;
448  return;
449  }
450 
451  // ### potentially raise a SYNTAX_ERR
452 
453  // Lookup if the method is well-known, and if so check if it's OK
454  QByteArray methodNormalized = _method.toUpper().toUtf8();
455  if (methodsLookup()->hasLeft(methodNormalized)) {
456  if (methodsLookup()->toRight(methodNormalized)) {
457  // OK, replace with the canonical version...
458  m_method = _method.toUpper();
459  } else {
460  // Scary stuff like CONNECT
461  ec = DOMException::SECURITY_ERR;
462  return;
463  }
464  } else {
465  // Unknown -> pass through unchanged
466  m_method = _method;
467  }
468 
469  url = _url;
470  async = _async;
471 
472  changeState(XHRS_Open);
473 }
474 
475 void XMLHttpRequest::send(const QString &_body, int &ec)
476 {
477  aborted = false;
478 
479  if (m_state != XHRS_Open) {
480  ec = DOMException::INVALID_STATE_ERR;
481  return;
482  }
483 
484  const QString protocol = url.scheme();
485  // Abandon the request when the protocol is other than "http",
486  // instead of blindly doing a KIO::get on other protocols like file:/.
487  if (!protocol.startsWith(QLatin1String("http")) &&
488  !protocol.startsWith(QLatin1String("webdav"))) {
489  ec = DOMException::INVALID_ACCESS_ERR;
490  abort();
491  return;
492  }
493 
494  // We need to use a POST-like setup even for non-post whenever we
495  // have a payload.
496  const bool havePayload = !_body.isEmpty();
497  if (m_method == QLatin1String("POST") || havePayload) {
498  // FIXME: determine post encoding correctly by looking in headers
499  // for charset.
500  QByteArray buf = _body.toUtf8();
501  job = KIO::storedHttpPost(buf, url, KIO::HideProgressInfo);
502  } else {
503  job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo);
504  }
505 
506  // Regardless of job type, make sure the method is set
507  job->addMetaData("CustomHTTPMethod", m_method);
508 
509  // Set headers
510 
511  if (!contentType.isNull()) {
512  job->addMetaData("content-type", contentType);
513  } else if (havePayload) {
514  job->addMetaData("content-type", "Content-type: text/plain");
515  }
516 
517  if (!m_requestHeaders.isEmpty()) {
518  QString rh;
519  HTTPHeaderMap::ConstIterator begin = m_requestHeaders.constBegin();
520  HTTPHeaderMap::ConstIterator end = m_requestHeaders.constEnd();
521  for (HTTPHeaderMap::ConstIterator i = begin; i != end; ++i) {
522  QString key = i.key().original();
523  QString value = i.value();
524  if (key.toLower() == "accept") {
525  // The HTTP KIO slave supports an override this way
526  job->addMetaData("accept", value);
527  } else {
528  if (!rh.isEmpty()) {
529  rh += "\r\n";
530  }
531  rh += key + ": " + value;
532  }
533  }
534 
535  job->addMetaData("customHTTPHeader", rh);
536  }
537 
538  job->addMetaData("PropagateHttpHeader", "true");
539 
540  // Set the default referrer. NOTE: the user can still disable
541  // this feature at the protocol level (kio_http).
542  QUrl documentURL(doc->URL());
543  documentURL.setPassword(QString());
544  documentURL.setUserName(QString());
545  job->addMetaData("referrer", documentURL.url());
546  // qCDebug(KHTML_LOG) << "Adding referrer: " << documentURL;
547 
548  if (!async) {
549  QByteArray data;
550  QUrl finalURL;
551  QString headers;
552 
553 #ifdef APPLE_CHANGES
554  data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
555 #else
556  QMap<QString, QString> metaData;
557 
558  if (job->exec()) {
559  data = job->data();
560  finalURL = job->redirectUrl().isEmpty() ? job->url() : job->redirectUrl();
561  headers = metaData[ "HTTP-Headers" ];
562  }
563 #endif
564  job = nullptr;
565  processSyncLoadResults(data, finalURL, headers);
566  return;
567  }
568 
569  qObject->connect(job, SIGNAL(result(KJob*)),
570  SLOT(slotFinished(KJob*)));
571 #ifdef APPLE_CHANGES
572  qObject->connect(job, SIGNAL(data(KIO::Job*,const char*,int)),
573  SLOT(slotData(KIO::Job*,const char*,int)));
574 #else
575  qObject->connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
576  SLOT(slotData(KIO::Job*,QByteArray)));
577 #endif
578  qObject->connect(job, SIGNAL(redirection(KIO::Job*,QUrl)),
579  SLOT(slotRedirection(KIO::Job*,QUrl)));
580 
581 #ifdef APPLE_CHANGES
582  KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
583 #else
585 #endif
586 }
587 
588 void XMLHttpRequest::clearDecoder()
589 {
590  delete decoder;
591  decoder = nullptr;
592  binaryMode = false;
593 }
594 
595 void XMLHttpRequest::abort()
596 {
597  if (job) {
598  job->kill();
599  job = nullptr;
600  }
601  aborted = true;
602  clearDecoder();
603  changeState(XHRS_Uninitialized);
604 }
605 
606 void XMLHttpRequest::overrideMIMEType(const QString &override)
607 {
608  m_mimeTypeOverride = override;
609 }
610 
611 void XMLHttpRequest::setRequestHeader(const QString &_name, const QString &_value, int &ec)
612 {
613  // throw exception if connection is not open or the send flag is set
614  if (m_state != XHRS_Open || job != nullptr) {
615  ec = DOMException::INVALID_STATE_ERR;
616  return;
617  }
618 
619  if (!isValidFieldName(_name) || !isValidFieldValue(_value)) {
620  ec = DOMException::SYNTAX_ERR;
621  return;
622  }
623 
624  QString value = _value.trimmed();
625 
626  // Content-type needs to be set separately from the other headers
627  if (_name.compare(QLatin1String("content-type"), Qt::CaseInsensitive) == 0) {
628  contentType = "Content-type: " + value;
629  return;
630  }
631 
632  // Reject all banned headers.
633  if (!canSetRequestHeader(_name)) {
634  qCWarning(KHTML_LOG) << "Refusing to set unsafe XMLHttpRequest header" << _name;
635  return;
636  }
637 
638  if (m_requestHeaders.contains(_name)) {
639  m_requestHeaders[_name] += (QLatin1String(", ") + value);
640  } else {
641  m_requestHeaders[_name] = value;
642  }
643 }
644 
645 JSValue *XMLHttpRequest::getAllResponseHeaders(int &ec) const
646 {
647  if (m_state < XHRS_Receiving) {
648  ec = DOMException::INVALID_STATE_ERR;
649  return jsString("");
650  }
651 
652  // ### test error flag, return jsNull
653 
654  if (responseHeaders.isEmpty()) {
655  return jsUndefined();
656  }
657 
658  int endOfLine = responseHeaders.indexOf("\n");
659 
660  if (endOfLine == -1) {
661  return jsUndefined();
662  }
663 
664  return jsString(responseHeaders.mid(endOfLine + 1) + '\n');
665 }
666 
667 JSValue *XMLHttpRequest::getResponseHeader(const QString &name, int &ec) const
668 {
669  if (m_state < XHRS_Receiving) {
670  ec = DOMException::INVALID_STATE_ERR;
671  return jsString("");
672  }
673 
674  if (!isValidFieldName(name)) {
675  return jsString("");
676  }
677 
678  // ### test error flag, return jsNull
679 
680  if (responseHeaders.isEmpty()) {
681  return jsUndefined();
682  }
683 
684  QRegExp headerLinePattern(name + ':', Qt::CaseInsensitive);
685 
686  int matchLength;
687  int headerLinePos = headerLinePattern.indexIn(responseHeaders, 0);
688  matchLength = headerLinePattern.matchedLength();
689  while (headerLinePos != -1) {
690  if (headerLinePos == 0 || responseHeaders[headerLinePos - 1] == '\n') {
691  break;
692  }
693 
694  headerLinePos = headerLinePattern.indexIn(responseHeaders, headerLinePos + 1);
695  matchLength = headerLinePattern.matchedLength();
696  }
697 
698  if (headerLinePos == -1) {
699  return jsNull();
700  }
701 
702  int endOfLine = responseHeaders.indexOf("\n", headerLinePos + matchLength);
703 
704  return jsString(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).trimmed());
705 }
706 
707 static JSValue *httpStatus(const QString &response, bool textStatus = false)
708 {
709  if (response.isEmpty()) {
710  return jsUndefined();
711  }
712 
713  int endOfLine = response.indexOf("\n");
714  QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine);
715  int codeStart = firstLine.indexOf(" ");
716  int codeEnd = firstLine.indexOf(" ", codeStart + 1);
717 
718  if (codeStart == -1 || codeEnd == -1) {
719  return jsUndefined();
720  }
721 
722  if (textStatus) {
723  QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).trimmed();
724  return jsString(statusText);
725  }
726 
727  QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
728 
729  bool ok = false;
730  int code = number.toInt(&ok);
731  if (!ok) {
732  return jsUndefined();
733  }
734 
735  return jsNumber(code);
736 }
737 
738 JSValue *XMLHttpRequest::getStatus() const
739 {
740  return httpStatus(responseHeaders);
741 }
742 
743 JSValue *XMLHttpRequest::getStatusText() const
744 {
745  return httpStatus(responseHeaders, true);
746 }
747 
748 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const QUrl &finalURL, const QString &headers)
749 {
750  if (!urlMatchesDocumentDomain(finalURL)) {
751  abort();
752  return;
753  }
754 
755  responseHeaders = headers;
756  changeState(XHRS_Sent);
757  if (aborted) {
758  return;
759  }
760 
761 #ifdef APPLE_CHANGES
762  const char *bytes = (const char *)data.data();
763  int len = (int)data.size();
764 
765  slotData(0, bytes, len);
766 #else
767  slotData(nullptr, data);
768 #endif
769 
770  if (aborted) {
771  return;
772  }
773 
774  slotFinished(nullptr);
775 }
776 
777 void XMLHttpRequest::slotFinished(KJob *)
778 {
779  if (decoder) {
780  response += decoder->flush();
781  }
782 
783  // make sure to forget about the job before emitting completed,
784  // since changeState triggers JS code, which might e.g. call abort.
785  job = nullptr;
786  changeState(XHRS_Loaded);
787 
788  clearDecoder();
789 }
790 
791 void XMLHttpRequest::slotRedirection(KIO::Job *, const QUrl &url)
792 {
793  if (!urlMatchesDocumentDomain(url)) {
794  abort();
795  }
796 }
797 
798 static QString encodingFromContentType(const QString &type)
799 {
800  QString encoding;
801  int index = type.indexOf(';');
802  if (index > -1) {
803  encoding = type.mid(index + 1).remove(QRegExp("charset[ ]*=[ ]*", Qt::CaseInsensitive)).trimmed();
804  }
805  return encoding;
806 }
807 
808 #ifdef APPLE_CHANGES
809 void XMLHttpRequest::slotData(KIO::Job *, const char *data, int len)
810 #else
811 void XMLHttpRequest::slotData(KIO::Job *, const QByteArray &_data)
812 #endif
813 {
814  if (m_state < XHRS_Sent) {
815  responseHeaders = job->queryMetaData("HTTP-Headers");
816 
817  // NOTE: Replace a 304 response with a 200! Both IE and Mozilla do this.
818  // Problem first reported through bug# 110272.
819  int codeStart = responseHeaders.indexOf("304");
820  if (codeStart != -1) {
821  int codeEnd = responseHeaders.indexOf("\n", codeStart + 3);
822  if (codeEnd != -1) {
823  responseHeaders.replace(codeStart, (codeEnd - codeStart), "200 OK");
824  }
825  }
826 
827  changeState(XHRS_Sent);
828  }
829 
830 #ifndef APPLE_CHANGES
831  const char *data = (const char *)_data.data();
832  int len = (int)_data.size();
833 #endif
834 
835  if (!decoder && !binaryMode) {
836  if (!m_mimeTypeOverride.isEmpty()) {
837  encoding = encodingFromContentType(m_mimeTypeOverride);
838  }
839 
840  if (encoding.isEmpty()) {
841  int pos = responseHeaders.indexOf(QLatin1String("content-type:"), 0, Qt::CaseInsensitive);
842  if (pos > -1) {
843  pos += 13;
844  int index = responseHeaders.indexOf('\n', pos);
845  QString type = responseHeaders.mid(pos, (index - pos));
846  encoding = encodingFromContentType(type);
847  }
848  }
849 
850  if (encoding == QLatin1String("x-user-defined")) {
851  binaryMode = true;
852  } else {
853  decoder = new KEncodingDetector;
854  if (!encoding.isEmpty()) {
855  decoder->setEncoding(encoding.toLatin1().constData(), KEncodingDetector::EncodingFromHTTPHeader);
856  } else {
857  decoder->setEncoding("UTF-8", KEncodingDetector::DefaultEncoding);
858  }
859  }
860  }
861 
862  if (len == 0) {
863  return;
864  }
865 
866  if (len == -1) {
867  len = strlen(data);
868  }
869 
870  QString decoded;
871  if (binaryMode) {
872  decoded = QString::fromLatin1(data, len);
873  } else {
874  decoded = decoder->decodeWithBuffering(data, len);
875  }
876 
877  response += decoded;
878 
879  if (!aborted) {
880  changeState(XHRS_Receiving);
881  }
882 }
883 
884 JSValue *XMLHttpRequestProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
885 {
886  if (!thisObj->inherits(&XMLHttpRequest::info)) {
887  return throwError(exec, TypeError);
888  }
889 
890  XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj);
891 
892  if (!request->doc) {
893  setDOMException(exec, DOMException::INVALID_STATE_ERR);
894  return jsUndefined();
895  }
896 
897  int ec = 0;
898 
899  switch (id) {
900  case XMLHttpRequest::Abort:
901  request->abort();
902  return jsUndefined();
903  case XMLHttpRequest::GetAllResponseHeaders: {
904  JSValue *ret = request->getAllResponseHeaders(ec);
905  setDOMException(exec, ec);
906  return ret;
907  }
908  case XMLHttpRequest::GetResponseHeader: {
909  if (args.size() < 1) {
910  return throwError(exec, SyntaxError, "Not enough arguments");
911  }
912  JSValue *ret = request->getResponseHeader(args[0]->toString(exec).qstring(), ec);
913  setDOMException(exec, ec);
914  return ret;
915  }
916  case XMLHttpRequest::Open: {
917  if (args.size() < 2) {
918  return throwError(exec, SyntaxError, "Not enough arguments");
919  }
920 
921  QString method = args[0]->toString(exec).qstring();
922  DOMString urlArg = args[1]->toString(exec).domString().trimSpaces();
923  QUrl url = QUrl(request->doc->completeURL(urlArg.string()));
924 
925  bool async = true;
926  if (args.size() >= 3) {
927  async = args[2]->toBoolean(exec);
928  }
929 
930  // Set url userinfo
931  if (args.size() >= 4 && !args[3]->isUndefinedOrNull()) {
932  QString user = args[3]->toString(exec).qstring();
933  if (!user.isEmpty()) {
934  url.setUserName(user);
935  if (args.size() >= 5 && !args[4]->isUndefinedOrNull()) {
936  url.setPassword(args[4]->toString(exec).qstring());
937  }
938  }
939  }
940 
941  request->open(method, url, async, ec);
942  setDOMException(exec, ec);
943  return jsUndefined();
944  }
945  case XMLHttpRequest::Send: {
946  QString body;
947  if (!args[0]->isUndefinedOrNull()
948  // make sure we don't marshal "undefined" or such;
949  && request->m_method != QLatin1String("GET")
950  && request->m_method != QLatin1String("HEAD")) {
951  // ... or methods that don't have payload
952 
953  DOM::NodeImpl *docNode = toNode(args[0]);
954  if (docNode && docNode->isDocumentNode()) {
955  DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode);
956  body = doc->toString().string();
957  // FIXME: also need to set content type, including encoding!
958  } else {
959  body = args[0]->toString(exec).qstring();
960  }
961  }
962 
963  request->send(body, ec);
964  setDOMException(exec, ec);
965  return jsUndefined();
966  }
967  case XMLHttpRequest::SetRequestHeader: {
968  if (args.size() < 2) {
969  return throwError(exec, SyntaxError, "Not enough arguments");
970  }
971 
972  QString headerFieldName = args[0]->toString(exec).qstring();
973  QString headerFieldValue = args[1]->toString(exec).qstring();
974  request->setRequestHeader(headerFieldName, headerFieldValue, ec);
975  setDOMException(exec, ec);
976  return jsUndefined();
977  }
978 
979  case XMLHttpRequest::OverrideMIMEType:
980  if (args.size() < 1) {
981  return throwError(exec, SyntaxError, "Not enough arguments");
982  }
983 
984  request->overrideMIMEType(args[0]->toString(exec).qstring());
985  return jsUndefined();
986  }
987 
988  return jsUndefined();
989 }
990 
991 } // end namespace
992 
QString url(QUrl::FormattingOptions options) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
const QChar * constData() const const
void addMetaData(const QString &key, const QString &value)
Provides encoding detection capabilities.
static void setJobPriority(SimpleJob *job, int priority)
QString host(QUrl::ComponentFormattingOptions options) const const
QByteArray toUpper() const const
void setPassword(const QString &password, QUrl::ParsingMode mode)
QSet::iterator insert(const T &value)
QString & remove(int position, int n)
int port(int defaultPort) const const
QString queryMetaData(const QString &key)
void ref()
const QList< QKeySequence > & begin()
KIOCORE_EXPORT QString number(KIO::filesize_t size)
EventImpl * handle() const
This file is part of the KHTML renderer for KDE.
Definition: translator.h:31
CaseInsensitive
int toInt(bool *ok, int base) const const
bool isEmpty() const const
QString trimmed() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
KIOCORE_EXPORT StoredTransferJob * storedGet(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QString scheme() const const
ushort unicode() const const
void initEvent(const DOMString &eventTypeArg, bool canBubbleArg, bool cancelableArg)
The initEvent method is used to initialize the value of an Event created through the DocumentEvent in...
QString toLower() const const
void setUserName(const QString &userName, QUrl::ParsingMode mode)
const QList< QKeySequence > & endOfLine()
bool contains(const T &value) const const
bool setEncoding(const char *encoding, EncodingChoiceSource type)
This library provides a full-featured HTML parser and widget.
const QList< QKeySequence > & end()
bool isValid() const const
int size() const
QByteArray toLatin1() const const
Introduced in DOM Level 2.
Definition: dom2_events.h:116
QString mid(int position, int n) const const
char * toString(const T &value)
int length() const const
char * data()
bool isEmpty() const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
QString mimeType(Type)
int size() const const
KIOCORE_EXPORT StoredTransferJob * storedHttpPost(QIODevice *device, const QUrl &url, qint64 size=-1, JobFlags flags=DefaultFlags)
int compare(const QString &other, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:10 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.