xmlhttprequest.cpp 26.9 KB
Newer Older
George Staikos's avatar
George Staikos committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// -*- c-basic-offset: 2 -*-
/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 2003 Apple Computer, Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "xmlhttprequest.h"
#include "xmlhttprequest.lut.h"
#include "kjs_window.h"
#include "kjs_events.h"


#include "dom/dom_doc.h"
#include "dom/dom_exception.h"
#include "dom/dom_string.h"
#include "misc/loader.h"
31
#include "misc/translator.h"
George Staikos's avatar
George Staikos committed
32 33 34 35 36 37 38 39
#include "html/html_documentimpl.h"
#include "xml/dom2_eventsimpl.h"

#include "khtml_part.h"
#include "khtmlview.h"

#include <kio/scheduler.h>
#include <kio/job.h>
Dirk Mueller's avatar
Dirk Mueller committed
40
#include <QtCore/QObject>
41
#include <QtCore/QHash>
George Staikos's avatar
George Staikos committed
42 43 44 45 46 47
#include <kdebug.h>

#include <kio/netaccess.h>
using KIO::NetAccess;

using namespace KJS;
48
using namespace DOM;
49
//
George Staikos's avatar
George Staikos committed
50 51 52 53 54 55 56 57
////////////////////// XMLHttpRequest Object ////////////////////////

/* Source for XMLHttpRequestProtoTable.
@begin XMLHttpRequestProtoTable 7
  abort			XMLHttpRequest::Abort			DontDelete|Function 0
  getAllResponseHeaders	XMLHttpRequest::GetAllResponseHeaders	DontDelete|Function 0
  getResponseHeader	XMLHttpRequest::GetResponseHeader	DontDelete|Function 1
  open			XMLHttpRequest::Open			DontDelete|Function 5
58
  overrideMimeType	XMLHttpRequest::OverrideMIMEType	DontDelete|Function 1
George Staikos's avatar
George Staikos committed
59 60 61 62 63 64 65
  send			XMLHttpRequest::Send			DontDelete|Function 1
  setRequestHeader	XMLHttpRequest::SetRequestHeader	DontDelete|Function 2
@end
*/

namespace KJS {

66 67
KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto)
KJS_IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
Maks Orlovich's avatar
Maks Orlovich committed
68
KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest", XMLHttpRequestProto,XMLHttpRequestProtoFunc, ObjectPrototype)
George Staikos's avatar
George Staikos committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87


XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
{
  jsObject = _jsObject;
}

#ifdef APPLE_CHANGES
void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
{
  jsObject->slotData(job, data, size);
}
#else
void XMLHttpRequestQObject::slotData( KIO::Job* job, const QByteArray &data )
{
  jsObject->slotData(job, data);
}
#endif

88
void XMLHttpRequestQObject::slotFinished( KJob* job )
George Staikos's avatar
George Staikos committed
89 90 91 92
{
  jsObject->slotFinished(job);
}

93
void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KUrl& url)
George Staikos's avatar
George Staikos committed
94 95 96 97
{
  jsObject->slotRedirection( job, url );
}

98
XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *exec, DOM::DocumentImpl* d)
Maks Orlovich's avatar
Maks Orlovich committed
99
    : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d)
George Staikos's avatar
George Staikos committed
100
{
101 102
    JSObject* proto = XMLHttpRequestProto::self(exec);
    putDirect(exec->propertyNames().prototype, proto, DontDelete|ReadOnly);
George Staikos's avatar
George Staikos committed
103 104 105 106 107 108 109
}

bool XMLHttpRequestConstructorImp::implementsConstruct() const
{
  return true;
}

Harri Porten's avatar
Harri Porten committed
110
JSObject *XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
George Staikos's avatar
George Staikos committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
{
  return new XMLHttpRequest(exec, doc.get());
}

const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };


/* Source for XMLHttpRequestTable.
@begin XMLHttpRequestTable 7
  readyState		XMLHttpRequest::ReadyState		DontDelete|ReadOnly
  responseText		XMLHttpRequest::ResponseText		DontDelete|ReadOnly
  responseXML		XMLHttpRequest::ResponseXML		DontDelete|ReadOnly
  status		XMLHttpRequest::Status			DontDelete|ReadOnly
  statusText		XMLHttpRequest::StatusText		DontDelete|ReadOnly
  onreadystatechange	XMLHttpRequest::Onreadystatechange	DontDelete
  onload		XMLHttpRequest::Onload			DontDelete
@end
*/

bool XMLHttpRequest::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
{
  return getStaticValueSlot<XMLHttpRequest, DOMObject>(exec, &XMLHttpRequestTable, this, propertyName, slot);
}

