Bug 3565 - Posting data via XMLHttpRequest doesn't work (wrong Content-Type)
Summary: Posting data via XMLHttpRequest doesn't work (wrong Content-Type)
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: 412
Hardware: Mac OS X 10.4
: P2 Major
Assignee: Maciej Stachowiak
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-06-16 05:25 PDT by Ondra Nekola
Modified: 2005-12-26 14:08 PST (History)
1 user (show)

See Also:


Attachments
proposed fix (6.06 KB, patch)
2005-12-22 05:29 PST, Alexey Proskuryakov
eric: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Ondra Nekola 2005-06-16 05:25:16 PDT
I have s small XML HTTP Request based webapp. It works fine in all major
browsers with exception of Safari.
When I go to the particular Webpage I get the "There was a problem retrieving
the XML data: undefined" error message. On the server side (several servers with
different versions of Jakarta Tomcat servlet container) I can read no data from
the request.

The key function is this one:
function sendRequest(url, data, reqHolder){
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        req = new ActiveXObject("Microsoft.XMLHTTP");
    }
    req.onreadystatechange = reqHolder.processRequest;
    req.open("POST", url, true);
    reqHolder.setReq(req);
    //req.setRequestHeader('If-Modified-Since', 'Wed, 15 Nov 1995 00:00:00 GMT');
    sent = req.send(data);
    return req;
}

reqHolders look like this:

function Baz(someData){
	var req
	var toProcess = someData
	
	this.setReq = function (aReq) {
		req = aReq;
	}
	this.getReq = function () { 
		return req;
	}
	
	.....

	this.toXmlRequest = function() {
		result = "<foo>\n"
		for (i in toProcess) {
			result += "<bar>"+i+"</bar>\n"
		}
		result += "</foo>"
		return result
	}
	
	
	this.processRequest = function () {
		if (req && req.readyState == 4) {
        	if (req.status == 200) {
	
       			elements = req.responseXML.getElementsByTagName("blah")
       			for (q = 0; q < elements.length; q++) {
                          ....
                        }
        	}
	}

	...

}

Tested in Safari 1.2.4 (v125.12) and 2.0 (412)
Comment 1 Joost de Valk (AlthA) 2005-06-23 10:23:25 PDT
reporter, please make a testcase, to ease confirming and testing bugfixes.
Comment 2 Maciej Stachowiak 2005-06-29 01:57:22 PDT
We need steps to reproduce that we can actually follow, in order to make progress on this bug. Either a 
site where we can try specific steps that fail, or a standalone test case (could use a tool like tcpflow to see 
if the POST data is sent or not, or a trivial PHP script or the like could be used to view the post data).
Comment 3 Ondra Nekola 2005-07-11 04:41:55 PDT
Sorry, I was on holiday, so I was not able to make the testcase. The short
testcase look like this:

File bug.html:
<html>
<head></head>
<body>
<script type="text/javascript">
function write(aText) {
	alert (aText)
}

function sendRequest(url, data, reqHolder){
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest()
    } else if (window.ActiveXObject) {
        req = new ActiveXObject("Microsoft.XMLHTTP")
    }
    req.onreadystatechange = reqHolder.processRequest
    req.open("POST", url, true)
    reqHolder.setReq(req)
    //req.setRequestHeader('If-Modified-Since', 'Wed, 15 Nov 1995 00:00:00 GMT');
    sent = req.send(data)
    return req
}

function Baz(someData){
	var req
	var toProcess = someData
	
	this.setReq = function (aReq) {
		req = aReq
	}
	this.getReq = function () { 
		return req
	}
	
	this.toXmlRequest = function() {
		result = '<?xml version="1.0"?>\n'
		result += "<foo>\n"
		for (i in toProcess) {
			result += "<bar>"+i+"</bar>\n"
		}
		result += "</foo>"
		write("I will send: " + result)
		return result
	}
	
	this.processRequest = function () {
		if (req && req.readyState == 4) {
        	if (req.status == 200) {
        		elements = req.responseXML.getElementsByTagName("bar")
        		write ("Received: " + elements.length + " bar elements")
        	}
        }
	}
}

aReqHolder = new Baz(["1", "2", "three", 4])
sendRequest("http://myserver:8080/servlet/Echo", aReqHolder.toXmlRequest(),
aReqHolder)
</script>

</body>

You can run it against any http echo server. I can't make any public for you :(
If you want to create a new one as a java servlet, you can use something like this:
/*
 * XmlrpcServlet.java
 *
 * Created on 8. &#65533;&#65533;jen 2002, 11:35
 */

package gdjweb.servlet;

import gdjweb.core.Core;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.misuzilla.text.Punycode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import domains.Domain;
import domains.DomainStatus;

public class EchoServlet extends HttpServlet {

    private static final Log log = LogFactory.getLog(EchoServlet.class);

    ServletContext           app;
    boolean                  initialised;
    Core                     core;

