GWT FormPanel method replaced by input parameter - firefox

We've discovered a strange new bug in a GWT application I'm maintaining, and I'm not sure when it became an issue. Possibly with a new Firefox version.
We're sending a POST request to the server using a FormPanel, essentially like many examples I've seen online. But since we actually want a PUT request, one of the hidden input parameters is named "method" and has a value of "put".
Now, when I look at the request in Fiddler coming from Firefox, it is being transformed into a GET request with all the parameters in the QueryString. In IE and Chrome, the parameters are in the body of a POST request.
I've displayed the value of FormPanel.getMethod() in an alert, and in IE and Chrome the string "post" is displayed, whereas in firefox it is showing "object HTMLInputElement". Unfortunately, hosted mode debugging does not work with this project.
It obviously looks like the FormPanel's getMethod() function is returning the hidden input parameter named method instead of the actual form's method in Firefox.
Technically I should avoid changing the servlet as this is from an OpenSource project that we use, though I've found I can fix the issue by changing the hidden input parameter's name to "_method" on both ends.
Has anyone ever seen anything like this? I can't find anything in Google.
UPDATE: We're using GWT 2.3 in case that helps

Some insight can be found here Are the PUT, DELETE, HEAD, etc methods available in most web browsers?
I would also suggest using XMLHttpRequest. In this case you [most probably] don't have to change anything on the server side.
In case if you use Submit button, you can write in its clickHandler function:
submitMyForm(yourTextBox.getText(), self);
// self - is the instance of main class (named UploadForm here), needs to be passed here for future reference
and then some more (you can adapt this for your needs):
private native void submitMyForm(String text, UploadForm handler)/*-{
var fd = new FormData();
fd.append("textValue", text);
var xhr = new XMLHttpRequest();
var upload = xhr.upload;
readyStateChangeHandler = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var serverResponse = eval(xhr.responseText); // optional
handler.#com.project.UploadForm::onUploadIsDone(Lcom/google/gwt/core/client/JavaScriptObject;)(serverResponse);
} else {
handler.#com.project.UploadForm::onUploadFailed(I)(status);
}
}
};
xhr.onreadystatechange = readyStateChangeHandler;
xhr.open("PUT", yourActionUrlHere);
xhr.send(formData);
}-*/;

Related

webRequest API redirect uses http method of parent request