Harri Porten's avatar
Harri Porten committed
135
JSValue *XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
George Staikos's avatar
George Staikos committed
136 137 138
{
  switch (token) {
  case ReadyState:
Harri Porten's avatar
Harri Porten committed
139
    return jsNumber(m_state);
George Staikos's avatar
George Staikos committed
140 141 142
  case ResponseText:
    return ::getStringOrNull(DOM::DOMString(response));
  case ResponseXML:
143
    if (m_state != XHRS_Loaded) {
Harri Porten's avatar
Harri Porten committed
144
      return jsNull();
George Staikos's avatar
George Staikos committed
145 146 147 148
    }
    if (!createdDocument) {
      QString mimeType = "text/xml";

149
      if (!m_mimeTypeOverride.isEmpty()) {
150
        mimeType = m_mimeTypeOverride;
151
      } else {
152 153 154 155
        int dummy;
        JSValue *header = getResponseHeader("Content-Type", dummy);
        if (!header->isUndefinedOrNull())
          mimeType = header->toString(exec).qstring().split(";")[0].trimmed();
George Staikos's avatar
George Staikos committed
156 157 158 159 160 161
      }

      if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
	responseXML = doc->implementation()->createDocument();

	responseXML->open();
162
	responseXML->setURL(url.url());
George Staikos's avatar
George Staikos committed
163 164 165 166 167 168 169 170 171 172 173 174
	responseXML->write(response);
	responseXML->finishParsing();
	responseXML->close();

	typeIsXML = true;
      } else {
	typeIsXML = false;
      }
      createdDocument = true;
    }

    if (!typeIsXML) {
Harri Porten's avatar
Harri Porten committed
175
      return jsNull();
George Staikos's avatar
George Staikos committed
176 177 178 179 180 181 182 183 184 185 186
    }

    return getDOMNode(exec,responseXML.get());
  case Status:
    return getStatus();
  case StatusText:
    return getStatusText();
  case Onreadystatechange:
   if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObj()) {
     return onReadyStateChangeListener->listenerObj();
   } else {
Harri Porten's avatar
Harri Porten committed
187
     return jsNull();
George Staikos's avatar
George Staikos committed
188 189 190 191 192
   }
  case Onload:
   if (onLoadListener && onLoadListener->listenerObj()) {
     return onLoadListener->listenerObj();
   } else {
Harri Porten's avatar
Harri Porten committed
193
     return jsNull();
George Staikos's avatar
George Staikos committed
194 195
   }
  default:
Laurent Montel's avatar
Laurent Montel committed
196
    kWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token;
George Staikos's avatar
George Staikos committed
197 198 199 200
    return 0;
  }
}

Harri Porten's avatar
Harri Porten committed
201
void XMLHttpRequest::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
George Staikos's avatar
George Staikos committed
202 203 204 205
{
  lookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
}

Harri Porten's avatar
Harri Porten committed
206
void XMLHttpRequest::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/)
George Staikos's avatar
George Staikos committed
207 208 209
{
  switch(token) {
  case Onreadystatechange:
210
    if (onReadyStateChangeListener) onReadyStateChangeListener->deref();
George Staikos's avatar
George Staikos committed
211 212 213 214
    onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
    if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
    break;
  case Onload:
215
    if (onLoadListener) onLoadListener->deref();
George Staikos's avatar
George Staikos committed
216 217 218 219
    onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
    if (onLoadListener) onLoadListener->ref();
    break;
  default:
Laurent Montel's avatar
Laurent Montel committed
220
    kWarning() << "XMLHttpRequest::putValue unhandled token " << token;
George Staikos's avatar
George Staikos committed
221 222 223
  }
}

224 225 226
// Token according to RFC 2616
static bool isValidFieldName(const QString& name)
{
227
    const int l = name.length();
228 229
    if (l == 0)
        return false;
230

231
    const QChar* c = name.constData();
232
    for (int i = 0; i < l; ++i, ++c) {
233
        ushort u = c->unicode();
234 235 236 237 238 239 240 241
        if (u < 32 || u > 126)
            return false;
        switch (u) {
        case '(': case ')': case '<': case '>':
        case '@': case ',': case ';': case ':':
        case '\\': case '"': case '/':
        case '[': case ']': case '?': case '=':
        case '{': case '}': case '\t': case ' ':
242
            return false;
243 244 245 246 247 248 249
        default:
            break;
        }
    }
    return true;
}

250
static bool isValidFieldValue(const QString& name)
251
{
252
    const int l = name.length();
253 254 255
    if (l == 0)
        return true;

256
    const QChar* c = name.constData();
257
    for (int i = 0; i < l; ++i, ++c) {
258
        ushort u = c->unicode();
259 260
        if ( u == '\n' || u == '\r' )
           return false;
261 262
    }

263 264 265 266
    // ### what is invalid?
    return true;
}

