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