da5da1c5d74b0c0ee8996a67880659f166836ec4
[WebKit-https.git] / WebCore / xml / xmlhttprequest.cpp
1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 2004, 2006 Apple Computer, Inc.
4  *  Copyright (C) 2005, 2006 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 "CString.h"
25 #include "Cache.h"
26 #include "DOMImplementation.h"
27 #include "Decoder.h"
28 #include "Event.h"
29 #include "EventListener.h"
30 #include "EventNames.h"
31 #include "ExceptionCode.h"
32 #include "FormData.h"
33 #include "HTMLDocument.h"
34 #include "LoaderFunctions.h"
35 #include "PlatformString.h"
36 #include "RegularExpression.h"
37 #include "ResourceLoader.h"
38 #include "TextEncoding.h"
39 #include "kjs_binding.h"
40 #include <kjs/protect.h>
41 #include <wtf/Vector.h>
42
43 namespace WebCore {
44
45 using namespace EventNames;
46
47 typedef HashSet<XMLHttpRequest*> RequestsSet;
48
49 static HashMap<Document*, RequestsSet*>& requestsByDocument()
50 {
51     static HashMap<Document*, RequestsSet*> map;
52     return map;
53 }
54
55 static void addToRequestsByDocument(Document* doc, XMLHttpRequest* req)
56 {
57     ASSERT(doc);
58     ASSERT(req);
59
60     RequestsSet* requests = requestsByDocument().get(doc);
61     if (!requests) {
62         requests = new RequestsSet;
63         requestsByDocument().set(doc, requests);
64     }
65
66     ASSERT(!requests->contains(req));
67     requests->add(req);
68 }
69
70 static void removeFromRequestsByDocument(Document* doc, XMLHttpRequest* req)
71 {
72     ASSERT(doc);
73     ASSERT(req);
74
75     RequestsSet* requests = requestsByDocument().get(doc);
76     ASSERT(requests);
77     ASSERT(requests->contains(req));
78     requests->remove(req);
79     if (requests->isEmpty()) {
80         requestsByDocument().remove(doc);
81         delete requests;
82     }
83 }
84
85 static inline String getMIMEType(const String& contentTypeString)
86 {
87     String mimeType;
88     unsigned length = contentTypeString.length();
89     for (unsigned offset = 0; offset < length; offset++) {
90         UChar c = contentTypeString[offset];
91         if (c == ';')
92             break;
93         else if (DeprecatedChar(c).isSpace()) // FIXME: This seems wrong, " " is an invalid MIME type character according to RFC 2045.  bug 8644
94             continue;
95         // FIXME: This is a very slow way to build a string, given WebCore::String's implementation.
96         mimeType += String(&c, 1);
97     }
98     return mimeType;
99 }
100
101 static String getCharset(const String& contentTypeString)
102 {
103     int pos = 0;
104     int length = (int)contentTypeString.length();
105     
106     while (pos < length) {
107         pos = contentTypeString.find("charset", pos, false);
108         if (pos <= 0)
109             return String();
110         
111         // is what we found a beginning of a word?
112         if (contentTypeString[pos-1] > ' ' && contentTypeString[pos-1] != ';') {
113             pos += 7;
114             continue;
115         }
116         
117         pos += 7;
118
119         // skip whitespace
120         while (pos != length && contentTypeString[pos] <= ' ')
121             ++pos;
122     
123         if (contentTypeString[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others
124             continue;
125
126         while (pos != length && (contentTypeString[pos] <= ' ' || contentTypeString[pos] == '"' || contentTypeString[pos] == '\''))
127             ++pos;
128
129         // we don't handle spaces within quoted parameter values, because charset names cannot have any
130         int endpos = pos;
131         while (pos != length && contentTypeString[endpos] > ' ' && contentTypeString[endpos] != '"' && contentTypeString[endpos] != '\'' && contentTypeString[endpos] != ';')
132             ++endpos;
133     
134         return contentTypeString.substring(pos, endpos-pos);
135     }
136     
137     return String();
138 }
139
140 XMLHttpRequestState XMLHttpRequest::getReadyState() const
141 {
142     return m_state;
143 }
144
145 String XMLHttpRequest::getResponseText() const
146 {
147     return m_response;
148 }
149
150 Document* XMLHttpRequest::getResponseXML() const
151 {
152     if (m_state != Loaded)
153         return 0;
154
155     if (!m_createdDocument) {
156         if (responseIsXML()) {
157             m_responseXML = m_doc->implementation()->createDocument();
158             m_responseXML->open();
159             m_responseXML->write(m_response);
160             m_responseXML->finishParsing();
161             m_responseXML->close();
162         }
163         m_createdDocument = true;
164     }
165
166     return m_responseXML.get();
167 }
168
169 EventListener* XMLHttpRequest::onReadyStateChangeListener() const
170 {
171     return m_onReadyStateChangeListener.get();
172 }
173
174 void XMLHttpRequest::setOnReadyStateChangeListener(EventListener* eventListener)
175 {
176     m_onReadyStateChangeListener = eventListener;
177 }
178
179 EventListener* XMLHttpRequest::onLoadListener() const
180 {
181     return m_onLoadListener.get();
182 }
183
184 void XMLHttpRequest::setOnLoadListener(EventListener* eventListener)
185 {
186     m_onLoadListener = eventListener;
187 }
188
189 XMLHttpRequest::XMLHttpRequest(Document* d)
190     : m_doc(d)
191     , m_async(true)
192     , m_loader(0)
193     , m_state(Uninitialized)
194     , m_response("", 0)
195     , m_createdDocument(false)
196     , m_aborted(false)
197 {
198     ASSERT(m_doc);
199     addToRequestsByDocument(m_doc, this);
200 }
201
202 XMLHttpRequest::~XMLHttpRequest()
203 {
204     if (m_doc)
205         removeFromRequestsByDocument(m_doc, this);
206 }
207
208 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
209 {
210     if (m_state != newState) {
211         m_state = newState;
212         callReadyStateChangeListener();
213     }
214 }
215
216 void XMLHttpRequest::callReadyStateChangeListener()
217 {
218     if (m_doc && m_doc->frame() && m_onReadyStateChangeListener)
219         m_onReadyStateChangeListener->handleEvent(new Event(readystatechangeEvent, true, true), true);
220     
221     if (m_doc && m_doc->frame() && m_state == Loaded && m_onLoadListener)
222         m_onLoadListener->handleEvent(new Event(loadEvent, true, true), true);
223 }
224
225 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& url) const
226 {
227     KURL documentURL(m_doc->URL());
228
229     // a local file can load anything
230     if (documentURL.protocol().lower() == "file")
231         return true;
232
233     // but a remote document can only load from the same port on the server
234     if (documentURL.protocol().lower() == url.protocol().lower()
235             && documentURL.host().lower() == url.host().lower()
236             && documentURL.port() == url.port())
237         return true;
238
239     return false;
240 }
241
242 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
243 {
244     abort();
245     m_aborted = false;
246
247     // clear stuff from possible previous load
248     m_requestHeaders = DeprecatedString();
249     m_responseHeaders = String();
250     m_response = "";
251     m_createdDocument = false;
252     m_responseXML = 0;
253
254     changeState(Uninitialized);
255
256     if (!urlMatchesDocumentDomain(url)) {
257         ec = PERMISSION_DENIED;
258         return;
259     }
260
261     m_url = url;
262
263     if (!user.isNull())
264         m_url.setUser(user.deprecatedString());
265
266     if (!password.isNull())
267         m_url.setPass(password.deprecatedString());
268
269     // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
270     String methodUpper(method.upper());
271     if (methodUpper == "CONNECT" || methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
272         || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE" 
273         || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT" 
274         || methodUpper == "TRACE" || methodUpper == "UNLOCK")
275         m_method = methodUpper.deprecatedString();
276     else
277         m_method = method.deprecatedString();
278
279     m_async = async;
280
281     changeState(Open);
282 }
283
284 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
285 {
286     if (!m_doc)
287         return;
288
289     if (m_state != Open) {
290         ec = INVALID_STATE_ERR;
291         return;
292     }
293   
294     // FIXME: Should this abort or raise an exception instead if we already have a m_loader going?
295     if (m_loader)
296         return;
297
298     m_aborted = false;
299
300     if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && (m_url.protocol().lower() == "http" || m_url.protocol().lower() == "https")) {
301         String contentType = getRequestHeader("Content-Type");
302         String charset;
303         if (contentType.isEmpty()) {
304             ExceptionCode ec = 0;
305             setRequestHeader("Content-Type", "application/xml", ec);
306             ASSERT(ec == 0);
307         } else
308             charset = getCharset(contentType);
309       
310         if (charset.isEmpty())
311             charset = "UTF-8";
312       
313         TextEncoding m_encoding(charset);
314         if (!m_encoding.isValid()) // FIXME: report an error?
315             m_encoding = UTF8Encoding();
316
317         m_loader = new ResourceLoader(m_async ? this : 0, m_method, m_url, m_encoding.encode(body.characters(), body.length()));
318     } else {
319         // FIXME: HEAD requests just crash; see <rdar://4460899> and the commented out tests in http/tests/xmlhttprequest/methods.html.
320         if (m_method == "HEAD")
321             m_method = "GET";
322         m_loader = new ResourceLoader(m_async ? this : 0, m_method, m_url);
323     }
324
325     if (m_requestHeaders.length())
326         m_loader->addMetaData("customHTTPHeader", m_requestHeaders);
327
328     if (!m_async) {
329         Vector<char> data;
330         KURL finalURL;
331         DeprecatedString headers;
332
333         {
334             // avoid deadlock in case the loader wants to use JS on a background thread
335             KJS::JSLock::DropAllLocks dropLocks;
336             data = ServeSynchronousRequest(Cache::loader(), m_doc->docLoader(), m_loader, finalURL, headers);
337         }
338
339         m_loader = 0;
340         processSyncLoadResults(data, finalURL, headers);
341     
342         return;
343     }
344
345     // Neither this object nor the JavaScript wrapper should be deleted while
346     // a request is in progress because we need to keep the listeners alive,
347     // and they are referenced by the JavaScript wrapper.
348     ref();
349     {
350         KJS::JSLock lock;
351         gcProtectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
352     }
353   
354     if (!m_loader->start(m_doc->docLoader())) {
355         LOG_ERROR("Failed to send an XMLHttpRequest for %s", m_url.url().ascii());
356         m_loader = 0;
357     }
358 }
359
360 void XMLHttpRequest::abort()
361 {
362     bool hadLoader = m_loader;
363
364     if (hadLoader) {
365         m_loader->kill();
366         m_loader = 0;
367     }
368     m_decoder = 0;
369     m_aborted = true;
370
371     if (hadLoader) {
372         {
373             KJS::JSLock lock;
374             gcUnprotectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
375         }
376         deref();
377     }
378 }
379
380 void XMLHttpRequest::overrideMIMEType(const String& override)
381 {
382     m_mimeTypeOverride = override;
383 }
384
385 void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec)
386 {
387     if (m_state != Open) {
388         ec = INVALID_STATE_ERR;
389         return;
390     }
391
392     if (m_requestHeaders.length() > 0)
393         m_requestHeaders += "\r\n";
394     m_requestHeaders += name.deprecatedString();
395     m_requestHeaders += ": ";
396     m_requestHeaders += value.deprecatedString();
397 }
398
399 DeprecatedString XMLHttpRequest::getRequestHeader(const DeprecatedString& name) const
400 {
401     return getSpecificHeader(m_requestHeaders, name);
402 }
403
404 String XMLHttpRequest::getAllResponseHeaders() const
405 {
406     if (m_responseHeaders.isEmpty())
407         return String();
408
409     int endOfLine = m_responseHeaders.find("\n");
410     if (endOfLine == -1)
411         return String();
412
413     return m_responseHeaders.substring(endOfLine + 1) + "\n";
414 }
415
416 String XMLHttpRequest::getResponseHeader(const String& name) const
417 {
418     return getSpecificHeader(m_responseHeaders.deprecatedString(), name.deprecatedString());
419 }
420
421 DeprecatedString XMLHttpRequest::getSpecificHeader(const DeprecatedString& headers, const DeprecatedString& name)
422 {
423     if (headers.isEmpty())
424         return DeprecatedString();
425
426     RegularExpression headerLinePattern(name + ":", false);
427
428     int matchLength;
429     int headerLinePos = headerLinePattern.match(headers, 0, &matchLength);
430     while (headerLinePos != -1) {
431         if (headerLinePos == 0 || headers[headerLinePos-1] == '\n')
432             break;
433         headerLinePos = headerLinePattern.match(headers, headerLinePos + 1, &matchLength);
434     }
435     if (headerLinePos == -1)
436         return DeprecatedString();
437     
438     int endOfLine = headers.find("\n", headerLinePos + matchLength);
439     return headers.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).stripWhiteSpace();
440 }
441
442 bool XMLHttpRequest::responseIsXML() const
443 {
444     String mimeType = getMIMEType(m_mimeTypeOverride);
445     if (mimeType.isEmpty())
446         mimeType = getMIMEType(getResponseHeader("Content-Type"));
447     if (mimeType.isEmpty())
448         mimeType = "text/xml";
449     return DOMImplementation::isXMLMIMEType(mimeType);
450 }
451
452 int XMLHttpRequest::getStatus(ExceptionCode& ec) const
453 {
454     if (m_state == Uninitialized)
455         return 0;
456     
457     if (m_responseHeaders.isEmpty()) {
458         if (m_state != Receiving && m_state != Loaded)
459             // status MUST be available in these states, but we don't get any headers from non-HTTP requests
460             ec = INVALID_STATE_ERR;
461         return 0;
462     }
463     
464     int endOfLine = m_responseHeaders.find("\n");
465     String firstLine = endOfLine == -1 ? m_responseHeaders : m_responseHeaders.substring(0, endOfLine);
466     int codeStart = firstLine.find(" ");
467     int codeEnd = firstLine.find(" ", codeStart + 1);
468     ASSERT(codeStart != -1);
469     ASSERT(codeEnd != -1);
470   
471     String number = firstLine.substring(codeStart + 1, codeEnd - (codeStart + 1));
472     bool ok = false;
473     int code = number.toInt(&ok);
474     ASSERT(ok);
475
476     return code;
477 }
478
479 String XMLHttpRequest::getStatusText(ExceptionCode& ec) const
480 {
481     if (m_state == Uninitialized)
482         return "";
483     
484     if (m_responseHeaders.isEmpty()) {
485         if (m_state != Receiving && m_state != Loaded)
486             // statusText MUST be available in these states, but we don't get any headers from non-HTTP requests
487             ec = INVALID_STATE_ERR;
488         return String();
489     }
490   
491     int endOfLine = m_responseHeaders.find("\n");
492     String firstLine = endOfLine == -1 ? m_responseHeaders : m_responseHeaders.substring(0, endOfLine);
493     int codeStart = firstLine.find(" ");
494     int codeEnd = firstLine.find(" ", codeStart + 1);
495     ASSERT(codeStart != -1);
496     ASSERT(codeEnd != -1);
497   
498     return firstLine.substring(codeEnd + 1, endOfLine - (codeEnd + 1)).stripWhiteSpace();
499 }
500
501 void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const KURL& finalURL, const DeprecatedString& headers)
502 {
503     if (!urlMatchesDocumentDomain(finalURL)) {
504         abort();
505         return;
506     }
507
508     m_responseHeaders = headers;
509     changeState(Sent);
510     if (m_aborted)
511         return;
512
513     const char* bytes = static_cast<const char*>(data.data());
514     int len = static_cast<int>(data.size());
515
516     receivedData(0, bytes, len);
517     if (m_aborted)
518         return;
519
520     receivedAllData(0);
521 }
522
523 void XMLHttpRequest::receivedAllData(ResourceLoader*)
524 {
525     if (m_responseHeaders.isEmpty() && m_loader)
526         m_responseHeaders = m_loader->queryMetaData("HTTP-Headers");
527
528     if (m_state < Sent)
529         changeState(Sent);
530
531     if (m_decoder)
532         m_response += m_decoder->flush();
533
534     bool hadLoader = m_loader;
535     m_loader = 0;
536
537     changeState(Loaded);
538     m_decoder = 0;
539
540     if (hadLoader) {
541         {
542             KJS::JSLock lock;
543             gcUnprotectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this));
544         }
545         deref();
546     }
547 }
548
549 void XMLHttpRequest::receivedRedirect(ResourceLoader*, const KURL& m_url)
550 {
551     if (!urlMatchesDocumentDomain(m_url))
552         abort();
553 }
554
555 void XMLHttpRequest::receivedData(ResourceLoader*, const char* data, int len)
556 {
557     if (m_responseHeaders.isEmpty() && m_loader)
558         m_responseHeaders = m_loader->queryMetaData("HTTP-Headers");
559
560     if (m_state < Sent)
561         changeState(Sent);
562   
563     if (!m_decoder) {
564         m_encoding = getCharset(m_mimeTypeOverride);
565         if (m_encoding.isEmpty())
566             m_encoding = getCharset(getResponseHeader("Content-Type"));
567         if (m_encoding.isEmpty() && m_loader)
568             m_encoding = m_loader->queryMetaData("charset");
569     
570         if (!m_encoding.isEmpty())
571             m_decoder = new Decoder("text/plain", m_encoding);
572         else if (responseIsXML())
573             // allow Decoder to look inside the m_response if it's XML
574             m_decoder = new Decoder("application/xml");
575         else
576             m_decoder = new Decoder("text/plain", "UTF-8");
577     }
578     if (len == 0)
579         return;
580
581     if (len == -1)
582         len = strlen(data);
583
584     String decoded = m_decoder->decode(data, len);
585
586     m_response += decoded;
587
588     if (!m_aborted) {
589         if (m_state != Receiving)
590             changeState(Receiving);
591         else
592             // Firefox calls readyStateChanged every time it receives data, 4449442
593             callReadyStateChangeListener();
594     }
595 }
596
597 void XMLHttpRequest::cancelRequests(Document* m_doc)
598 {
599     RequestsSet* requests = requestsByDocument().get(m_doc);
600     if (!requests)
601         return;
602     RequestsSet copy = *requests;
603     RequestsSet::const_iterator end = copy.end();
604     for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it)
605         (*it)->abort();
606 }
607
608 void XMLHttpRequest::detachRequests(Document* m_doc)
609 {
610     RequestsSet* requests = requestsByDocument().get(m_doc);
611     if (!requests)
612         return;
613     requestsByDocument().remove(m_doc);
614     RequestsSet::const_iterator end = requests->end();
615     for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) {
616         (*it)->m_doc = 0;
617         (*it)->abort();
618     }
619     delete requests;
620 }
621
622 } // end namespace