267 268
static bool canSetRequestHeader(const QString& name)
{
269 270 271 272
    if (name.startsWith(QLatin1String("sec-"), Qt::CaseInsensitive) ||
        name.startsWith(QLatin1String("proxy-"), Qt::CaseInsensitive)) {
        return false;
    }
273

274
    static QSet<CaseInsensitiveString> forbiddenHeaders;
275
    if (forbiddenHeaders.isEmpty()) {
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
        static const char* const hdrs[] = {
            "accept-charset",
            "accept-encoding",
            "access-control-request-headers",
            "access-control-request-method",
            "connection",
            "content-length",
            "content-transfer-encoding",
            "cookie",
            "cookie2",
            "date",
            "dnt",
            "expect",
            "host",
            "keep-alive",
            "origin",
            "referer",
            "te",
            "trailer",
            "transfer-encoding",
            "upgrade",
            "user-agent",
            "via"
        };
        for (size_t i = 0; i < sizeof(hdrs)/sizeof(char*); ++i) {
            forbiddenHeaders.insert(CaseInsensitiveString(hdrs[i]));
        }
303
    }
304

305 306 307
    return !forbiddenHeaders.contains(name);
}

George Staikos's avatar
George Staikos committed
308 309 310 311 312 313
XMLHttpRequest::XMLHttpRequest(ExecState *exec, DOM::DocumentImpl* d)
  : qObject(new XMLHttpRequestQObject(this)),
    doc(d),
    async(true),
    contentType(QString()),
    job(0),
314
    m_state(XHRS_Uninitialized),
George Staikos's avatar
George Staikos committed
315 316 317
    onReadyStateChangeListener(0),
    onLoadListener(0),
    decoder(0),
318
    binaryMode(false),
319
    response(QString::fromLatin1("")),
George Staikos's avatar
George Staikos committed
320 321 322
    createdDocument(false),
    aborted(false)
{
323
  ref(); // we're a GC point, so refcount pin.
George Staikos's avatar
George Staikos committed
324 325 326 327 328
  setPrototype(XMLHttpRequestProto::self(exec));
}

XMLHttpRequest::~XMLHttpRequest()
{
329
  if (job && m_method != QLatin1String("POST")) {
330 331 332
        job->kill();
        job = 0;
  }
333
  if (onLoadListener)
334
      onLoadListener->deref();
335
  if (onReadyStateChangeListener)
336
      onReadyStateChangeListener->deref();
George Staikos's avatar
George Staikos committed
337 338 339 340 341 342 343 344
  delete qObject;
  qObject = 0;
  delete decoder;
  decoder = 0;
}

void XMLHttpRequest::changeState(XMLHttpRequestState newState)
{
345 346 347 348
  // Other engines cancel transfer if the controlling document doesn't
  // exist anymore. Match that, though being paranoid about post
  // (And don't emit any events w/o a doc, if we're kept alive otherwise).
  if (!doc) {
349
    if (job && m_method != QLatin1String("POST")) {
350 351 352 353 354 355
      job->kill();
      job = 0;
    }
    return;
  }

356 357
  if (m_state != newState) {
    m_state = newState;
Harri Porten's avatar
Harri Porten committed
358
    ProtectedPtr<JSObject> ref(this);
George Staikos's avatar
George Staikos committed
359 360 361 362

    if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) {
      DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
      ev.initEvent("readystatechange", true, true);
363 364
      ev.handle()->setTarget(this);
      ev.handle()->setCurrentTarget(this);
George Staikos's avatar
George Staikos committed
365
      onReadyStateChangeListener->handleEvent(ev);
366 367 368 369 370

      // Make sure the event doesn't point to us, since it can't prevent
      // us from being collecte.
      ev.handle()->setTarget(0);
      ev.handle()->setCurrentTarget(0);
George Staikos's avatar
George Staikos committed
371 372
    }

373
    if (m_state == XHRS_Loaded && onLoadListener != 0 && doc->view() && doc->view()->part()) {
George Staikos's avatar
George Staikos committed
374 375
      DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
      ev.initEvent("load", true, true);
376 377
      ev.handle()->setTarget(this);
      ev.handle()->setCurrentTarget(this);
George Staikos's avatar
George Staikos committed
378
      onLoadListener->handleEvent(ev);
379 380
      ev.handle()->setTarget(0);
      ev.handle()->setCurrentTarget(0);
George Staikos's avatar
George Staikos committed
381 382 383 384
    }
  }
}

385
bool XMLHttpRequest::urlMatchesDocumentDomain(const KUrl& _url) const
George Staikos's avatar
George Staikos committed
386 387 388 389 390
{
  // No need to do work if _url is not valid...
  if (!_url.isValid())
    return false;

391
  KUrl documentURL(doc->URL());
George Staikos's avatar
George Staikos committed
392 393

  // a local file can load anything
394
  if (documentURL.protocol() == "file") {
George Staikos's avatar
George Staikos committed
395 396 397 398
    return true;
  }

  // but a remote document can only load from the same port on the server
399
  if (documentURL.protocol() == _url.protocol() &&
George Staikos's avatar
George Staikos committed
400 401 402 403 404 405 406 407
      documentURL.host().toLower() == _url.host().toLower() &&
      documentURL.port() == _url.port()) {
    return true;
  }

  return false;
}

