I am trying to serialize my form (JSP/Struts 1.1) and put it into an object or map or whatever jQuery's .getJSON() method needs. Here is my js code:
// This function makes an AJAX call, passing the entire form to the Action class
function ajaxCallWithForm(inputURL, formName, onReturnFunction)
{
var formAsMap = serializeForm(formName);
$.getJSON(inputURL, formAsMap, onReturnFunction);
}
function serializeForm(formName)
{
var obj = {};
var a = $('#'+formName).serializeArray();
$.each(a, function() {
if (obj[this.name] !== undefined) {
if (!obj[this.name].push) {
obj[this.name] = [obj[this.name]];
}
obj[this.name].push(this.value || '');
} else {
obj[this.name] = this.value || '';
}
});
return obj;
}
This results in a java.lang.IllegalArgumentException on the back end (something to do with the BeanUtils.populate servlet method).
If I set the 2nd of 3 parameters of my .getJSON() call to something like this, it works fine and the data shows up in the form object in my Java back end:
// This function makes an AJAX call, passing the entire form to the Action class
function ajaxCallWithForm(inputURL, formName, onReturnFunction)
{
$.getJSON(inputURL, {"vehicleKeyNum":12345,
"vehicleID":"12345",
"rand":Math.random()},
onReturnFunction);
}
I have also tried creating a string with the proper syntax that includes the data from the form and that results in the same thing. I may have my syntax wrong for that. At any rate, my main problem is that:
1) The .getJSON() method accepts, "A map or string that is sent to the server with the request." as its 2nd parameter (see http://api.jquery.com/jQuery.getJSON/)
2) I am passing what I think is a "map"
3) I am getting a java.lang.IllegalArgumentException and don't know where to go from here
If you want to submit a form to server, you can simply use jQuery's serialize() OR serializeArray() method.
$.getJSON(inputURL, $(formName).serialize(), onReturnFunction);
You should have the data returned by the serialize/serializeArray method populated in your form bean if the element names are matched right.
here is a working example of serialize method (copied from jQuery website)
java.lang.IllegalArgumentException from the BeanUtils.populate servlet method is due to data type mismatch between the data submitted and the data on the form bean.
Related
I have the following Java REST method I implemented using Jersey:
#POST
#Path("copy")
public List<Integer> copyCompanionTextRule(#QueryParam("ruleid") List<Integer> ruleIdList,
#QueryParam("workgroupid") List<WorkgroupId> workgroupIds,
#Context HttpHeaders hh)
throws ETMSException
{
List<Integer> insertedItems = new ArrayList<Integer>();
if ( null != ruleIdList ){
for(Integer ruleId : ruleIdList) {
insertedItems.addAll(copyCompanionTextRule(ruleId, workgroupIds));
}
}
return insertedItems;
}
It receives a list of integer and a list of objects of type WorkgroupId as well as the context for some extra processing I'll do later.
I'm working the client with Sencha EXTJS 4.2 and my request is being performed this way:
Ext.Ajax.request({
url: '/sysadmin/companiontextrules/copy',
method: 'POST',
showException: true,
scope: this,
params: {
ruleid: Ext.encode(ruleIdsArray),
workgroupid: toWorkgroups
},
callback: function(options, success, response) {
me.setLoading(false);
if (!success) {
return;
}
this.destroy();
}
});
The ruleIdsArray is just an array of integers: [1274,1292,1745].
The toWorkgroups is an array of objects which has a model that is related to the WorkgroupId entity.
As you can see, both lists are being processed as query parameters and I'm using the "params" config in the Ajax request; however, this is not working.
Seems like the ruleId array is empty, when it tries to iterate the rulesIdList is empty so the method POST works but it is returning always an empty list.
I know I cannot use them in the form "url?ruleid=a&workgroupid=b". When I tried it just by curiosity, I got a QueryParamException and NumberFormatException saying that the rule array is being considered as string.
When I use the "Ext.encode" for both params I receive a message in browser console that the Maximum callstack size exceeded.
This is what I got from Chrome Console:
I've tried almost everything, but maybe some more eyes can help me in this, I'd really appreciate comments or any kind of help.
Thanks in advance.
Looks like your parameters are going in the POST body instead of as query parameters.
url?ruleid=a&workgroupid=b is getting a NumberFormatException because ruleId is supposed to be Integer.
url?ruleid=1&workgroupid=b or url?ruleid=1&ruleid=2&workgroupid=b should work
I am passing two variables in ajax call, one as normal string variable and other one js array object which i m sending to my java class..but when i m sending with array object,the ajax call is getting failed and throwing 500 response code with illegal argument exception
var array1 = new Array();
var inputs = document.getElementsByTagName("input");
for (var i = 0; i < inputs.length; i++) {
array1.push($(inputs[i]).attr('name').toString());
}
var path = "javaclassurl";
$.ajax({
type: "POST",
url: path,
data: {
var1: var1,
array1: array1
},
});
$(this).dialog("close");
//}
},
This is the way i m receiving in my java class
String values[]=request.getParameterValues("array1");
This ajax call is inside dialog box as its a dialog box plugin being used.Please help me in getting the error corrected
Could you please try doing it this way:
String values[]=request.getParameterValues("array1[]");
jQuery API documentation for .ajax() call states:
processData (default: true)
Type: Boolean
By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string, fitting to the default content-type "application/x-www-form-urlencoded". If you want to send a DOMDocument, or other non-processed data, set this option to false.
So you might try setting this to false.
Assume that I have Foo.class in Java:
public class Foo {
public int id;
public String data;
}
And that I have Foo "class" in JavaScript:
function Foo(id, data) {
this.id = id;
this.data = data;
}
Also, assume that I have Java controller that returns instance of Foo.class as a response to a REST request. In my JavaScript (AngularJS) code the request is sent as:
$http.get(url + 'bar/get-foo/')
.success(function (response) {
var foo = new Foo(response.id, response.data);
logger.info("SUCCESS: /get-foo");
})
.error(function (error_message) {
logger.error(error_message)
});
And it works. But is there a way to avoid passing every property from response to Foo constructor (some sort of expecting the Foo object, or casting it into a Foo object)?
I tried using Object.create(Foo, response) but I get TypeError: Property description must be an object: true
Of course there's always a possibility of refactoring the JavaScript side Foo constructor into:
function Foo(foo) {
this.id = foo.id;
this.data = foo.data;
}
But, that would require refactor of large portion of the codebase.
Thanks for your time. I appreciate it!
PS: For those who wonder why do I need this: It's not a problem with small classes like Foo, but some responses are instances of a much larger classes (with over a dozen of fields), which are not under my control.
EDIT: I accepted Chichozell's answer simply because it requires the least amount of work. Robin's and jonnyknowsbest's answers also work, (and will work for pure JavaScript, unlike Chichozell's answer, which is AngularJS specific). Haven't tried Laurentiu L.'s answer, but looks like it should also work.
Anyway this is A solution (not THE solution):
.success(function (response) {
var foo = new Foo();
angular.extend(foo, response); // angular.merge() for "deep-copy"
//...
}
Big thanks to everyone who answered/commented/edited in this thread.
If you want to keep your Java thinking on javascript, try using angular.extend(), which will "copy" the properties of an object to another
this = angular.extend(this, response)
In the foo function, or directly on the controler:
Foo = angular.extend(Foo, response)
You can do something like this to "deserialise" the JSON you receive back as the response to an initialised object:
function JSONToObj(jsondata) {
var json = JSON.parse(jsondata);
var name = null;
for(var i in json) { //Use first property as name
name = i;
break;
}
if (name == null)
return null;
var obj = new window[name]();
for(var i in json[name])
obj[i] = json[name][i];
return obj;
}
This assumes that the constructor exists in the global scope and that the response is JSON-formatted as such:
{
"Foo": {
"id": "the id",
"data": "the data"
}
}
You can make it pretty generic if you want to. And there wouldn't be too much refactoring to do, and this solution would ease your future changes to both classes.
You may change your Foo javascript object to an Angular JS service and inject it wherever you need it. This way you can have your data available globally. It's better than that local variable foo.
yourApp.factory('Foo',
function () {
//set a default or just initialize it
var fooObject= {};
return {
getId: function () { return fooObject.id; },
getData: function() { return fooObject.data;},
setId: function(newId){fooObject.id = newId},
setData: function(newData){fooObject.data=newData;},
initializeFromObject : function(response){
for (var prop in response){
fooObject[prop] = response[prop];
}
}
};
}
);
You can also make the creation of new services safer with methods like hasAllProperties (by iterating through the object's properties, whether it is an array or object). ; hasNullValues and so on.
Hope this helps and you see it's value.
You could also do something like this jsFiddle to achieve the structure you describe. The createObject function couold look something like the following code snippet.
function createObject(response, toCreate){
var newObject = new toCreate();
for(var attr in response){
if(newObject.hasOwnProperty(attr)){
newObject[attr] = response[attr];
}
}
return newObject;
}
Where you have createObject which takes a js object with the same attributes as your function as the response parameter, and a function (the object you want to create) as the toCreate parameter.
See the jsFiddle console log output, and you see that it works.
You could also, as seen in the jsFiddle, remove the check for hasOwnProperty to set the all attributes from the response regardless if the javascript function has them defined.
I'm trying to make sure my Jersey request parameters are sanitized.
When processing a Jersey GET request, do I need to filter non String types?
For example, if the parameter submitted is an integer are both option 1 (getIntData) and option 2 (getStringData) hacker safe? What about a JSON PUT request, is my ESAPI implementation enough, or do I need to validate each data parameter after it is mapped? Could it be validated before it is mapped?
Jersey Rest Example Class:
public class RestExample {
//Option 1 Submit data as an Integer
//Jersey throws an internal server error if the type is not Integer
//Is that a valid way to validate the data?
//Integer Data, not filtered
#Path("/data/int/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getIntData(#PathParam("data") Integer data){
return Response.ok("You entered:" + data).build();
}
//Option 2 Submit data as a String, then validate it and cast it to an Integer
//String Data, filtered
#Path("/data/string/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getStringData(#PathParam("data") String data) {
data = ESAPI.encoder().canonicalize(data);
if (ESAPI.validator().isValidInteger("data", data, 0, 999999, false))
{
int intData = Integer.parseInt(data);
return Response.ok("You entered:" + intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
//JSON data, HTML encoded
#Path("/post/{requestid}")
#POST
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Produces(MediaType.TEXT_HTML)
public Response postData(String json) {
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
//Is there a way to iterate through each JSON KeyValue and filter here?
ObjectMapper mapper = new ObjectMapper();
DataMap dm = new DataMap();
try {
dm = mapper.readValue(json, DataMap.class);
} catch (Exception e) {
e.printStackTrace();
}
//Do we need to validate each DataMap object value and is there a dynamic way to do it?
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
}
Data Map Class:
public class DataMap {
public DataMap(){}
String strData;
Integer intData;
}
The short answer is yes, though by "filter" I interpret it as "validate," because no amount of "filtering" will EVER provide you with SAFE data. You can still run into integer overflows in Java, and while those may not have immediate security concerns, they could still put parts of your application in an unplanned for state, and hacking is all about perturbing the system in ways you can control.
You packed waaaaay too many questions into one "question," but here we go:
First off, the lines
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
Aren't doing what you think they're doing. If your JSON is coming in as a raw String right here, these two calls are going to be applying mass rules across the entire string, when you really need to handle these with more surgical precision, which you seem to at least be subconsciously aware of in the next question.
//Is there a way to iterate through each JSON KeyValue and filter
here?
Partial duplicate of this question.
While you're in the loop discussed here, you can perform any data transformations you want, but what you should really be considering is using the JSONObject class referenced in that first link. Then you'll have JSON parsed into an object where you'll have better access to JSON key/value pairs.
//Do we need to validate each DataMap object value and is there a
dynamic way to do it?
Yes, we validate everything that comes from a user. All users are assumed to be trained hackers, and smarter than you. However if you handled filtering before you do your data mapping transformation, you don't need to do it a second time. Doing it dynamically?
Something like:
JSONObject json = new JSONObject(s);
Iterator iterator = json.keys();
while( iterator.hasNext() ){
String data = iterator.next();
//filter and or business logic
}
^^That syntax is skipping typechecks but it should get you where you need to go.
/Is Integer validation needed or will the thrown exception be good
enough?
I don't see where you're throwing an exception with these lines of code:
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
Firstly, in java we have autoboxing which means this:
int foo = 555555;
String bar = "";
//the code
foo + bar;
Will be cast to a string in any instance. The compiler will promote the int to an Integer and then silently call the Integer.toString() method. Also, in your Response.ok( String ); call, THIS is where you're going to want to encodeForHTML or whatever the output context may be. Encoding methods are ALWAYS For outputting data to user, whereas canonicalize you want to call when receiving data. Finally, in this segment of code we also have an error where you're assuming that you're dealing with an HTTPParameter. NOT at this point in the code. You'll validate http Parameters in instances where you're calling request.getParameter("id"): where id isn't a large blob of data like an entire JSON response or an entire XML response. At this point you should be validating for things like "SafeString"
Usually there are parsing libraries in Java that can at least get you to the level of Java objects, but on the validation side you're always going to be running through every item and punting whatever might be malicious.
As a final note, while coding, keep these principles in mind your code will be cleaner and your thought process much more focused:
user input is NEVER safe. (Yes, even if you've run it through an XSS filter.)
Use validate and canonicalize methods whenever RECEIVING data, and encode methods whenever transferring data to a different context, where context is defined as "Html field. Http attribute. Javascript input, etc...)
Instead of using the method isValidInput() I'd suggest using getValidInput() because it will call canonicalize for you, making you have to provide one less call.
Encode ANY time your data is going to be passed to another dynamic language, like SQL, groovy, Perl, or javascript.
I have looked previous questions on this topic on SO, but my problem is not solved yet.
I am passing the array from javascript to servlet.
JavaScript Code:
var action = new Array();
function getProtAcionValues(rowNo,columnCount)
{
for(var j=0;j<columnCount;j++)
{
action[j] = document.getElementById('textActions'+rowNo+''+j).value;
alert(action[j]);
}
}
Servlet Code:
String actions[] = request.getParameterValues("action[]");
if(actions!=null)
for(int i=0;i<actions.length;i++)
{
System.out.print(" Action: "+actions);
}
else
System.out.println("Action is null");
Using above code I am getting message "Action is null".
And if I try
String actions[] = request.getParameterNames("action[]");
I am getting Syntax error:
The method getParameterNames() in the type ServletRequest is not applicable for the arguments (String)
Please let me know if there is something wrong in code.
you can just simply get the array with the name of the array...
String actions[] = request.getParameterValues("action");
You can't pass a java array as a parameter, as it is an structure. The best way is to serialize it into an string object like a jSon. You can use JSON.stringify. Simple and efficient. As you can serialize in the server also, it's very useful.
Pass Javascript array variable with form action to send values to servlet, and then use
String[] darray=request.getParameterValues("variable name used with link");