diff --git a/server/src/com/cloud/api/ApiGsonHelper.java b/server/src/com/cloud/api/ApiGsonHelper.java index 7a48f49a0aa..fe75283ced4 100644 --- a/server/src/com/cloud/api/ApiGsonHelper.java +++ b/server/src/com/cloud/api/ApiGsonHelper.java @@ -25,7 +25,8 @@ public class ApiGsonHelper { static { s_gBuilder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); s_gBuilder.setVersion(1.3); - s_gBuilder.registerTypeAdapter(ResponseObject.class, new ResponseObjectTypeAdapter()); + s_gBuilder.registerTypeAdapter(ResponseObject.class, new ResponseObjectTypeAdapter()); + s_gBuilder.registerTypeAdapter(String.class, new EncodedStringTypeAdapter()); } public static GsonBuilder getBuilder() { diff --git a/server/src/com/cloud/api/EncodedStringTypeAdapter.java b/server/src/com/cloud/api/EncodedStringTypeAdapter.java new file mode 100644 index 00000000000..4f148bce725 --- /dev/null +++ b/server/src/com/cloud/api/EncodedStringTypeAdapter.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.api; + +import java.lang.reflect.Type; + +import org.apache.log4j.Logger; + +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.encoding.URLEncoder; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class EncodedStringTypeAdapter implements JsonSerializer { + public static final Logger s_logger = Logger.getLogger(EncodedStringTypeAdapter.class.getName()); + private static final boolean encodeApiResponse = configure(); + + private static boolean configure() { + ComponentLocator locator = ComponentLocator.getCurrentLocator(); + + ConfigurationDao configDao = locator.getDao(ConfigurationDao.class); + if (configDao != null) { + return Boolean.valueOf(configDao.getValue(Config.EncodeApiResponse.key())); + } else { + return true; + } + } + + @Override + public JsonElement serialize(String src, Type typeOfResponseObj, JsonSerializationContext ctx) { + return new JsonPrimitive(encodeString(src)); + + } + + private static String encodeString(String value) { + if (!encodeApiResponse) { + return value; + } + try { + return new URLEncoder().encode(value).replaceAll("\\+", "%20"); + } catch (Exception e) { + s_logger.warn("Unable to encode: " + value, e); + } + return value; + } + +} diff --git a/server/src/com/cloud/api/response/ApiResponseSerializer.java b/server/src/com/cloud/api/response/ApiResponseSerializer.java index 266ce016f98..3e7dd310cac 100644 --- a/server/src/com/cloud/api/response/ApiResponseSerializer.java +++ b/server/src/com/cloud/api/response/ApiResponseSerializer.java @@ -34,11 +34,28 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiGsonHelper; import com.cloud.api.BaseCmd; import com.cloud.api.ResponseObject; +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.encoding.URLEncoder; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; public class ApiResponseSerializer { private static final Logger s_logger = Logger.getLogger(ApiResponseSerializer.class.getName()); + private static final boolean encodeApiResponse = configure(); + + private static boolean configure() { + ComponentLocator locator = ComponentLocator.getCurrentLocator(); + + ConfigurationDao configDao = locator.getDao(ConfigurationDao.class); + if (configDao != null) { + return Boolean.valueOf(configDao.getValue(Config.EncodeApiResponse.key())); + } else { + return true; + } + } + public static String toSerializedString(ResponseObject result, String responseType) { if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) { @@ -193,7 +210,12 @@ public class ApiResponseSerializer { } else if (fieldValue instanceof Date) { sb.append("<" + serializedName.value() + ">" + BaseCmd.getDateString((Date) fieldValue) + ""); } else { - sb.append("<" + serializedName.value() + ">" + escapeSpecialXmlChars(fieldValue.toString()) + ""); + String resultString = escapeSpecialXmlChars(fieldValue.toString()); + if (!(obj instanceof ExceptionResponse)) { + resultString = encodeParam(resultString); + } + + sb.append("<" + serializedName.value() + ">" + resultString + ""); } } } catch (IllegalArgumentException e) { @@ -267,6 +289,20 @@ public class ApiResponseSerializer { resultString.append(singleChar); } } + return resultString.toString(); } + + private static String encodeParam(String value) { + if (!encodeApiResponse) { + return value; + } + try { + return new URLEncoder().encode(value).replaceAll("\\+", "%20"); + } catch (Exception e) { + s_logger.warn("Unable to encode: " + value, e); + } + return value; + } + } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 784a06dd0f2..fd5a5b4af85 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -236,7 +236,8 @@ public enum Config { DirectAgentLoadSize("Advanced", ManagementServer.class, Integer.class, "direct.agent.load.size", "16", "The number of direct agents to load each time", null), AgentLbEnable("Advanced", ClusterManager.class, Boolean.class, "agent.lb.enabled", "true", "If agent load balancing enabled in cluster setup", null), - SubDomainNetworkAccess("Advanced", NetworkManager.class, Boolean.class, "allow.subdomain.network.access", "true", "Allow subdomains to use networks dedicated to their parent domain(s)", null); + SubDomainNetworkAccess("Advanced", NetworkManager.class, Boolean.class, "allow.subdomain.network.access", "true", "Allow subdomains to use networks dedicated to their parent domain(s)", null), + EncodeApiResponse("Advanced", ManagementServer.class, Boolean.class, "encode.api.response", "true", "Do UTF-8 encoding for the api response, true by default", null); private final String _category; diff --git a/setup/db/db/schema-226to227.sql b/setup/db/db/schema-226to227.sql index b3be9450bc5..b70cdc6a190 100644 --- a/setup/db/db/schema-226to227.sql +++ b/setup/db/db/schema-226to227.sql @@ -115,3 +115,5 @@ UPDATE vm_instance set vm_type=type; ALTER TABLE `cloud`.`networks` ADD COLUMN `is_domain_specific` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if network is domain specific, 0 false otherwise'; INSERT INTO configuration (`category`, `instance`, `component`, `name`, `value`, `description`) VALUES ('Advanced', 'DEFAULT', 'NetworkManager', 'allow.subdomain.network.access', 'true', 'Allow subdomains to use networks dedicated to their parent domain(s)'); + +INSERT INTO configuration (`category`, `instance`, `component`, `name`, `value`, `description`) VALUES ('Advanced', 'DEFAULT', 'management-server', 'encode.api.response', 'true', 'Do UTF-8 encoding for the api response, true by default'); diff --git a/utils/LICENSE b/utils/LICENSE new file mode 100644 index 00000000000..6279e5206de --- /dev/null +++ b/utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 1999-2005 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/utils/src/com/cloud/utils/encoding/URLEncoder.java b/utils/src/com/cloud/utils/encoding/URLEncoder.java new file mode 100644 index 00000000000..ce20c6e2b86 --- /dev/null +++ b/utils/src/com/cloud/utils/encoding/URLEncoder.java @@ -0,0 +1,120 @@ +/* + * This file was modified by Cloud.com to omit encoding for non-ascii characters - this functionality is needed by multi-language support + * Line 86 was modified; lines 114-117 were added + */ + +/* + * Copyright 2011 Cloud.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.cloud.utils.encoding; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.BitSet; + +/** + * + * This class is very similar to the java.net.URLEncoder class. + * + * Unfortunately, with java.net.URLEncoder there is no way to specify to the + * java.net.URLEncoder which characters should NOT be encoded. + * + * This code was moved from DefaultServlet.java + * + * @author Craig R. McClanahan + * @author Remy Maucherat + */ + + + + +public class URLEncoder { + protected static final char[] hexadecimal = { '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + static CharsetEncoder asciiEncoder = + Charset.forName("US-ASCII").newEncoder(); // or "ISO-8859-1" for ISO Latin 1 + + //Array containing the safe characters set. + protected BitSet safeCharacters = new BitSet(256); + + public URLEncoder() { + for (char i = 'a'; i <= 'z'; i++) { + addSafeCharacter(i); + } + for (char i = 'A'; i <= 'Z'; i++) { + addSafeCharacter(i); + } + for (char i = '0'; i <= '9'; i++) { + addSafeCharacter(i); + } + } + + public void addSafeCharacter(char c) { + safeCharacters.set(c); + } + + public String encode(String path) { + int maxBytesPerChar = 10; + StringBuffer rewrittenPath = new StringBuffer(path.length()); + ByteArrayOutputStream buf = new ByteArrayOutputStream( + maxBytesPerChar); + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(buf, "UTF8"); + } catch (Exception e) { + e.printStackTrace(); + writer = new OutputStreamWriter(buf); + } + + for (int i = 0; i < path.length(); i++) { + int c = (int) path.charAt(i); + //NOTICE - !isPureAscii(path.charAt(i)) check was added by Cloud.com + if (safeCharacters.get(c) || !isPureAscii(path.charAt(i))) { + rewrittenPath.append((char) c); + } else { + // convert to external encoding before hex conversion + try { + writer.write((char) c); + writer.flush(); + } catch (IOException e) { + buf.reset(); + continue; + } + byte[] ba = buf.toByteArray(); + for (int j = 0; j < ba.length; j++) { + // Converting each byte in the buffer + byte toEncode = ba[j]; + rewrittenPath.append('%'); + int low = (int) (toEncode & 0x0f); + int high = (int) ((toEncode & 0xf0) >> 4); + rewrittenPath.append(hexadecimal[high]); + rewrittenPath.append(hexadecimal[low]); + } + buf.reset(); + } + } + return rewrittenPath.toString(); + } + + + //NOTICE - this part was added by Cloud.com + public static boolean isPureAscii(Character v) { + return asciiEncoder.canEncode(v); + } +}