408 409 410 411
// Methods we're to recognize per the XHR spec (3.6.1, #3).
// We map it to whether the method should be permitted or not (#4)
static const IDTranslator<QByteArray, bool, const char*>::Info methodsTable[] = {
    {"CONNECT", false},
412
    {"DELETE", true},
413 414 415 416 417 418 419 420 421 422 423 424
    {"GET", true},
    {"HEAD", true},
    {"OPTIONS", true},
    {"POST", true},
    {"PUT", true},
    {"TRACE", false},
    {"TRACK", false},
    {0, false}
};

MAKE_TRANSLATOR(methodsLookup, QByteArray, bool, const char*, methodsTable)

425
void XMLHttpRequest::open(const QString& _method, const KUrl& _url, bool _async, int& ec)
George Staikos's avatar
George Staikos committed
426 427 428 429 430
{
  abort();
  aborted = false;

  // clear stuff from possible previous load
431
  m_requestHeaders.clear();
George Staikos's avatar
George Staikos committed
432
  responseHeaders.clear();
433
  response = QString::fromLatin1("");
George Staikos's avatar
George Staikos committed
434 435 436 437
  createdDocument = false;
  responseXML = 0;

  if (!urlMatchesDocumentDomain(_url)) {
438
    ec = DOMException::SECURITY_ERR;
George Staikos's avatar
George Staikos committed
439 440 441
    return;
  }

442
  // ### potentially raise a SYNTAX_ERR
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458

  // Lookup if the method is well-known, and if so check if it's OK
  QByteArray methodNormalized = _method.toUpper().toUtf8();
  if (methodsLookup()->hasLeft(methodNormalized)) {
      if (methodsLookup()->toRight(methodNormalized)) {
          // OK, replace with the canonical version...
          m_method = _method.toUpper();
      } else {
          // Scary stuff like CONNECT
          ec = DOMException::SECURITY_ERR;
          return;
      }
  } else {
      // Unknown -> pass through unchanged
      m_method = _method;
  }
459

George Staikos's avatar
George Staikos committed
460 461 462
  url = _url;
  async = _async;

463
  changeState(XHRS_Open);
George Staikos's avatar
George Staikos committed
464 465
}

Harri Porten's avatar
Harri Porten committed
466
void XMLHttpRequest::send(const QString& _body, int& ec)
George Staikos's avatar
George Staikos committed
467 468 469
{
  aborted = false;

Harri Porten's avatar
Harri Porten committed
470 471 472 473 474
  if (m_state != XHRS_Open) {
      ec =  DOMException::INVALID_STATE_ERR;
      return;
  }

475
  const QString protocol = url.protocol();
476 477 478 479 480
  // Abandon the request when the protocol is other than "http",
  // instead of blindly doing a KIO::get on other protocols like file:/.
  if (!protocol.startsWith(QLatin1String("http")) &&
      !protocol.startsWith(QLatin1String("webdav")))
  {
481 482 483
      ec = DOMException::INVALID_ACCESS_ERR;
      abort();
      return;
484
  }
George Staikos's avatar
George Staikos committed
485

486
  // We need to use a POST-like setup even for non-post whenever we
487
  // have a payload.
488 489
  const bool havePayload = !_body.isEmpty();
  if (m_method == QLatin1String("POST") || havePayload) {
George Staikos's avatar
George Staikos committed
490 491
    // FIXME: determine post encoding correctly by looking in headers
    // for charset.
492
    QByteArray buf = _body.toUtf8();
David Faure's avatar
David Faure committed
493
    job = KIO::http_post( url, buf, KIO::HideProgressInfo );
494 495 496 497 498
  } else if (m_method == QLatin1String("HEAD")) {
    job = KIO::mimetype(url, KIO::HideProgressInfo);
  } else if (m_method == QLatin1String("PUT")) {
    job = KIO::put(url, -1, KIO::HideProgressInfo);
  } else {
David Faure's avatar
David Faure committed
499
    job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
George Staikos's avatar
George Staikos committed
500
  }
501

502 503
  // Regardless of job type, make sure the method is set
  job->addMetaData("CustomHTTPMethod", m_method);
George Staikos's avatar
George Staikos committed
504

505 506 507 508 509 510 511 512
  // Set headers

  if (!contentType.isNull()) {
    job->addMetaData("content-type", contentType);
  } else if (havePayload) {
    job->addMetaData("content-type", "Content-type: text/plain");
  }

513
  if (!m_requestHeaders.isEmpty()) {
George Staikos's avatar
George Staikos committed
514
    QString rh;
515 516
    HTTPHeaderMap::ConstIterator begin = m_requestHeaders.constBegin();
    HTTPHeaderMap::ConstIterator end = m_requestHeaders.constEnd();
517
    for (HTTPHeaderMap::ConstIterator i = begin; i != end; ++i) {
518 519 520 521 522 523 524 525 526 527
      QString key = i.key().original();
      QString value = i.value();
      if (key.toLower() == "accept") {
        // The HTTP KIO slave supports an override this way
        job->addMetaData("accept", value);
      } else {
        if (!rh.isEmpty())
          rh += "\r\n";
        rh += key + ": " + value;
      }
George Staikos's avatar
George Staikos committed
528 529 530 531 532 533 534
    }

    job->addMetaData("customHTTPHeader", rh);
  }

  job->addMetaData("PropagateHttpHeader", "true");

535
  // Set the default referrer. NOTE: the user can still disable
George Staikos's avatar
George Staikos committed
536
  // this feature at the protocol level (kio_http).
537 538 539 540
  KUrl documentURL(doc->URL());
  documentURL.setPass(QString());
  documentURL.setUser(QString());
  job->addMetaData("referrer", documentURL.url());
Laurent Montel's avatar
Laurent Montel committed
541
  // kDebug() << "Adding referrer: " << documentURL;
George Staikos's avatar
George Staikos committed
542 543 544

  if (!async) {
    QByteArray data;
545
    KUrl finalURL;
George Staikos's avatar
George Staikos committed
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
    QString headers;

#ifdef APPLE_CHANGES
    data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
#else
    QMap<QString, QString> metaData;
    if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
      headers = metaData[ "HTTP-Headers" ];
    }
#endif
    job = 0;
    processSyncLoadResults(data, finalURL, headers);
    return;
  }

