1) Make generic dao be able to persist UTF-8 string for internationalization support

2) Undo gson Unicode escape in API response object to avoid double escaping which can break Javascript from getting correct text content
3) Correct API layer in dealing with character encoding
4) Remove double escape in cloud.core.js
This commit is contained in:
Kelven Yang 2010-11-30 09:40:35 -08:00
parent fa9c882d8c
commit 54f177cacc
9 changed files with 77 additions and 30 deletions

View File

@ -11,7 +11,6 @@
<classpathentry kind="lib" path="/thirdparty/commons-pool-1.4.jar"/>
<classpathentry kind="lib" path="/thirdparty/ehcache-1.5.0.jar"/>
<classpathentry kind="lib" path="/thirdparty/junit-4.8.1.jar"/>
<classpathentry kind="lib" path="/thirdparty/xenserver-5.6.0-1.jar" sourcepath="/thirdparty/XenServerJava"/>
<classpathentry kind="lib" path="/thirdparty/trilead-ssh2-build213.jar"/>
<classpathentry kind="lib" path="/thirdparty/commons-httpclient-3.1.jar"/>
<classpathentry kind="lib" path="/thirdparty/commons-codec-1.4.jar"/>
@ -39,5 +38,6 @@
<classpathentry kind="lib" path="/thirdparty/vmware-vim.jar"/>
<classpathentry kind="lib" path="/thirdparty/vmware-vim25.jar"/>
<classpathentry kind="lib" path="/thirdparty/gson.jar"/>
<classpathentry kind="lib" path="/thirdparty/xenserver-5.5.0-1.jar" sourcepath="/thirdparty/xen/XenServerJava"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -13,13 +13,13 @@
<classpathentry kind="lib" path="/thirdparty/email.jar"/>
<classpathentry kind="lib" path="/thirdparty/junit-4.8.1.jar"/>
<classpathentry kind="lib" path="/thirdparty/commons-codec-1.4.jar"/>
<classpathentry kind="lib" path="/thirdparty/xenserver-5.6.0-1.jar" sourcepath="/thirdparty/XenServerJava"/>
<classpathentry kind="lib" path="/thirdparty/xmlrpc-client-3.1.3.jar"/>
<classpathentry kind="lib" path="/thirdparty/xmlrpc-client-3.1.3.jar"/>
<classpathentry kind="lib" path="/thirdparty/xmlrpc-common-3.1.3.jar"/>
<classpathentry kind="lib" path="/thirdparty/servlet-api.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/api"/>
<classpathentry kind="lib" path="/thirdparty/trilead-ssh2-build213.jar"/>
<classpathentry kind="lib" path="/thirdparty/xstream-1.3.1.jar"/>
<classpathentry kind="lib" path="/thirdparty/gson.jar"/>
<classpathentry kind="lib" path="/thirdparty/xenserver-5.5.0-1.jar" sourcepath="/thirdparty/xen/XenServerJava"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -14,3 +14,4 @@ public class ApiGsonHelper {
return s_gBuilder;
}
}

View File