I'm using the webRequest-API in a WebExtension to monitor requests made by the client. This works quite well, however the API doesn't behave as expected in case of a redirect:
In this case a POST is issued which is answered with a 302 FOUND and a new location. My browser (Firefox 57, other versions and other browsers - e.g. Chrome - act the same way) follows this redirect and now issues a GET to the new location.
Unfortunately the webRequest-API behaves differently: It traces the first POST (which is correct) but than handles the second request as a POST, too, whereas it should be a GET. This is a severe problem since the API traces something my browser supposedly did, which it actually did in another way...
This scenario (the browser-part) can be reproduced by following this link to surfnet.nl and choosing an IDP form the list (e.g. Academisch Medisch Centrum).
So, long story short: Why does the webRequest-API behave different form the way browsers behave? And is there a way to let it exactly trace the browser's actions?
Interestingly the webRequest-API might do it correctly, regarding the documentation:
Even if the specification requires the method, and the body, not to be altered when the redirection is performed, not all user-agents conform here [browsers obviously change the method!], and you can still find buggy software out there. It is therefore recommended to set the 302 code only as a response for GET or HEAD methods and to use 307 Temporary Redirect instead, as the method change is explicitly prohibited in that case.
In the cases where you want the method used to be changed to GET, use 303 See Other instead.
EDIT:
It seems as if browser change the method on a 302 due to historical reasons, even tho it contradicts RFC 2616...
https://stackoverflow.com/a/8139246/594832
https://trac.ietf.org/trac/httpbis/ticket/160
https://stackoverflow.com/a/8138447/594832
Anyways... the question remains: how can I induce the webRequest-API to act the same way?
For anyone interested, I ended up with the following:
The docs let me to this (redirectUrl):
[...] Redirects initiated by a redirect action use the original request method for the redirect, with one exception: If the redirect is initiated at the onHeadersReceived stage, then the redirect will be issued using the GET method. [...]
Although the statement above gave me some hope, the method of that redirected request was still labeled as a POST...
So I updated the code to something roughly like this:
// Keep track of all requests issued so far and their responses
var httpRequests = [];
// Redirected requests come in with their originating parents' ID
browser.webRequest.onBeforeRequest.addListener(request => {
// Check if the request is a redirect
var isRedirected = function(requestId) {
var parentRequest = httpRequests.find(r => r.req.requestId === requestId);
if (parentRequest != null && parentRequest.res != null && parentRequest.res.statusCode === 302) {
return true;
}
return false;
};
// The webRequest-API seems to keep the HTTP verbs which is correct in resepct to RFC 2616 but
// differs from a typical browser behaviour which will usually change the POST to a GET. So do we here...
if (request.method === 'POST' && isRedirected(request.requestId)) {
console.log(`Redirected 302-request '${request.requestId}' is a POST but is here changed to a GET to conform to browser behaviour...`);
request.method = 'GET';
}
// Store the request
var entry = {
id: id,
req: request
};
httpRequests.push(entry);
});
browser.webRequest.onHeadersReceived.addListener(response => {
// Store the response alongside the request
var entry = httpRequests.find(req => req.id === response.requestId);
entry.res = response;
// If it turns out that a request should be redirected...
if (response.statusCode === 302) {
var location = response.responseHeaders.find(header => header.name.toLowerCase() === "location");
console.log(`Redirecting request '${id}' to new location '${location.value}'...`);
// The new location is set as redirectUrl, which results in a new invocation of onBeforeRequest with
// the current requestId
return {
redirectUrl: location.value
};
}
});
What happens?
Say onBeforeRequest receives a new request 2640 which is a POST.
onHeadersReceived gets the response which says that this request should be redirected to a new location (302).
The code above does so by setting the redirectUrl to that new location.
Then onBeforeRequest is triggered again. The webRequest-API passes the same requestId to it (2640).
The code checks if there's a parent for this request, and in this case it's true.
The method (which is still POST) is then modified to GET.
For the request lifecycle have a look at the illustration in the docs.

Writing an equivalent to Chrome's onBeforeRequest in a Safari extension

Chrome extensions have the ability to intercept all web requests to specified URLs using chrome.webRequest.onBeforeRequest. This includes not only static asset requests, but requests for AJAX, PJAX, favicons, and everything in between.
Apple provides a few close approximations to this functionality, such as the beforeLoad (handles images, CSS, and JS) and beforeNavigate (handles full page loads) event handlers, but neither catch AJAX requests. I've tried overloading XMLHttpRequest in an attempt to catch AJAX loads to no avail (I might be doing something wrong). Here's a brief example of how I'm doing this:
var originalOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
console.log("overriden");
return originalOpen.apply(this, arguments);
}
How can I catch all web requests (AJAX, CSS, JS, etc.) in a Safari extension?
Update: You can check entire code flow on my first Safari Extension I've wrote for TimeCamp tracker: https://github.com/qdevro/timecamp.safariextz
I have succeeded to intercept all AJAX calls (actually the responses were interesting for me, because there all the magic happens), but unfortunately I couldn't find (yet) a solution to send it back to my injected script (I still work on this) now fully working - getting the xhr to the injected script:
I've done it like this:
1) on the injected START script, I've added into the DOM another script (the one which does the interception):
$(document).ready(function(){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = safari.extension.baseURI + 'path/to/your/script/bellow.js';
document.getElementsByTagName('head')[0].appendChild(script);
})
2) the interception code uses this repository as override of the XMLHttpRequest object, that I've tweaked a little bit as well in order to attach the method, url and sent data to it in order to be easily available when the response get's back.
Basically, I've overriden the open() method of the XMLHttpsRequest to attach those values that I might need in my script, and added the sentData in the send() method as well:
var RealXHROpen = XMLHttpRequest.prototype.open;
...
// Override open method of all XHR requests (inside wire() method
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
this.method = method;
this.url = url;
RealXHROpen.apply(this, arguments);
}
...
// Override send method of all XHR requests
XMLHttpRequest.prototype.send = function(sentData) {
...
this.sentData = sentData;
...
}
Then, I've added a callback on the response, which get's a modified XMLHttpRequest object WHEN the data comes back, and cotains everything: url, method, sentData and responseText with the retrieved data:
AjaxInterceptor.addResponseCallback(function(xhr) {
console.debug("response",xhr);
// xhr.method - contains the method used by the call
// xhr.url - contains the URL was sent to
// xhr.sentData - contains all the sent data (if any)
// xhr.responseText - contains the data sent back to the request
// Send XHR object back to injected script using DOM events
var event = new CustomEvent("ajaxResponse", {
detail: xhr
});
window.dispatchEvent(event);
});
AjaxInterceptor.wire();
For sending the XHR object from the intercept script back to the injected script, I just had to use DOM events like #Xan has suggested (thanks for that):
window.addEventListener("ajaxResponse", function(evt) {
console.debug('xhr response', evt.detail);
// do whatever you want with the XHR details ...
}, false);
Some extra hints / (workflow) optimisations that I've used in my project:
I've cleaned the GET url's and moved all the parameters (? &) into the dataSent property;
I've merged this dataSent property if there's the case (in send(data) method)
I've added an identifier on request send (timestamp) in order to match it later (see point bellow and get the idea);
I've sent a custom event to the script called "ajaxRequest" in order to prepare / optimise load times (I had to request some other data to some external API using CORS - by passing the call / response back and forth to the global.html which is capable of handling CORS), so I didn't had to wait for the original request to come back before sending my API call, but just matching the responses based on timestamp above;