561 562
  qObject->connect( job, SIGNAL(result(KJob*)),
		    SLOT(slotFinished(KJob*)) );
George Staikos's avatar
George Staikos committed
563
#ifdef APPLE_CHANGES
564 565
  qObject->connect( job, SIGNAL(data(KIO::Job*,const char*,int)),
		    SLOT(slotData(KIO::Job*,const char*,int)) );
George Staikos's avatar
George Staikos committed
566
#else
567 568
  qObject->connect( job, SIGNAL(data(KIO::Job*,QByteArray)),
		    SLOT(slotData(KIO::Job*,QByteArray)) );
George Staikos's avatar
George Staikos committed
569
#endif
570 571
  qObject->connect( job, SIGNAL(redirection(KIO::Job*,KUrl)),
		    SLOT(slotRedirection(KIO::Job*,KUrl)) );
George Staikos's avatar
George Staikos committed
572 573 574 575

#ifdef APPLE_CHANGES
  KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
#else
576
  KIO::Scheduler::setJobPriority( job, 1 );
George Staikos's avatar
George Staikos committed
577 578 579
#endif
}

580 581 582 583 584 585 586
void XMLHttpRequest::clearDecoder()
{
    delete decoder;
    decoder = 0;
    binaryMode = false;
}

George Staikos's avatar
George Staikos committed
587 588 589 590 591 592 593
void XMLHttpRequest::abort()
{
  if (job) {
    job->kill();
    job = 0;
  }
  aborted = true;
594
  clearDecoder();
595
  changeState(XHRS_Uninitialized);
George Staikos's avatar
George Staikos committed
596 597
}

598 599 600 601 602
void XMLHttpRequest::overrideMIMEType(const QString& override)
{
    m_mimeTypeOverride = override;
}

Harri Porten's avatar
Harri Porten committed
603
void XMLHttpRequest::setRequestHeader(const QString& _name, const QString& _value, int& ec)
George Staikos's avatar
George Staikos committed
604
{
605 606
  // throw exception if connection is not open or the send flag is set
  if (m_state != XHRS_Open || job != 0) {
Harri Porten's avatar
Harri Porten committed
607 608 609 610
      ec = DOMException::INVALID_STATE_ERR;
      return;
  }

611
  if (!isValidFieldName(_name) || !isValidFieldValue(_value)) {
Harri Porten's avatar
Harri Porten committed
612 613 614
      ec = DOMException::SYNTAX_ERR;
      return;
  }
615

Harri Porten's avatar
Harri Porten committed
616
  QString value = _value.trimmed();
George Staikos's avatar
George Staikos committed
617

618
  // Content-type needs to be set separately from the other headers
619
  if (_name.compare(QLatin1String("content-type"), Qt::CaseInsensitive) == 0) {
George Staikos's avatar
George Staikos committed
620 621 622 623
    contentType = "Content-type: " + value;
    return;
  }

624
  // Reject all banned headers.
625
  if (!canSetRequestHeader(_name)) {
Andrea Iacovitti's avatar
Andrea Iacovitti committed
626
      kWarning(6070) << "Refusing to set unsafe XMLHttpRequest header" << _name;
627 628
      return;
  }
George Staikos's avatar
George Staikos committed
629

630 631 632 633 634
  if (m_requestHeaders.contains(_name)) {
    m_requestHeaders[_name] += (QLatin1String(", ") + value);
  } else {
    m_requestHeaders[_name] = value;
  }
George Staikos's avatar
George Staikos committed
635 636
}