@ -274,9 +274,9 @@ public class ApiServer implements HttpRequestHandler {
response.setStatusCode(se.getErrorCode());
response.setReasonPhrase(se.getDescription());
BasicHttpEntity body = new BasicHttpEntity();
body.setContentType("text/xml; charset=UTF-8");
body.setContentType("text/xml");
String responseStr = "<error>"+se.getErrorCode()+" : "+se.getDescription()+"</error>";
body.setContent(new ByteArrayInputStream(responseStr.getBytes()));
body.setContent(new ByteArrayInputStream(responseStr.getBytes("UTF-8")));
response.setEntity(body);
sb.append(" " + se.getErrorCode() + " " + responseStr.length());
@ -714,19 +714,19 @@ public class ApiServer implements HttpRequestHandler {
BasicHttpEntity body = new BasicHttpEntity();
if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
// JSON response
body.setContentType("text/javascript; charset=UTF-8");
body.setContentType("text/javascript");
if (responseText == null) {
body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes()));
body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes("UTF-8")));
}
} else {
body.setContentType("text/xml; charset=UTF-8");
body.setContentType("text/xml");
if (responseText == null) {
body.setContent(new ByteArrayInputStream("<error>Internal Server Error</error>".getBytes()));
body.setContent(new ByteArrayInputStream("<error>Internal Server Error</error>".getBytes("UTF-8")));
}
}
if (responseText != null) {
body.setContent(new ByteArrayInputStream(responseText.getBytes()));
body.setContent(new ByteArrayInputStream(responseText.getBytes("UTF-8")));
}
resp.setEntity(body);
} catch (Exception ex) {

View File

@ -320,14 +320,10 @@ public class ApiServlet extends HttpServlet {
} else {
resp.setContentType("text/xml");
}
resp.setStatus(isError? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : HttpServletResponse.SC_OK);
byte[] respBytes = response.getBytes();
resp.setContentLength(respBytes.length);
OutputStream os = resp.getOutputStream();
os.write(respBytes);
os.flush();
os.close();
resp.flushBuffer();
resp.setStatus(isError? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : HttpServletResponse.SC_OK);
// use getWriter() instead of manually manipulate encoding to have better localization support
resp.getWriter().print(response);
} catch (IOException ioex) {
if (s_logger.isTraceEnabled()) {
s_logger.trace("exception writing response: " + ioex);

View File

@ -4,10 +4,10 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
@ -28,6 +28,16 @@ public class ApiResponseSerializer {
return toXMLSerializedString(result);
}
}
private static final Pattern s_unicodeEscapePattern = Pattern.compile("\\\\u([0-9A-Fa-f]{4})");
public static String unescape(String escaped) {
String str = escaped;
Matcher matcher = s_unicodeEscapePattern.matcher(str);
while(matcher.find()) {
str = str.replaceAll("\\" + matcher.group(0), Character.toString((char)Integer.parseInt(matcher.group(1), 16)));
}
return str;
}
private static String toJSONSerializedString(ResponseObject result) {
if (result != null) {
@ -39,10 +49,12 @@ public class ApiResponseSerializer {
List<? extends ResponseObject> responses = ((ListResponse)result).getResponses();
if ((responses != null) && !responses.isEmpty()) {
int count = responses.size();
String jsonStr = gson.toJson(responses.get(0));
String jsonStr = gson.toJson(responses.get(0));;
jsonStr = unescape(jsonStr);
sb.append("{ \"" + responses.get(0).getObjectName() + "\" : [ " + jsonStr);
for (int i = 1; i < count; i++) {
jsonStr = gson.toJson(responses.get(i));
jsonStr = unescape(jsonStr);
sb.append(", " + jsonStr);
}
sb.append(" ] }");
@ -54,6 +66,7 @@ public class ApiResponseSerializer {
} else {
String jsonStr = gson.toJson(result);
if ((jsonStr != null) && !"".equals(jsonStr)) {
jsonStr = unescape(jsonStr);
if (result instanceof AsyncJobResponse || result instanceof CreateCmdResponse) {
sb.append(jsonStr);
} else {

View File

@ -66,15 +66,16 @@ public class ConsoleProxyServlet extends HttpServlet {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
try {
String userId = null;
String account = null;
Account accountObj = null;
Map<String, Object[]> params = new HashMap<String, Object[]>();
params.putAll(req.getParameterMap());
HttpSession session = req.getSession(false);
if(session == null) {
if(verifyRequest(params)) {

View File

@ -537,11 +537,11 @@ function createURL(url) {
}
function fromdb(val) {
return sanitizeXSS(unescape(noNull(val)));
return sanitizeXSS(noNull(val));
}
function todb(val) {
return encodeURIComponent(escape(val));
return encodeURIComponent(val);
}
var midmenuItemCount = 20;

View File

@ -18,6 +18,7 @@
package com.cloud.utils.db;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@ -448,7 +449,20 @@ public abstract class GenericDaoBase<T, ID extends Serializable> implements Gene
try {
final Class<?> type = field.getType();
if (type == String.class) {
field.set(entity, rs.getString(index));
byte[] bytes = rs.getBytes(index);
if(bytes != null) {
try {
field.set(entity, new String(bytes, "UTF-8"));
} catch (IllegalArgumentException e) {
assert(false);
throw new CloudRuntimeException("IllegalArgumentException when converting UTF-8 data");
} catch (UnsupportedEncodingException e) {
assert(false);
throw new CloudRuntimeException("UnsupportedEncodingException when converting UTF-8 data");
}
} else {
field.set(entity, null);
}
} else if (type == long.class) {
field.setLong(entity, rs.getLong(index));
} else if (type == Long.class) {
@ -560,7 +574,16 @@ public abstract class GenericDaoBase<T, ID extends Serializable> implements Gene
@DB(txn=false) @SuppressWarnings("unchecked")
protected <M> M getObject(Class<M> type, ResultSet rs, int index) throws SQLException {
if (type == String.class) {
return (M)rs.getString(index);
byte[] bytes = rs.getBytes(index);
if(bytes != null) {
try {
return (M)new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new CloudRuntimeException("UnsupportedEncodingException exception while converting UTF-8 data");
}
} else {
return (M)null;
}
} else if (type == int.class) {
return (M)new Integer(rs.getInt(index));
} else if (type == Integer.class) {
@ -1197,10 +1220,23 @@ public abstract class GenericDaoBase<T, ID extends Serializable> implements Gene
final Column column = attr.field.getAnnotation(Column.class);
final int length = column != null ? column.length() : 255;
// to support generic localization, utilize MySql UTF-8 support
if (length < str.length()) {
pstmt.setString(j, str.substring(0, column.length()));
try {
pstmt.setBytes(j, str.substring(0, column.length()).getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// no-way it can't support UTF-8 encoding
assert(false);
throw new CloudRuntimeException("UnsupportedEncodingException when saving string as UTF-8 data");
}
} else {
pstmt.setString(j, str);
try {
pstmt.setBytes(j, str.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// no-way it can't support UTF-8 encoding
assert(false);
throw new CloudRuntimeException("UnsupportedEncodingException when saving string as UTF-8 data");
}
}
} else if (attr.field.getType() == Date.class) {
final Date date = (Date)value;