    /** Initializes the servlet.
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        app = config.getServletContext();
    }


    /** Processes requests for both HTTP <code>GET</code> and <code>POST</code>
methods.
     * @param request servlet request
     * @param response servlet response
     * @throws IOException 
     */
    protected void processRequest(HttpServletRequest request,
HttpServletResponse response) {

        response.setHeader("Pragma", "no-cache");
        response.setContentType("text/xml");

        try {
            InputStream is = request.getInputStream();
            Writer w = response.getWriter();
            int aByte;
            while ((aByte = is.read()) >=0) {
                w.write(aByte);
                System.out.write(aByte);
            }
            System.out.println();
            System.out.flush();
            w.flush();
        }
        catch (Throwable e) {
            log.error("Chyba: " + e, e);
        }
    }

    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, java.io.IOException {
        processRequest(request, response);
    }

    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, java.io.IOException {
        processRequest(request, response);
    }

    /** Returns a short description of the servlet.
     */
    @Override
    public String getServletInfo() {
        return "Echo";
    }

}

-------------------------------------------
Testing:
In other browsers (Fire Fox for example) I will see the message "I will send:
<....>"
In the log of the server I see the received xml and then a new browser message
appears, that tells, that 4 bar elements were received.
In Safari no data come to server.
Comment 4 Alexey Proskuryakov 2005-10-27 13:24:15 PDT
This test case works for me in both Safari 2.0.1/10.4.2 and ToT. Here is the captured traffic for ToT 
(2.0.1 is similar, but includes a weird If-Modified-Since header):

POST / HTTP/1.1
Accept: */*
Accept-Language: ru-ru
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/420+ (KHTML, like Gecko) 
Safari/412.5
Content-Type: application/x-www-form-urlencoded
Content-Length: 86
Connection: keep-alive
Host: server.com

<?xml version="1.0"?>
<foo>
<bar>0</bar>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
Comment 5 Sulka Haro 2005-11-02 00:33:25 PST
Hi! I can confirm the bug exists, and here's data on what's wrong with Safari's behavior.

Safari has two bugs related to this:

1) The Content-Type of the request is currently set to "application/x-www-form-urlencoded". All other 
browsers set it to "text/xml". This causes the parsing behavior of Safari's POST to be quite different 
from other browsers.

2) The end of line character Safari uses is different from other browsers so parsing for the POST 
parameters fails.

For a discussion about this, see http://jira.atlassian.com/browse/JRA-8354

Please up the priority on this, if the blog post about better AJAX support was indeed true, this is one of 
the biggest blockers in that regard.
Comment 6 Alexey Proskuryakov 2005-11-02 03:19:14 PST
Confirming the Content-Type difference between Firefox 1.0.6 and Safari 2.0.2. It's pretty easy to work 
around, though:
try {
   req.setRequestHeader('Content-Type','text/xml');
} catch (error) {};

I am not sure if the end-of-line issue in POSTed parameters is valid, but it appears to be out of scope of 
this bug. Here, both Firefox and Safari use LF in serialized XML.
Comment 7 Dylan Etkin 2005-11-02 14:31:38 PST
I tried setting the content type of the XMLHTTPRequest with
req.setRequestHeader('Content-Type','text/xml'); but is seemed to make no
difference, when I examined the Content-Type on the sent request it was still
application/x-www-form-urlencoded.
Comment 8 Alexey Proskuryakov 2005-11-02 21:10:32 PST
(In reply to comment #7)

Hmm, that's quite strange - worked for me in Safari 2.0.2. Actually, I have now found a hint on Wikipedia 
advising the same, so it must have worked before, as well.
Comment 9 Alexey Proskuryakov 2005-12-22 02:33:08 PST
(In reply to comment #7)
For the workaround to work, setRequestHeader() should be called after open() (and before send()).

Still a bug that needs to be fixed, of course.
Comment 10 Alexey Proskuryakov 2005-12-22 05:29:56 PST
Created attachment 5219 [details]
proposed fix
Comment 11 Alexey Proskuryakov 2005-12-22 05:32:30 PST
Comment on attachment 5219 [details]
proposed fix

Default Content-Type to application/xml (match Firefox); encode the request
body using a correct codec if a charset was specified.
Comment 12 Alexey Proskuryakov 2005-12-22 06:27:08 PST
Concerning overriding headers via setRequestHeader, see also:
https://bugzilla.mozilla.org/show_bug.cgi?id=302809
https://bugzilla.mozilla.org/show_bug.cgi?id=302263
https://bugzilla.mozilla.org/show_bug.cgi?id=308484
Comment 13 Eric Seidel (no email) 2005-12-22 06:36:28 PST
Comment on attachment 5219 [details]
proposed fix

ap and I talked over IRC.  ap noted that there could be security concerns wrt
our setRequestHeader support on XMLHTTPRequest.  He was able to convince me
however that this patch does not introduce any additional security risk, mearly
adds support for respecting content-type headers which one was already able to
set.

ap agreed to file additional bugs on the subject.

The patch itself appeared to me to be technically sound.

The FireFox bugs in question are:
https://bugzilla.mozilla.org/show_bug.cgi?id=302809
https://bugzilla.mozilla.org/show_bug.cgi?id=302263
https://bugzilla.mozilla.org/show_bug.cgi?id=308484