637
JSValue *XMLHttpRequest::getAllResponseHeaders(int& ec) const
George Staikos's avatar
George Staikos committed
638
{
639 640 641 642
  if (m_state < XHRS_Receiving) {
      ec = DOMException::INVALID_STATE_ERR;
      return jsString("");
  }
643

644 645
  // ### test error flag, return jsNull

George Staikos's avatar
George Staikos committed
646
  if (responseHeaders.isEmpty()) {
Harri Porten's avatar
Harri Porten committed
647
    return jsUndefined();
George Staikos's avatar
George Staikos committed
648 649
  }

Tobias Koenig's avatar
Tobias Koenig committed
650
  int endOfLine = responseHeaders.indexOf("\n");
George Staikos's avatar
George Staikos committed
651 652

  if (endOfLine == -1) {
Harri Porten's avatar
Harri Porten committed
653
    return jsUndefined();
George Staikos's avatar
George Staikos committed
654 655
  }

Harri Porten's avatar
Harri Porten committed
656
  return jsString(responseHeaders.mid(endOfLine + 1) + '\n');
George Staikos's avatar
George Staikos committed
657 658
}

659
JSValue *XMLHttpRequest::getResponseHeader(const QString& name, int& ec) const
George Staikos's avatar
George Staikos committed
660
{
661 662 663 664 665 666 667 668 669 670 671
  if (m_state < XHRS_Receiving) {
      ec = DOMException::INVALID_STATE_ERR;
      return jsString("");
  }

  if (!isValidFieldName(name)) {
    return jsString("");
  }

  // ### test error flag, return jsNull

George Staikos's avatar
George Staikos committed
672
  if (responseHeaders.isEmpty()) {
Harri Porten's avatar
Harri Porten committed
673
    return jsUndefined();
George Staikos's avatar
George Staikos committed
674 675
  }

Laurent Montel's avatar
Fix ebn  
Laurent Montel committed
676
  QRegExp headerLinePattern(name + ':', Qt::CaseInsensitive);
George Staikos's avatar
George Staikos committed
677 678

  int matchLength;
Tobias Koenig's avatar
Tobias Koenig committed
679
  int headerLinePos = headerLinePattern.indexIn(responseHeaders, 0);
George Staikos's avatar
George Staikos committed
680 681 682 683 684 685
  matchLength = headerLinePattern.matchedLength();
  while (headerLinePos != -1) {
    if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
      break;
    }

Tobias Koenig's avatar
Tobias Koenig committed
686
    headerLinePos = headerLinePattern.indexIn(responseHeaders, headerLinePos + 1);
George Staikos's avatar
George Staikos committed
687 688 689 690 691
    matchLength = headerLinePattern.matchedLength();
  }


  if (headerLinePos == -1) {
Harri Porten's avatar
Harri Porten committed
692
    return jsNull();
George Staikos's avatar
George Staikos committed
693 694
  }

Tobias Koenig's avatar
Tobias Koenig committed
695
  int endOfLine = responseHeaders.indexOf("\n", headerLinePos + matchLength);
George Staikos's avatar
George Staikos committed
696

Harri Porten's avatar
Harri Porten committed
697
  return jsString(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).trimmed());
George Staikos's avatar
George Staikos committed
698 699
}

Harri Porten's avatar
Harri Porten committed
700
static JSValue *httpStatus(const QString& response, bool textStatus = false)
George Staikos's avatar
George Staikos committed
701 702
{
  if (response.isEmpty()) {
Harri Porten's avatar
Harri Porten committed
703
    return jsUndefined();
George Staikos's avatar
George Staikos committed
704 705
  }

Tobias Koenig's avatar
Tobias Koenig committed
706
  int endOfLine = response.indexOf("\n");
George Staikos's avatar
George Staikos committed
707
  QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine);
Tobias Koenig's avatar
Tobias Koenig committed
708 709
  int codeStart = firstLine.indexOf(" ");
  int codeEnd = firstLine.indexOf(" ", codeStart + 1);
George Staikos's avatar
George Staikos committed
710 711

  if (codeStart == -1 || codeEnd == -1) {
Harri Porten's avatar
Harri Porten committed
712
    return jsUndefined();
George Staikos's avatar
George Staikos committed
713 714 715 716
  }

  if (textStatus) {
    QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).trimmed();
Harri Porten's avatar
Harri Porten committed
717
    return jsString(statusText);
George Staikos's avatar
George Staikos committed
718 719 720 721 722 723 724
  }

  QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));

  bool ok = false;
  int code = number.toInt(&ok);
  if (!ok) {
Harri Porten's avatar
Harri Porten committed
725
    return jsUndefined();
George Staikos's avatar
George Staikos committed
726 727
  }

Harri Porten's avatar
Harri Porten committed
728
  return jsNumber(code);
George Staikos's avatar
George Staikos committed
729 730
}

Harri Porten's avatar
Harri Porten committed
731
JSValue *XMLHttpRequest::getStatus() const
George Staikos's avatar
George Staikos committed
732 733 734 735
{
  return httpStatus(responseHeaders);
}

Harri Porten's avatar
Harri Porten committed
736
JSValue *XMLHttpRequest::getStatusText() const
George Staikos's avatar
George Staikos committed
737 738 739 740
{
  return httpStatus(responseHeaders, true);
}