nsIProtocolHandler and nsIURI: Relative URLs in self-created protocol

I have a simple implementation of custom protocol. It's said that newURI method takes 3 arguments (spec, charset & baseURI) and "if the protocol has no concept of relative URIs, third parameter is ignored".
So i open a page like this tada://domain/samplepage which has XML starting with this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Product SYSTEM "product.dtd">
But i don't see any request regarding product.dtd to my protocol (newURI is not even called). Do i miss smth in my implementation?
BTW: the page itself opens correctly, but there's no request to the DTD-file.
const
Cc = Components.classes,
Ci = Components.interfaces,
Cr = Components.results,
Cu = Components.utils,
nsIProtocolHandler = Ci.nsIProtocolHandler;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function TadaProtocol() {
}
TadaProtocol.prototype = {
scheme: "tada",
protocolFlags: nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
newURI: function(aSpec, aOriginCharset, aBaseURI) {
let uri = Cc["#mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
uri.spec = (aBaseURI === null)
? aSpec
: aBaseURI.resolve(aSpec);
return uri;
},
newChannel: function(aURI) {
let
ioService = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
uri = ioService.newURI("chrome://my-extension/content/about/product.xml", null, null);
return ioService.newChannelFromURI(uri);
},
classDescription: "Sample Protocol Handler",
contractID: "#mozilla.org/network/protocol;1?name=tada",
classID: Components.ID('{1BC90DA3-5450-4FAF-B6FF-F110BB73A5EB}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
}
let NSGetFactory = XPCOMUtils.generateNSGetFactory([TadaProtocol]);
The channel you return from newChannel has the chrome:// URI you passed to newChannelFromURI as its URI. So that's the URI the page has as its URI, and as its base URI. So the DTD load happens from "chrome://my-extension/content/about/product.dtd" directly.
What you probably want to do is to set aURI as the originalURI on the channel you return from newChannel.
As Boris mentioned in his answer, your protocol implementation doesn't set nsIChannel.originalURI property so that URLs will be resolved relative to the chrome: URL and not relative to your tada: URL. There is a second issue with your code however: in Firefox loading external DTDs only works with chrome: URLs, this check is hardcoded. There is a limited number of supported DTDs that are mapped to local files (various HTML doctypes) but that's it - Gecko doesn't support random URLs in <!DOCTYPE>. You can see the current logic in the source code. The relevant bug is bug 22942 which isn't going to be fixed.
Boris and Wladimir, thank you!
After some time i have a solution. The problem was that the DTD-file could not be loaded from my custom-created protocol. The idea was to use Proxy API to override schemeIs() method, which was called in newURI method of nsIProtocolHandler.
So now i have this snippet of code in newURI method:
let standardUrl = Cc["#mozilla.org/network/standard-url;1"].createInstance(Ci.nsIStandardURL);
standardUrl.init(standardUrl.URLTYPE_STANDARD, -1, spec, charset, baseURI);
standardUrl.QueryInterface(Ci.nsIURL);
return Proxy.create(proxyHandlerMaker(standardUrl));
proxyHandlerMaker just implements Proxy API and overrides the needed schemeIs() method. This solved the problem and now all the requests come to newChannel where we can handle them.
Important notes:
Request to DTD comes to newURI() method and does not come to newChannel(). This is the default behavior. This happens because schemeIs("chrome") method is called on the object which was returned by newURI() method. This method should return "true" for DTD-requests if you want the request to reach the newChannel() method.
newChannel() method is invoked with the {nsIURI} object which is not the same as the object which was returned by the newURI method.
If you want to handle both protocol:page & protocol://domain/page URLs by your protocol, you should use both {nsIURI} and {nsIStandardURL} objects
You can pass the created {nsIStandardUrl}-object (standardUrl in the snippet above) as a 2nd argument to the Proxy.create() function. This will make your baseURI (3rd arguments in newURI) pass "baseURI instanceof nsIStandardUrl" check. SchemeIs() method of this proxied object will also return true for the DTD-files requests. But unfortunately the requests won't reach newChannel() method. This could be a nice DTD-problem solution but I can't solve this problem.

Null parameters in MVC action from jQuery ajax call during navigation (IE only)

I am a programmer on an MVC/ajax project that receives significant traffic from its customers. We've been seeing one or two isolated instances (per day) of a controller action not receiving parameters from the client.
Long story short: the actions are being called via jQuery ajax, and the action params are only null if the ajax call is made while the browser is navigating to another page. IE click a link and then trigger an ajax call.
I added some crude validation around these calls to ensure that we aren't actually passing nulls in the ajax data, and this hasn't alleviated the problem. An example of one of the calls is below.
var searchValue = _txtSearch.val().trim();
if (searchValue === null
|| searchValue === undefined
|| searchValue.length < _minimumLengthForSearch) {
_txtSearch.focus();
return;
}
// clear out the value when launching
_txtSearch.val('');
$.post(_quickSearchUrl,
{ searchString: searchValue },
function (data) {...},
"json");
I found an old IEBlog post that suggests IE might handle this situation differently from other browsers. I was curious as to whether anyone else has encountered this phenomena before. Again, I can only reproduce this issue in IE and only during page navigation.
Edit: It is difficult to reproduce this exception with Fiddler active for some reason, but when I manage to Fiddler displays the following error message:
Fiddler has detected a protocol violation in session #4.
Content-Length mismatch: Request Header indicated 24 bytes, but client sent 0 bytes.
Using Fiddler, I was able to reproduce this in a very rare instance and realized that it can be treated as a content length mismatch (that is the error message that fiddler displays when it occurs). Specifically, on the server side the request content length will not match the actual Form/InputStream contents. We overrode OnAuthorization to manually detect and handle this case.
protected override void OnAuthorization(AuthorizationContext filterContext)
{
//Detect IE missing post data phenomenon
var request = HttpContext.Request;
if (request.IsAjaxRequest() == true && request.ContentLength > 0
&& request.Form.HasKeys() == false && request.InputStream.Length == 0)
{
throw new ContentLengthMismatchException(string.Format("Content Length Mismatch in URL: {0}", request.Url));
}
base.OnAuthorization(filterContext);
}

parsing a reponse from a XMLHttpRequest

I'm struggling with how to parse a response from an XMLHttpRequest. The response is in json format:
http://flickr.com/services/rest/?method=flickr.photos.search&api_key=75564008a468bf8a284dc94bbd176dd8&tags=paris&format=json
to make sure it does indeed come in as such i tested it:
document.getElementById('info').innerHTML = this.responseText
which returns me a page with a long line of data written in json format. Could someone help me figure out the next steps in order to extract data from the response i.e. a list of all titles
i did some research and came across this:
response = this.responseText ;
var doc = new DOMParser().parseFromString(response, "text/xml");
what do i need to do next?
(Note: i wish to do this manually i.e. without the help of jQuery or similar tools.)
[EDIT]
based on the suggestions below and on the Flickr page on that matter, i have tried the following:
request.onreadystatechange = function()
{
...
if (this.responseXML != null)
{
jsonFlickrApi(this.responseText) ;
function jsonFlickrApi(rsp){
for (var i=0; i<rsp.photos.photo.length; i++){
var blog = rsp.photos.photo[i];
var div = document.createElement('div');
var txt = document.createTextNode(photo.owner);
div.appendChild(txt);
//document.body.appendChild(div);
document.getElementById('info').innerHTML.appendChild(div);
}
...
}
this doesn't return anything visible yet.
[EDIT2]
further troubleshooting reveals:
rsp = this.responseText ;
document.getElementById('info').innerHTML = rsp.stat ;
prints undefined
The URL you've given returns somethins like this :
jsonFlickrApi({"photos":{"page":1, "p ... , "stat":"ok"})
So, basically, it looks like Javascript code, which :
Calls the jsonFlickrApi function,
Passing it a big JSON object as a parameter.
First of all, here, you are working with JSON, so you should not use any DOM-related stuff : DOM functions' goal is to help manipulate XML.
Instead, you should :
Write a jsonFlickrApi function,
Make sure it's called when you receive the data from Flickr
About that, you shuld find a bit more informations, and an example, here : http://www.flickr.com/services/api/response.json.html
Else, adding the &nojsoncallback=1 parameter at the end of the URL of your request, you'll get pure-JSON as a result (and not a function-call).
That would allow you to use standard JSON-manipulation functions to work with that data, not having to implement any specific function.
Between those solutions, up to you to choose which one you prefer :-)
A different alternative is not to use JSON at all, and use XML instead. Leave out the format=json part of the URL and you get the data as XML. This XML can be parsed, for example with the DOMParser() method you tried, or with this.responseXML. However, the "logistics" of using XML, compared to JSON, are a bit more complicated, as you're browsing a DOM tree and not a JS object.
Update:
So here's one of the murky details of AJAX. Depending on the browser, you can't just make XML requests between domains. The following code will work (return something useful) on Safari, but not Firefox or Chrome. (There, it will return null or empty strings.) The JSON requests seem to work fine without on all browsers, however.
<script>
function createXHR(){
if (window.XMLHttpRequest){
// code for IE7+, Firefox, Chrome, Opera, Safari
return new XMLHttpRequest();
}
if (window.ActiveXObject){
// code for IE6, IE5
return new ActiveXObject("Microsoft.XMLHTTP");
}
return null;
}
function getFlickr(){
xmlhttp=createXHR();
url="http://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=75564008a468bf8a284dc94bbd176dd8&tags=paris&";
xmlhttp.onreadystatechange=stateChanged;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
function stateChanged(){
if (xmlhttp.readyState==4){
alert(xmlhttp.getAllResponseHeaders());
alert(xmlhttp.responseXML)
alert(xmlhttp.responseText)
var xmlDoc=xmlhttp.responseXML.documentElement;
}
}
getFlickr();
</script>
The cool thing about JSON is that it's actually executable code. You don't need to do any "manual" parsing – just run the code. Perhaps Flickr supplies a function called jsonFlickrApi with their API libs that they exepct you to use, but you could just as well supply your own.
function parseFlickrJson(jsonstring){
var data=null;
var jsonFlickrApi=function(d){
data = d;
}
eval(jsonstring);
return data;
}
myreturndata = parseFlickrJson(response);
// Try getting something from the object
alert(myreturndata.photos.pages);

Resources