2c3b5012648fd053162f804b75141f90dccc6edc
[WebKit-https.git] / WebCore / khtml / ecma / xmlhttprequest.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 2003 Apple Computer, Inc.
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 "xmlhttprequest.h"
22 #include "xmlhttprequest.lut.h"
23 #include "kjs_window.h"
24 #include "kjs_events.h"
25
26 #include "dom/dom_doc.h"
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
33 #include "khtml_part.h"
34 #include "khtmlview.h"
35
36 #include <kdebug.h>
37 #include <kio/job.h>
38 #include <qobject.h>
39
40 #ifdef APPLE_CHANGES
41 #include "KWQLoader.h"
42 #endif
43
44 using namespace KJS;
45 using khtml::Decoder;
46
47 ////////////////////// XMLHttpRequest Object ////////////////////////
48
49 /* Source for XMLHttpRequestProtoTable.
50 @begin XMLHttpRequestProtoTable 7
51   abort                 XMLHttpRequest::Abort                   DontDelete|Function 0
52   getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders   DontDelete|Function 0
53   getResponseHeader     XMLHttpRequest::GetResponseHeader       DontDelete|Function 1
54   open                  XMLHttpRequest::Open                    DontDelete|Function 5
55   send                  XMLHttpRequest::Send                    DontDelete|Function 1
56   setRequestHeader      XMLHttpRequest::SetRequestHeader        DontDelete|Function 2
57 @end
58 */
59 DEFINE_PROTOTYPE("XMLHttpRequest",XMLHttpRequestProto)
60 IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
61 IMPLEMENT_PROTOTYPE(XMLHttpRequestProto,XMLHttpRequestProtoFunc)
62
63 namespace KJS {
64
65 XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject) 
66 {
67   jsObject = _jsObject; 
68 }
69
70 #if 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( KIO::Job* job )
83 {
84   jsObject->slotFinished(job); 
85 }
86
87 void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KURL& url)
88
89   jsObject->slotRedirection( job, url ); 
90 }
91
92 XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *, const DOM::Document &d)
93     : ObjectImp(), doc(d)
94 {
95 }
96
97 bool XMLHttpRequestConstructorImp::implementsConstruct() const
98 {
99   return true;
100 }
101
102 Object XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
103 {
104   return Object(new XMLHttpRequest(exec, doc));
105 }
106
107 const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
108
109 /* Source for XMLHttpRequestTable.
110 @begin XMLHttpRequestTable 7
111   readyState            XMLHttpRequest::ReadyState              DontDelete|ReadOnly
112   responseText          XMLHttpRequest::ResponseText            DontDelete|ReadOnly
113   responseXML           XMLHttpRequest::ResponseXML             DontDelete|ReadOnly
114   status                XMLHttpRequest::Status                  DontDelete|ReadOnly
115   statusText            XMLHttpRequest::StatusText              DontDelete|ReadOnly
116   onreadystatechange    XMLHttpRequest::Onreadystatechange      DontDelete
117   onload                XMLHttpRequest::Onload                  DontDelete
118 @end
119 */
120
121 Value XMLHttpRequest::tryGet(ExecState *exec, const Identifier &propertyName) const
122 {
123   return DOMObjectLookupGetValue<XMLHttpRequest,DOMObject>(exec, propertyName, &XMLHttpRequestTable, this);
124 }
125
126 Value XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
127 {
128   switch (token) {
129   case ReadyState:
130     return Number(state);
131   case ResponseText:
132     return getStringOrNull(DOM::DOMString(response));
133   case ResponseXML:
134     if (state != Completed) {
135       return Undefined();
136     }
137     if (!createdDocument) {
138       QString mimeType = "text/xml";
139       
140       Value header = getResponseHeader("Content-Type");
141       if (header.type() != UndefinedType) {
142         mimeType = QStringList::split(";", header.toString(exec).qstring())[0].stripWhiteSpace();
143       }
144       
145       if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
146         responseXML = DOM::Document(doc->implementation()->createDocument());
147       } else {
148         responseXML = DOM::Document(doc->implementation()->createHTMLDocument());
149       }
150
151       DOM::DocumentImpl *docImpl = static_cast<DOM::DocumentImpl *>(responseXML.handle());
152       
153       docImpl->open();
154       docImpl->write(response);
155       docImpl->finishParsing();
156       docImpl->close();
157       createdDocument = true;
158     }
159
160     return getDOMNode(exec,responseXML);
161   case Status:
162     return getStatus();
163   case StatusText:
164     return getStatusText();
165   case Onreadystatechange:
166    if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObjImp()) {
167      return onReadyStateChangeListener->listenerObj();
168    } else {
169      return Null();
170    }
171   case Onload:
172    if (onLoadListener && onLoadListener->listenerObjImp()) {
173      return onLoadListener->listenerObj();
174    } else {
175      return Null();
176    }
177   default:
178     kdWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token << endl;
179     return Value();
180   }
181 }
182
183 void XMLHttpRequest::tryPut(ExecState *exec, const Identifier &propertyName, const Value& value, int attr)
184 {
185   DOMObjectLookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
186 }
187
188 void XMLHttpRequest::putValue(ExecState *exec, int token, const Value& value, int /*attr*/)
189 {
190   switch(token) {
191   case Onreadystatechange:
192     onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
193     if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
194     break;
195   case Onload:
196     onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
197     if (onLoadListener) onLoadListener->ref();
198     break;
199   default:
200     kdWarning() << "HTMLDocument::putValue unhandled token " << token << endl;
201   }
202 }
203
204 XMLHttpRequest::XMLHttpRequest(ExecState *exec, const DOM::Document &d)
205   : DOMObject(XMLHttpRequestProto::self(exec)),
206     qObject(new XMLHttpRequestQObject(this)),
207     doc(static_cast<DOM::DocumentImpl*>(d.handle())),
208     async(true),
209     job(0),
210     state(Uninitialized),
211     onReadyStateChangeListener(0),
212     onLoadListener(0),
213     decoder(0),
214     createdDocument(false)
215 {
216 }
217
218 XMLHttpRequest::~XMLHttpRequest()
219 {
220   if (decoder) {
221     decoder->deref();
222   }
223 }
224
225 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
226 {
227   if (state != newState) {
228     state = newState;
229     
230     if (onReadyStateChangeListener != 0 && doc->part()) {
231       DOM::Event ev = doc->part()->document().createEvent("HTMLEvents");
232       ev.initEvent("readystatechange", true, true);
233       onReadyStateChangeListener->handleEvent(ev, true);
234     }
235     
236     if (state == Completed && onLoadListener != 0 && doc->part()) {
237       DOM::Event ev = doc->part()->document().createEvent("HTMLEvents");
238       ev.initEvent("load", true, true);
239       onLoadListener->handleEvent(ev, true);
240     }
241   }
242 }
243
244 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& _url) const
245 {
246   KURL documentURL(doc->URL());
247
248   // a local file can load anything
249   if (documentURL.protocol().lower() == "file") {
250     return true;
251   }
252
253   // but a remote document can only load from the same port on the server
254   if (documentURL.protocol().lower() == _url.protocol().lower() &&
255       documentURL.host().lower() == _url.host().lower() &&
256       documentURL.port() == _url.port()) {
257     return true;
258   }
259
260   return false;
261 }
262
263 void XMLHttpRequest::open(const QString& _method, const KURL& _url, bool _async)
264 {
265   if (!urlMatchesDocumentDomain(_url)) {
266     return;
267   }
268
269   method = _method;
270   url = _url;
271   async = _async;
272
273   changeState(Loading);
274 }
275
276 void XMLHttpRequest::send(const QString& _body)
277 {
278   if (method.lower() == "post" && (url.protocol().lower() == "http" || url.protocol().lower() == "https") ) {
279       // FIXME: determine post encoding correctly by looking in headers for charset
280       job = KIO::http_post( url, QCString(_body.utf8()), false );
281   }
282   else
283   {
284      job = KIO::get( url, false, false );
285   }
286   if (requestHeaders.length() > 0) {
287     job->addMetaData("customHTTPHeader", requestHeaders);
288   }
289
290   qObject->connect( job, SIGNAL( result( KIO::Job* ) ),
291                     SLOT( slotFinished( KIO::Job* ) ) );
292 #if APPLE_CHANGES
293   qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
294                     SLOT( slotData( KIO::Job*, const char*, int ) ) );
295 #else
296   qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
297                     SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
298 #endif
299   qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KURL& ) ),
300                     SLOT( slotRedirection(KIO::Job*, const KURL&) ) );
301
302 #ifdef APPLE_CHANGES
303   KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
304 #else 
305   KIO::Scheduler::scheduleJob( job );
306 #endif
307 }
308
309 void XMLHttpRequest::abort()
310 {
311   if (job) {
312     job->kill();
313     job = 0;
314   }
315 }
316
317 void XMLHttpRequest::setRequestHeader(const QString& name, const QString &value)
318 {
319   if (requestHeaders.length() > 0) {
320     requestHeaders += "\r\n";
321   }
322   requestHeaders += name;
323   requestHeaders += ": ";
324   requestHeaders += value;
325 }
326
327 Value XMLHttpRequest::getAllResponseHeaders() const
328 {
329   if (responseHeaders.isEmpty()) {
330     return Undefined();
331   }
332
333   int endOfLine = responseHeaders.find("\n");
334
335   if (endOfLine == -1) {
336     return Undefined();
337   }
338
339   return String(responseHeaders.mid(endOfLine + 1) + "\n");
340 }
341
342 Value XMLHttpRequest::getResponseHeader(const QString& name) const
343 {
344   if (responseHeaders.isEmpty()) {
345     return Undefined();
346   }
347
348   QRegExp headerLinePattern(name + ":", false);
349
350   int matchLength;
351   int headerLinePos = headerLinePattern.match(responseHeaders, 0, &matchLength);
352   while (headerLinePos != -1) {
353     if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
354       break;
355     }
356     
357     headerLinePos = headerLinePattern.match(responseHeaders, headerLinePos + 1, &matchLength);
358   }
359
360
361   if (headerLinePos == -1) {
362     return Undefined();
363   }
364     
365   int endOfLine = responseHeaders.find("\n", headerLinePos + matchLength);
366
367   return String(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace());
368 }
369
370 Value XMLHttpRequest::getStatus() const
371 {
372   if (responseHeaders.isEmpty()) {
373     return Undefined();
374   }
375   
376   int endOfLine = responseHeaders.find("\n");
377   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
378   int codeStart = firstLine.find(" ");
379   int codeEnd = firstLine.find(" ", codeStart + 1);
380   
381   if (codeStart == -1 || codeEnd == -1) {
382     return Undefined();
383   }
384   
385   QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
386   
387   bool ok = false;
388   int code = number.toInt(&ok);
389   if (!ok) {
390     return Undefined();
391   }
392
393   return Number(code);
394 }
395
396 Value XMLHttpRequest::getStatusText() const
397 {
398   if (responseHeaders.isEmpty()) {
399     return Undefined();
400   }
401   
402   int endOfLine = responseHeaders.find("\n");
403   QString firstLine = endOfLine == -1 ? responseHeaders : responseHeaders.left(endOfLine);
404   
405   return String(firstLine);
406 }
407    
408 void XMLHttpRequest::slotFinished(KIO::Job *)
409 {
410   if (decoder) {
411     response += decoder->flush();
412   }
413
414   changeState(Completed);
415   job = 0;
416   
417   if (decoder) {
418     decoder->deref();
419     decoder = 0;
420   }
421 }
422
423 void XMLHttpRequest::slotRedirection(KIO::Job*, const KURL& url)
424 {
425   if (!urlMatchesDocumentDomain(url)) {
426     job->kill();
427     job = 0;
428   }
429 }
430
431 #if APPLE_CHANGES
432 void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
433 #else
434 void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
435 #endif
436 {
437   if (state < Loaded) {
438     responseHeaders = job->queryMetaData("HTTP-Headers");
439     changeState(Loaded);
440   }
441   
442 #if !APPLE_CHANGES
443   const char *data = (const char *)_data.data();
444   int len = (int)_data.size();
445 #endif
446
447   if ( decoder == NULL ) {
448     decoder = new Decoder;
449     if (!encoding.isNull())
450       decoder->setEncoding(encoding.latin1(), Decoder::EncodingFromHTTPHeader);
451     else {
452       // FIXME: Inherit the default encoding from the parent document?
453     }
454   }
455   if (len == 0)
456     return;
457
458   if (len == -1)
459     len = strlen(data);
460
461   QString decoded = decoder->decode(data, len);
462
463   response += decoded;
464
465   if (job != 0) {
466     changeState(Interactive);
467   }
468 }
469
470 Value XMLHttpRequestProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
471 {
472   if (!thisObj.inherits(&XMLHttpRequest::info)) {
473     Object err = Error::create(exec,TypeError);
474     exec->setException(err);
475     return err;
476   }
477
478   XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj.imp());
479
480   switch (id) {
481   case XMLHttpRequest::Abort:
482     request->abort();
483     return Undefined();
484   case XMLHttpRequest::GetAllResponseHeaders:
485     if (args.size() != 0) {
486       return Undefined();
487     }
488
489     return request->getAllResponseHeaders();
490   case XMLHttpRequest::GetResponseHeader:
491     if (args.size() != 1) {
492       return Undefined();
493     }
494
495     return request->getResponseHeader(args[0].toString(exec).qstring());
496   case XMLHttpRequest::Open: 
497     {
498       if (args.size() < 2 || args.size() > 5) {
499         return Undefined();
500       }
501     
502       if (request->state != Uninitialized) {
503         return Undefined();
504       }
505
506       QString method = args[0].toString(exec).qstring();
507       KURL url = KURL(Window::retrieveActive(exec)->part()->htmlDocument().completeURL(args[1].toString(exec).qstring()).string());
508
509       bool async = true;
510       if (args.size() >= 3) {
511         async = args[2].toBoolean(exec);
512       }
513     
514       if (args.size() >= 4) {
515         url.setUser(args[3].toString(exec).qstring());
516       }
517       
518       if (args.size() >= 5) {
519         url.setPass(args[4].toString(exec).qstring());
520       }
521
522       request->open(method, url, async);
523
524       return Undefined();
525     }
526   case XMLHttpRequest::Send:
527     {
528       if (args.size() > 1) {
529         return Undefined();
530       }
531
532       if (request->state != Loading) {
533         return Undefined();
534       }
535
536       QString body;
537
538       if (args.size() >= 1) {
539         if (args[0].toObject(exec).inherits(&DOMDocument::info)) {
540           DOM::Node docNode = static_cast<KJS::DOMDocument *>(args[0].toObject(exec).imp())->toNode();
541           DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode.handle());
542           
543           try {
544             body = doc->toString().string();
545             // FIXME: also need to set content type, including encoding!
546
547           } catch(DOM::DOMException& e) {
548              Object err = Error::create(exec, GeneralError, "Exception serializing document");
549              exec->setException(err);
550           }
551         } else {
552           body = args[0].toString(exec).qstring();
553         }
554       }
555
556       request->send(body);
557
558       return Undefined();
559     }
560   case XMLHttpRequest::SetRequestHeader:
561     if (args.size() != 2) {
562       return Undefined();
563     }
564     
565     request->setRequestHeader(args[0].toString(exec).qstring(), args[1].toString(exec).qstring());
566     
567     return Undefined();
568   }
569
570   return Undefined();
571 }
572
573 }; // end namespace