741
void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KUrl &finalURL, const QString &headers)
George Staikos's avatar
George Staikos committed
742 743 744 745 746 747 748
{
  if (!urlMatchesDocumentDomain(finalURL)) {
    abort();
    return;
  }

  responseHeaders = headers;
749
  changeState(XHRS_Sent);
George Staikos's avatar
George Staikos committed
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
  if (aborted) {
    return;
  }

#ifdef APPLE_CHANGES
  const char *bytes = (const char *)data.data();
  int len = (int)data.size();

  slotData(0, bytes, len);
#else
  slotData(0, data);
#endif

  if (aborted) {
    return;
  }

  slotFinished(0);
}

770
void XMLHttpRequest::slotFinished(KJob *)
George Staikos's avatar
George Staikos committed
771 772 773 774 775 776 777 778
{
  if (decoder) {
    response += decoder->flush();
  }

  // make sure to forget about the job before emitting completed,
  // since changeState triggers JS code, which might e.g. call abort.
  job = 0;
779
  changeState(XHRS_Loaded);
George Staikos's avatar
George Staikos committed
780

781
  clearDecoder();
George Staikos's avatar
George Staikos committed
782 783
}

784
void XMLHttpRequest::slotRedirection(KIO::Job*, const KUrl& url)
George Staikos's avatar
George Staikos committed
785 786 787 788 789 790
{
  if (!urlMatchesDocumentDomain(url)) {
    abort();
  }
}

791 792 793 794 795 796 797 798 799
static QString encodingFromContentType(const QString& type)
{
    QString encoding;
    int index = type.indexOf(';');
    if (index > -1)
        encoding = type.mid( index+1 ).remove(QRegExp("charset[ ]*=[ ]*", Qt::CaseInsensitive)).trimmed();
    return encoding;
}

George Staikos's avatar
George Staikos committed
800 801 802 803 804 805
#ifdef APPLE_CHANGES
void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
#else
void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
#endif
{
806
  if (m_state < XHRS_Sent ) {
George Staikos's avatar
George Staikos committed
807 808 809 810
    responseHeaders = job->queryMetaData("HTTP-Headers");

    // NOTE: Replace a 304 response with a 200! Both IE and Mozilla do this.
    // Problem first reported through bug# 110272.
Tobias Koenig's avatar
Tobias Koenig committed
811
    int codeStart = responseHeaders.indexOf("304");
George Staikos's avatar
George Staikos committed
812
    if ( codeStart != -1) {
Tobias Koenig's avatar
Tobias Koenig committed
813
      int codeEnd = responseHeaders.indexOf("\n", codeStart+3);
George Staikos's avatar
George Staikos committed
814 815 816 817
      if (codeEnd != -1)
        responseHeaders.replace(codeStart, (codeEnd-codeStart), "200 OK");
    }

818
    changeState(XHRS_Sent);
George Staikos's avatar
George Staikos committed
819 820 821 822 823 824 825
  }

#ifndef APPLE_CHANGES
  const char *data = (const char *)_data.data();
  int len = (int)_data.size();
#endif

826
  if ( !decoder && !binaryMode ) {
827 828
    if (!m_mimeTypeOverride.isEmpty())
        encoding = encodingFromContentType(m_mimeTypeOverride);
829

830
    if (encoding.isEmpty()) {
831 832 833 834
      int pos = responseHeaders.indexOf(QLatin1String("content-type:"), 0, Qt::CaseInsensitive);
      if ( pos > -1 ) {
        pos += 13;
        int index = responseHeaders.indexOf('\n', pos);
835 836
        QString type = responseHeaders.mid(pos, (index-pos));
        encoding = encodingFromContentType(type);
837 838
      }
    }
George Staikos's avatar
George Staikos committed
839

840 841 842 843 844 845 846 847 848
    if (encoding == QLatin1String("x-user-defined")) {
      binaryMode = true;
    } else {
      decoder = new KEncodingDetector;
      if (!encoding.isEmpty())
        decoder->setEncoding(encoding.toLatin1().constData(), KEncodingDetector::EncodingFromHTTPHeader);
      else
        decoder->setEncoding("UTF-8", KEncodingDetector::DefaultEncoding);
    }
George Staikos's avatar
George Staikos committed
849
  }
850

George Staikos's avatar
George Staikos committed
851 852 853 854 855 856
  if (len == 0)
    return;

  if (len == -1)
    len = strlen(data);

857 858 859 860 861
  QString decoded;
  if (binaryMode)
    decoded = QString::fromLatin1(data, len);
  else
    decoded = decoder->decodeWithBuffering(data, len);
862

George Staikos's avatar
George Staikos committed
863
  response += decoded;
864

George Staikos's avatar
George Staikos committed
865
  if (!aborted) {
866
    changeState(XHRS_Receiving);
George Staikos's avatar
George Staikos committed
867 868 869
  }
}

