Reviewed by Eric Seidel.
[WebKit-https.git] / WebCore / khtml / ecma / xmlhttprequest.cpp
1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 2004 Apple Computer, Inc.
4  *  Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "xmlhttprequest.h"
23
24 #include "kjs_window.h"
25 #include "kjs_events.h"
26
27 #include "dom/dom_exception.h"
28 #include "dom/dom_string.h"
29 #include "misc/loader.h"
30 #include "html/html_documentimpl.h"
31 #include "xml/dom2_eventsimpl.h"
32 #include "xml/EventNames.h"
33
34 #include "khtml_part.h"
35 #include "khtmlview.h"
36
37 #include <kdebug.h>
38 #include <kio/job.h>
39 #include <qobject.h>
40 #include <qregexp.h>
41
42 #include "KWQLoader.h"
43
44 #include "xmlhttprequest.lut.h"
45
46 using DOM::DocumentImpl;
47 using DOM::DOMImplementationImpl;
48 using DOM::EventImpl;
49 using namespace DOM::EventNames;
50
51 using khtml::Decoder;
52
53 static inline QString getMIMEType(const QString& contentTypeString)
54 {
55     return QStringList::split(";", contentTypeString, true)[0].stripWhiteSpace();
56 }
57
58 static QString getCharset(const QString& contentTypeString)
59 {
60     int pos = 0;
61     int length = (int)contentTypeString.length();
62     
63     while (pos < length) {
64         pos = contentTypeString.find("charset", pos, false);
65         if (pos <= 0)
66             return QString();
67         
68         // is what we found a beginning of a word?
69         if (contentTypeString[pos-1] > ' ' && contentTypeString[pos-1] != ';') {
70             pos += 7;
71             continue;
72         }
73         
74         pos += 7;
75
76         // skip whitespace
77         while (pos != length && contentTypeString[pos] <= ' ')
78             ++pos;
79     
80         if (contentTypeString[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others
81             continue;
82
83         while (pos != length && (contentTypeString[pos] <= ' ' || contentTypeString[pos] == '"' || contentTypeString[pos] == '\''))
84             ++pos;
85
86         // we don't handle spaces within quoted parameter values, because charset names cannot have any
87         int endpos = pos;
88         while (pos != length && contentTypeString[endpos] > ' ' && contentTypeString[endpos] != '"' && contentTypeString[endpos] != '\'' && contentTypeString[endpos] != ';')
89             ++endpos;
90     
91         return contentTypeString.mid(pos, endpos-pos);
92     }
93     
94     return QString();
95 }
96
97 namespace KJS {
98
99 ////////////////////// XMLHttpRequest Object ////////////////////////
100
101 /* Source for XMLHttpRequestProtoTable.
102 @begin XMLHttpRequestProtoTable 7
103   abort                 XMLHttpRequest::Abort                   DontDelete|Function 0
104   getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders   DontDelete|Function 0
105   getResponseHeader     XMLHttpRequest::GetResponseHeader       DontDelete|Function 1
106   open                  XMLHttpRequest::Open                    DontDelete|Function 5
107   overrideMimeType      XMLHttpRequest::OverrideMIMEType        DontDelete|Function 1
108   send                  XMLHttpRequest::Send                    DontDelete|Function 1
109   setRequestHeader      XMLHttpRequest::SetRequestHeader        DontDelete|Function 2
110 @end
111 */
112 KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto)
113 KJS_IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
114 KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest",XMLHttpRequestProto,XMLHttpRequestProtoFunc)
115
116 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject) 
117 {
118   jsObject = _jsObject; 
119 }
120
121 void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
122 {
123   jsObject->slotData(job, data, size);
124 }
125
126 void XMLHttpRequestQObject::slotFinished( KIO::Job* job )
127 {
128   jsObject->slotFinished(job); 
129 }
130
131 void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KURL& url)
132
133   jsObject->slotRedirection( job, url ); 
134 }
135
136 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *, DOM::DocumentImpl *d)
137     : doc(d)
138 {
139 }
140
141 XMLHttpRequestConstructorImp::~XMLHttpRequestConstructorImp()
142 {
143 }
144
145 bool XMLHttpRequestConstructorImp::implementsConstruct() const
146 {
147   return true;
148 }
149
150 JSObject *XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
151 {
152   return new XMLHttpRequest(exec, doc.get());
153 }
154
155 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
156
157 /* Source for XMLHttpRequestTable.
158 @begin XMLHttpRequestTable 7
159   readyState            XMLHttpRequest::ReadyState              DontDelete|ReadOnly
160   responseText          XMLHttpRequest::ResponseText            DontDelete|ReadOnly
161   responseXML           XMLHttpRequest::ResponseXML             DontDelete|ReadOnly
162   status                XMLHttpRequest::Status                  DontDelete|ReadOnly
163   statusText            XMLHttpRequest::StatusText              DontDelete|ReadOnly
164   onreadystatechange    XMLHttpRequest::Onreadystatechange      DontDelete
165   onload                XMLHttpRequest::Onload                  DontDelete
166 @end
167 */
168
169 bool XMLHttpRequest::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
170 {
171   return getStaticValueSlot<XMLHttpRequest, DOMObject>(exec, &XMLHttpRequestTable, this, propertyName, slot);
172 }
173
174 JSValue *XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
175 {
176   switch (token) {
177   case ReadyState:
178     return jsNumber(state);
179   case ResponseText:
180     return jsStringOrNull(DOM::DOMString(response));
181   case ResponseXML:
182     if (state != Completed) {
183       return jsUndefined();
184     }
185
186     if (!createdDocument) {
187       if (typeIsXML = responseIsXML()) {
188         responseXML = doc->implementation()->createDocument();
189
190         DocumentImpl *docImpl = responseXML.get();
191         
192         docImpl->open();
193         docImpl->write(response);
194         docImpl->finishParsing();
195         docImpl->close();
196       }
197       createdDocument = true;
198     }
199
200     if (!typeIsXML) {
201       return jsUndefined();
202     }
203
204     return getDOMNode(exec, responseXML.get());
205   case Status:
206     return getStatus();
207   case StatusText:
208     return getStatusText();
209   case Onreadystatechange:
210    if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObj()) {
211      return onReadyStateChangeListener->listenerObj();
212    } else {
213      return jsNull();
214    }
215   case Onload:
216    if (onLoadListener && onLoadListener->listenerObj()) {
217      return onLoadListener->listenerObj();
218    } else {
219      return jsNull();
220    }
221   default:
222     kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
223     return NULL;
224   }
225 }
226
227 void XMLHttpRequest::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
228 {
229   lookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
230 }
231
232 void XMLHttpRequest::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/)
233 {
234   switch(token) {
235   case Onreadystatechange:
236     onReadyStateChangeListener = Window::retrieveActive(exec)->getJSUnprotectedEventListener(value, true);
237     break;
238   case Onload:
239     onLoadListener = Window::retrieveActive(exec)->getJSUnprotectedEventListener(value, true);
240     break;
241   default:
242     kdWarning() << "HTMLDocument::putValueProperty unhandled token " << token << endl;
243   }
244 }
245
246 void XMLHttpRequest::mark()
247 {
248   DOMObject::mark();
249
250   if (onReadyStateChangeListener)
251     onReadyStateChangeListener->mark();
252
253   if (onLoadListener)
254     onLoadListener->mark();
255 }
256
257
258 XMLHttpRequest::XMLHttpRequest(ExecState *exec, DOM::DocumentImpl *d)
259   : qObject(new XMLHttpRequestQObject(this)),
260     doc(d),
261     async(true),
262     job(0),
263     state(Uninitialized),
264     createdDocument(false),
265     aborted(false)
266 {
267   setPrototype(XMLHttpRequestProto::self(exec));
268 }
269
270 XMLHttpRequest::~XMLHttpRequest()
271 {
272     delete qObject;
273 }
274
275 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
276 {
277   if (state != newState) {
278     state = newState;
279     
280     if (doc && doc->part() && onReadyStateChangeListener) {
281       int ignoreException;
282       RefPtr<EventImpl> ev = doc->createEvent("HTMLEvents", ignoreException);
283       ev->initEvent(readystatechangeEvent, true, true);
284       onReadyStateChangeListener->handleEventImpl(ev.get(), true);
285     }
286     
287     if (doc && doc->part() && state == Completed && onLoadListener) {
288       int ignoreException;
289       RefPtr<EventImpl> ev = doc->createEvent("HTMLEvents", ignoreException);
290       ev->initEvent(loadEvent, true, true);
291       onLoadListener->handleEventImpl(ev.get(), true);
292     }
293   }
294 }
295
296 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
297 {
298   KURL documentURL(doc->URL());
299
300   // a local file can load anything
301   if (documentURL.protocol().lower() == "file") {
302     return true;
303   }
304
305   // but a remote document can only load from the same port on the server
306   if (documentURL.protocol().lower() == _url.protocol().lower() &&
307       documentURL.host().lower() == _url.host().lower() &&
308       documentURL.port() == _url.port()) {
309     return true;
310   }
311
312   return false;
313 }
314
315 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
316 {
317   abort();
318   aborted = false;
319
320   // clear stuff from possible previous load
321   requestHeaders = QString();
322   responseHeaders = QString();
323   response = QString();
324   createdDocument = false;
325   responseXML = 0;
326
327   changeState(Uninitialized);
328
329   if (aborted) {
330     return;
331   }
332
333   if (!urlMatchesDocumentDomain(_url)) {
334     return;
335   }
336
337   
338   method = _method;
339   url = _url;
340   async = _async;
341
342   changeState(Loading);
343 }
344
345 void XMLHttpRequest::send(const QString& _body)
346 {
347   if (!doc)
348     return;
349
350   // FIXME: Should this abort instead if we already have a job going?
351   if (job)
352     return;
353
354   aborted = false;
355
356
357   if (method.lower() == "post" && (url.protocol().lower() == "http" || url.protocol().lower() == "https") ) {
358       QString contentType = getRequestHeader("Content-Type");
359       QString charset;
360       if (contentType.isEmpty())
361         setRequestHeader("Content-Type", "application/xml");
362       else
363         charset = getCharset(contentType);
364       
365       if (charset.isEmpty())
366         charset = "UTF-8";
367       
368       QTextCodec *codec = QTextCodec::codecForName(charset.latin1());
369       if (!codec)   // FIXME: report an error?
370         codec = QTextCodec::codecForName("UTF-8");
371
372       job = KIO::http_post(url, codec->fromUnicode(_body), false);
373   }
374   else
375   {
376      job = KIO::get( url, false, false );
377   }
378   if (requestHeaders.length() > 0) {
379     job->addMetaData("customHTTPHeader", requestHeaders);
380   }
381
382   if (!async) {
383     QByteArray data;
384     KURL finalURL;
385     QString headers;
386
387     { // scope
388         // avoid deadlock in case the loader wants to use JS on a background thread
389         JSLock::DropAllLocks dropLocks;
390
391         data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
392     }
393
394     job = 0;
395     processSyncLoadResults(data, finalURL, headers);
396     
397     return;
398   }
399
400   {
401     JSLock lock;
402     gcProtect(this);
403   }
404   
405   qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
406                     SLOT( slotFinished( KIO::Job* ) ) );
407   qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
408                     SLOT( slotData( KIO::Job*, const char*, int ) ) );
409   qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
410                     SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
411
412   addToRequestsByDocument();
413
414   KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
415 }
416
417 void XMLHttpRequest::abort()
418 {
419   bool hadJob = job;
420
421   if (hadJob) {
422     removeFromRequestsByDocument();
423     job->kill();
424     job = 0;
425   }
426   decoder = 0;
427   aborted = true;
428
429   if (hadJob) {
430     JSLock lock;
431     gcUnprotect(this);
432   }
433 }
434
435 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value)
436 {
437   if (requestHeaders.length() > 0) {
438     requestHeaders += "\r\n";
439   }
440   requestHeaders += name;
441   requestHeaders += ": ";
442   requestHeaders += value;
443 }
444
445 QString XMLHttpRequest::getRequestHeader(const QString& name) const
446 {
447   return getSpecificHeader(requestHeaders, name);
448 }
449
450 JSValue *XMLHttpRequest::getAllResponseHeaders() const
451 {
452   if (responseHeaders.isEmpty()) {
453     return jsUndefined();
454   }
455
456   int endOfLine = responseHeaders.find("\n");
457
458   if (endOfLine == -1) {
459     return jsUndefined();
460   }
461
462   return jsString(responseHeaders.mid(endOfLine + 1) + "\n");
463 }
464
465 QString XMLHttpRequest::getResponseHeader(const QString& name) const
466 {
467   return getSpecificHeader(responseHeaders, name);
468 }
469
470 QString XMLHttpRequest::getSpecificHeader(const QString& headers, const QString& name)
471 {
472   if (headers.isEmpty())
473     return QString();
474
475   QRegExp headerLinePattern(name + ":", false);
476
477   int matchLength;
478   int headerLinePos = headerLinePattern.match(headers, 0, &matchLength);
479   while (headerLinePos != -1) {
480     if (headerLinePos == 0 || headers[headerLinePos-1] == '\n') {
481       break;
482     }
483     
484     headerLinePos = headerLinePattern.match(headers, headerLinePos + 1, &matchLength);
485   }
486
487   if (headerLinePos == -1)
488     return QString();
489     
490   int endOfLine = headers.find("\n", headerLinePos + matchLength);
491   
492   return headers.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace();
493 }
494
495 bool XMLHttpRequest::responseIsXML() const
496 {
497     QString mimeType = getMIMEType(MIMETypeOverride);
498     if (mimeType.isEmpty())
499         mimeType = getMIMEType(getResponseHeader("Content-Type"));
500     if (mimeType.isEmpty())
501       mimeType = "text/xml";
502     
503     return DOMImplementationImpl::isXMLMIMEType(mimeType);
504 }
505
506 JSValue *XMLHttpRequest::getStatus() const
507 {
508   if (responseHeaders.isEmpty()) {
509     return jsUndefined();
510   }
511   
512   int endOfLine = responseHeaders.find("\n");
513   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
514   int codeStart = firstLine.find(" ");
515   int codeEnd = firstLine.find(" ", codeStart + 1);
516   
517   if (codeStart == -1 || codeEnd == -1) {
518     return jsUndefined();
519   }
520   
521   QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
522   
523   bool ok = false;
524   int code = number.toInt(&ok);
525   if (!ok) {
526     return jsUndefined();
527   }
528
529   return jsNumber(code);
530 }
531
532 JSValue *XMLHttpRequest::getStatusText() const
533 {
534   if (responseHeaders.isEmpty()) {
535     return jsUndefined();
536   }
537   
538   int endOfLine = responseHeaders.find("\n");
539   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
540   int codeStart = firstLine.find(" ");
541   int codeEnd = firstLine.find(" ", codeStart + 1);
542
543   if (codeStart == -1 || codeEnd == -1) {
544     return jsUndefined();
545   }
546   
547   QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
548   
549   return jsString(statusText);
550 }
551
552 void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KURL &finalURL, const QString &headers)
553 {
554   if (!urlMatchesDocumentDomain(finalURL)) {
555     abort();
556     return;
557   }
558   
559   responseHeaders = headers;
560   changeState(Loaded);
561   if (aborted) {
562     return;
563   }
564   
565   const char *bytes = (const char *)data.data();
566   int len = (int)data.size();
567
568   slotData(0, bytes, len);
569
570   if (aborted) {
571     return;
572   }
573
574   slotFinished(0);
575 }
576
577 void XMLHttpRequest::slotFinished(KIO::Job *)
578 {
579   if (responseHeaders.isEmpty() && job)
580     responseHeaders = job->queryMetaData("HTTP-Headers");
581
582   if (state < Loaded)
583     changeState(Loaded);
584
585   if (decoder)
586     response += decoder->flush();
587
588   removeFromRequestsByDocument();
589   job = 0;
590
591   changeState(Completed);
592   decoder = 0;
593
594   JSLock lock;
595   gcUnprotect(this);
596 }
597
598 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
599 {
600   if (!urlMatchesDocumentDomain(url)) {
601     abort();
602   }
603 }
604
605 void XMLHttpRequest::slotData(KIO::Job*, const char *data, int len)
606 {
607   if (responseHeaders.isEmpty() && job)
608     responseHeaders = job->queryMetaData("HTTP-Headers");
609
610   if (state < Loaded)
611     changeState(Loaded);
612   
613   if (!decoder) {
614     encoding = getCharset(MIMETypeOverride);
615     if (encoding.isEmpty())
616       encoding = getCharset(getResponseHeader("Content-Type"));
617     if (encoding.isEmpty() && job)
618       encoding = job->queryMetaData("charset");
619     
620     decoder = new Decoder;
621     if (!encoding.isEmpty())
622       decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
623     else {
624       // only allow Decoder to look inside the response if it's XML
625       decoder->setEncoding("UTF-8", responseIsXML() ? Decoder::DefaultEncoding : Decoder::EncodingFromHTTPHeader);
626     }
627   }
628   if (len == 0)
629     return;
630
631   if (len == -1)
632     len = strlen(data);
633
634   QString decoded = decoder->decode(data, len);
635
636   response += decoded;
637
638   if (!aborted) {
639     changeState(Interactive);
640   }
641 }
642
643 QPtrDict< QPtrDict<XMLHttpRequest> > &XMLHttpRequest::requestsByDocument()
644 {
645     static QPtrDict< QPtrDict<XMLHttpRequest> > dictionary;
646     return dictionary;
647 }
648
649 void XMLHttpRequest::addToRequestsByDocument()
650 {
651   assert(doc);
652
653   QPtrDict<XMLHttpRequest> *requests = requestsByDocument().find(doc);
654   if (!requests) {
655     requests = new QPtrDict<XMLHttpRequest>;
656     requestsByDocument().insert(doc, requests);
657   }
658
659   assert(requests->find(this) == 0);
660   requests->insert(this, this);
661 }
662
663 void XMLHttpRequest::removeFromRequestsByDocument()
664 {
665   assert(doc);
666
667   QPtrDict<XMLHttpRequest> *requests = requestsByDocument().find(doc);
668
669   // Since synchronous loads are not added to requestsByDocument(), we need to make sure we found the request.
670   if (!requests || !requests->find(this))
671     return;
672
673   requests->remove(this);
674
675   if (requests->isEmpty()) {
676     requestsByDocument().remove(doc);
677     delete requests;
678   }
679 }
680
681 void XMLHttpRequest::cancelRequests(DOM::DocumentImpl *d)
682 {
683   while (QPtrDict<XMLHttpRequest> *requests = requestsByDocument().find(d))
684     QPtrDictIterator<XMLHttpRequest>(*requests).current()->abort();
685 }
686
687 JSValue *XMLHttpRequestProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
688 {
689   if (!thisObj->inherits(&XMLHttpRequest::info))
690     return throwError(exec, TypeError);
691
692   XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj);
693
694   switch (id) {
695   case XMLHttpRequest::Abort: {
696     request->abort();
697     return jsUndefined();
698   }
699   case XMLHttpRequest::GetAllResponseHeaders: {
700     if (args.size() != 0) {
701       return jsUndefined();
702     }
703
704     return request->getAllResponseHeaders();
705   }
706   case XMLHttpRequest::GetResponseHeader: {
707     if (args.size() != 1) {
708       return jsUndefined();
709     }
710     
711     QString header = request->getResponseHeader(args[0]->toString(exec).qstring());
712
713     if (header.isNull())
714       return jsUndefined();
715
716     return jsString(header);
717   }
718   case XMLHttpRequest::Open:
719     {
720       if (args.size() < 2 || args.size() > 5) {
721         return jsUndefined();
722       }
723     
724       QString method = args[0]->toString(exec).qstring();
725       KURL url = KURL(Window::retrieveActive(exec)->part()->xmlDocImpl()->completeURL(args[1]->toString(exec).qstring()));
726
727       bool async = true;
728       if (args.size() >= 3) {
729         async = args[2]->toBoolean(exec);
730       }
731     
732       if (args.size() >= 4) {
733         url.setUser(args[3]->toString(exec).qstring());
734       }
735       
736       if (args.size() >= 5) {
737         url.setPass(args[4]->toString(exec).qstring());
738       }
739
740       request->open(method, url, async);
741
742       return jsUndefined();
743     }
744   case XMLHttpRequest::Send:
745     {
746       if (args.size() > 1) {
747         return jsUndefined();
748       }
749
750       if (request->state != Loading) {
751         return jsUndefined();
752       }
753
754       QString body;
755
756       if (args.size() >= 1) {
757         if (args[0]->toObject(exec)->inherits(&DOMDocument::info)) {
758           DocumentImpl *doc = static_cast<DocumentImpl *>(static_cast<DOMDocument *>(args[0]->toObject(exec))->impl());
759           body = doc->toString().qstring();
760         } else {
761           // converting certain values (like null) to object can set an exception
762           exec->clearException();
763           body = args[0]->toString(exec).qstring();
764         }
765       }
766
767       request->send(body);
768
769       return jsUndefined();
770     }
771   case XMLHttpRequest::SetRequestHeader: {
772     if (args.size() != 2) {
773       return jsUndefined();
774     }
775     
776     request->setRequestHeader(args[0]->toString(exec).qstring(), args[1]->toString(exec).qstring());
777     
778     return jsUndefined();
779   }
780   case XMLHttpRequest::OverrideMIMEType: {
781     if (args.size() != 1) {
782       return jsUndefined();
783     }
784     request->MIMETypeOverride = args[0]->toString(exec).qstring();
785     return jsUndefined();
786   }
787   }
788
789   return jsUndefined();
790 }
791
792 } // end namespace