Harri Porten's avatar
Harri Porten committed
870
JSValue *XMLHttpRequestProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
George Staikos's avatar
George Staikos committed
871 872 873 874 875 876
{
  if (!thisObj->inherits(&XMLHttpRequest::info)) {
    return throwError(exec, TypeError);
  }

  XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj);
Harri Porten's avatar
Harri Porten committed
877

878
  if (!request->doc) {
879 880
    setDOMException(exec, DOMException::INVALID_STATE_ERR);
    return jsUndefined();
881 882
  }

Harri Porten's avatar
Harri Porten committed
883 884
  int ec = 0;

George Staikos's avatar
George Staikos committed
885 886 887
  switch (id) {
  case XMLHttpRequest::Abort:
    request->abort();
Harri Porten's avatar
Harri Porten committed
888
    return jsUndefined();
George Staikos's avatar
George Staikos committed
889
  case XMLHttpRequest::GetAllResponseHeaders:
890
    {
891
      JSValue *ret = request->getAllResponseHeaders(ec);
892 893 894
      setDOMException(exec, ec);
      return ret;
    }
895
  case XMLHttpRequest::GetResponseHeader:
896 897 898
    {
      if (args.size() < 1)
          return throwError(exec, SyntaxError, "Not enough arguments");
899
      JSValue *ret = request->getResponseHeader(args[0]->toString(exec).qstring(), ec);
900 901 902
      setDOMException(exec, ec);
      return ret;
    }
George Staikos's avatar
George Staikos committed
903 904
  case XMLHttpRequest::Open:
    {
905 906
      if (args.size() < 2)
          return throwError(exec, SyntaxError, "Not enough arguments");
George Staikos's avatar
George Staikos committed
907 908

      QString method = args[0]->toString(exec).qstring();
909 910
      DOMString urlArg = args[1]->toString(exec).domString().trimSpaces();
      KUrl url = KUrl(request->doc->completeURL(urlArg.string()));
George Staikos's avatar
George Staikos committed
911 912 913

      bool async = true;
      if (args.size() >= 3) {
914
          async = args[2]->toBoolean(exec);
George Staikos's avatar
George Staikos committed
915 916
      }

917 918 919 920 921 922 923 924 925
      // Set url userinfo
      if (args.size() >= 4 && !args[3]->isUndefinedOrNull()) {
          QString user = args[3]->toString(exec).qstring();
          if (!user.isEmpty()) {
              url.setUser(user);
              if (args.size() >= 5 && !args[4]->isUndefinedOrNull()) {
                  url.setPass(args[4]->toString(exec).qstring());
              }
          }
George Staikos's avatar
George Staikos committed
926 927
      }

928 929
      request->open(method, url, async, ec);
      setDOMException(exec, ec);
Harri Porten's avatar
Harri Porten committed
930
      return jsUndefined();
George Staikos's avatar
George Staikos committed
931 932 933 934
    }
  case XMLHttpRequest::Send:
    {
      QString body;
935 936 937
      if (!args[0]->isUndefinedOrNull()
            // make sure we don't marshal "undefined" or such;
          && request->m_method != QLatin1String("GET")
938 939
          && request->m_method != QLatin1String("HEAD")) {
            // ... or methods that don't have payload
940

941 942 943 944 945 946 947
          DOM::NodeImpl* docNode = toNode(args[0]);
          if (docNode && docNode->isDocumentNode()) {
              DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode);
              body = doc->toString().string();
              // FIXME: also need to set content type, including encoding!
          } else {
              body = args[0]->toString(exec).qstring();
948
          }
George Staikos's avatar
George Staikos committed
949 950
      }

Harri Porten's avatar
Harri Porten committed
951
      request->send(body, ec);
952
      setDOMException(exec, ec);
953
      return jsUndefined();
George Staikos's avatar
George Staikos committed
954 955
    }
  case XMLHttpRequest::SetRequestHeader:
956
      {
Harri Porten's avatar
Harri Porten committed
957 958
      if (args.size() < 2)
          return throwError(exec, SyntaxError, "Not enough arguments");
959 960 961 962

      QString headerFieldName = args[0]->toString(exec).qstring();
      QString headerFieldValue = args[1]->toString(exec).qstring();
      request->setRequestHeader(headerFieldName, headerFieldValue, ec);
Harri Porten's avatar
Harri Porten committed
963
      setDOMException(exec, ec);
964
      return jsUndefined();
965 966 967 968 969 970 971 972
      }

  case XMLHttpRequest::OverrideMIMEType:
      if (args.size() < 1)
          return throwError(exec, SyntaxError, "Not enough arguments");

      request->overrideMIMEType(args[0]->toString(exec).qstring());
      return jsUndefined();
George Staikos's avatar
George Staikos committed
973 974
  }

Harri Porten's avatar
Harri Porten committed
975
  return jsUndefined();
George Staikos's avatar
George Staikos committed
976 977 978 979 980
}

} // end namespace

#include "xmlhttprequest.moc"
981
// kate: indent-width 2; replace-tabs on; tab-width 4; space-indent on;