001: package com.meterware.httpunit;
002:
003: /********************************************************************************************************************
004: * $Id: HttpUnitUtils.java,v 1.22 2005/09/05 23:41:39 russgold Exp $
005: *
006: * Copyright (c) 2000-2004, Russell Gold
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
014: * of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020: * DEALINGS IN THE SOFTWARE.
021: *
022: *******************************************************************************************************************/
023: import java.util.StringTokenizer;
024: import java.io.ByteArrayOutputStream;
025: import java.io.UnsupportedEncodingException;
026:
027: import javax.xml.parsers.DocumentBuilder;
028: import javax.xml.parsers.DocumentBuilderFactory;
029: import javax.xml.parsers.ParserConfigurationException;
030:
031: import org.xml.sax.SAXException;
032: import org.xml.sax.InputSource;
033: import org.xml.sax.EntityResolver;
034:
035: /**
036: * Utility code shared by httpunit and servletunit.
037: **/
038: public class HttpUnitUtils {
039:
040: public static final int DEFAULT_TEXT_BUFFER_SIZE = 2048;
041: public static final int DEFAULT_BUFFER_SIZE = 128;
042: public static final String DEFAULT_CHARACTER_SET = "iso-8859-1";
043:
044: /**
045: * Returns the content type and encoding as a pair of strings.
046: * If no character set is specified, the second entry will be null.
047: **/
048: public static String[] parseContentTypeHeader(String header) {
049: String[] result = new String[] { "text/plain", null };
050: StringTokenizer st = new StringTokenizer(header, ";=");
051: result[0] = st.nextToken();
052: while (st.hasMoreTokens()) {
053: String parameter = st.nextToken();
054: if (st.hasMoreTokens()) {
055: String value = stripQuotes(st.nextToken());
056: if (parameter.trim().equalsIgnoreCase("charset"))
057: result[1] = value;
058: }
059: }
060: return result;
061: }
062:
063: public static String stripQuotes(String value) {
064: if (value.startsWith("'") || value.startsWith("\""))
065: value = value.substring(1);
066: if (value.endsWith("'") || value.endsWith("\""))
067: value = value.substring(0, value.length() - 1);
068: return value;
069: }
070:
071: /**
072: * Returns an interpretation of the specified URL-encoded string, using the iso-8859-1 character set.
073: *
074: * @since 1.6
075: **/
076: public static String decode(String byteString) {
077: return decode(byteString, "iso-8859-1");
078: }
079:
080: /**
081: * Returns a string representation of a number, trimming off any trailing decimal zeros.
082: */
083: static String trimmedValue(Number number) {
084: String rawNumber = number.toString();
085: if (rawNumber.indexOf('.') == -1)
086: return rawNumber;
087:
088: int index = rawNumber.length();
089: while (rawNumber.charAt(index - 1) == '0')
090: index--;
091: if (rawNumber.charAt(index - 1) == '.')
092: index--;
093: return rawNumber.substring(0, index);
094: }
095:
096: /**
097: * Decodes a URL safe string into its original form using the
098: * specified character set. Escaped characters are converted back
099: * to their original representation.
100: *
101: * This method is copied from the <b>Jakarta Commons Codec</b>;
102: * <code>org.apache.commons.codec.net.URLCodec</code> class.
103: *
104: * @param string URL safe string to convert into its original form
105: * @return original string
106: * @throws IllegalArgumentException thrown if URL decoding is unsuccessful,
107: */
108: public static String decode(String string, String charset) {
109: try {
110: if (string == null)
111: return null;
112:
113: return new String(decodeUrl(string.getBytes("US-ASCII")),
114: charset);
115: } catch (UnsupportedEncodingException e) {
116: throw new RuntimeException(e.toString());
117: }
118: }
119:
120: /**
121: * Decodes an array of URL safe 7-bit characters into an array of
122: * original bytes. Escaped characters are converted back to their
123: * original representation.
124: *
125: * This method is copied from the <b>Jakarta Commons Codec</b>;
126: * <code>org.apache.commons.codec.net.URLCodec</code> class.
127: *
128: * @param pArray array of URL safe characters
129: * @return array of original bytes
130: */
131: private static final byte[] decodeUrl(byte[] pArray)
132: throws IllegalArgumentException {
133: ByteArrayOutputStream buffer = new ByteArrayOutputStream();
134: for (int i = 0; i < pArray.length; i++) {
135: int b = pArray[i];
136: if (b == '+') {
137: buffer.write(' ');
138: } else if (b != '%') {
139: buffer.write(b);
140: } else {
141: try {
142: int u = Character.digit((char) pArray[++i], 16);
143: int l = Character.digit((char) pArray[++i], 16);
144: if (u == -1 || l == -1)
145: throw new IllegalArgumentException(
146: "Invalid URL encoding");
147: buffer.write((char) ((u << 4) + l));
148: } catch (ArrayIndexOutOfBoundsException e) {
149: throw new IllegalArgumentException(
150: "Invalid URL encoding");
151: }
152: }
153: }
154: return buffer.toByteArray();
155: }
156:
157: /**
158: * creates a parser using JAXP API.
159: */
160: public static DocumentBuilder newParser() throws SAXException {
161: try {
162: DocumentBuilderFactory factory = DocumentBuilderFactory
163: .newInstance();
164: DocumentBuilder builder = factory.newDocumentBuilder();
165: builder
166: .setEntityResolver(new HttpUnitUtils.ClasspathEntityResolver());
167: return builder;
168: } catch (ParserConfigurationException ex) {
169: // redirect the new exception for code compatibility
170: throw new SAXException(ex);
171: }
172: }
173:
174: /**
175: * Returns a string array created by appending a string to an existing array. The existing array may be null.
176: **/
177: static String[] withNewValue(String[] oldValue, String newValue) {
178: String[] result;
179: if (oldValue == null) {
180: result = new String[] { newValue };
181: } else {
182: result = new String[oldValue.length + 1];
183: System.arraycopy(oldValue, 0, result, 0, oldValue.length);
184: result[oldValue.length] = newValue;
185: }
186: return result;
187: }
188:
189: /**
190: * Returns a string array created by appending an object to an existing array. The existing array may be null.
191: **/
192: static Object[] withNewValue(Object[] oldValue, Object newValue) {
193: Object[] result;
194: if (oldValue == null) {
195: result = new Object[] { newValue };
196: } else {
197: result = new Object[oldValue.length + 1];
198: System.arraycopy(oldValue, 0, result, 0, oldValue.length);
199: result[oldValue.length] = newValue;
200: }
201: return result;
202: }
203:
204: /**
205: * Return true if the first string contains the second.
206: * Case sensitivity is according to the setting of HttpUnitOptions.matchesIgnoreCase
207: */
208: static boolean contains(String string, String substring) {
209: if (HttpUnitOptions.getMatchesIgnoreCase()) {
210: return string.toUpperCase()
211: .indexOf(substring.toUpperCase()) >= 0;
212: } else {
213: return string.indexOf(substring) >= 0;
214: }
215: }
216:
217: /**
218: * Return true if the first string starts with the second.
219: * Case sensitivity is according to the setting of HttpUnitOptions.matchesIgnoreCase
220: */
221: static boolean hasPrefix(String string, String prefix) {
222: if (HttpUnitOptions.getMatchesIgnoreCase()) {
223: return string.toUpperCase()
224: .startsWith(prefix.toUpperCase());
225: } else {
226: return string.startsWith(prefix);
227: }
228: }
229:
230: /**
231: * Return true if the first string equals the second.
232: * Case sensitivity is according to the setting of HttpUnitOptions.matchesIgnoreCase
233: */
234: static boolean matches(String string1, String string2) {
235: if (HttpUnitOptions.getMatchesIgnoreCase()) {
236: return string1.equalsIgnoreCase(string2);
237: } else {
238: return string1.equals(string2);
239: }
240: }
241:
242: static boolean isJavaScriptURL(String urlString) {
243: return urlString.toLowerCase().startsWith("javascript:");
244: }
245:
246: /**
247: * Trims not only whitespace from the ends, but also from the middle. Spaces within quotes are respected.
248: */
249: static String trimAll(String s) {
250: s = s.trim();
251: if (s.indexOf(' ') < 0)
252: return s;
253:
254: boolean inQuotes = false;
255: StringBuffer sb = new StringBuffer();
256: char[] chars = s.toCharArray();
257: for (int i = 0; i < chars.length; i++) {
258: char aChar = chars[i];
259: if (aChar == '"' || aChar == '\'') {
260: inQuotes = !inQuotes;
261: sb.append(aChar);
262: } else if (inQuotes || (aChar > ' ')) {
263: sb.append(aChar);
264: }
265: }
266: return sb.toString();
267: }
268:
269: static String replaceEntities(String string) {
270: int i = 0;
271: int ampIndex;
272: while ((ampIndex = string.indexOf('&', i)) >= 0) {
273: int semiColonIndex = string.indexOf(';', ampIndex + 1);
274: if (semiColonIndex < 0)
275: break;
276: i = ampIndex + 1;
277:
278: String entityName = string.substring(ampIndex + 1,
279: semiColonIndex);
280: if (entityName.equalsIgnoreCase("amp")) {
281: string = string.substring(0, ampIndex) + '&'
282: + string.substring(semiColonIndex + 1);
283: }
284:
285: }
286: return string;
287: }
288:
289: /**
290: * Strips the fragment identifier (if any) from the Url.
291: */
292: static String trimFragment(String rawUrl) {
293: if (isJavaScriptURL(rawUrl))
294: return rawUrl;
295: final int hashIndex = rawUrl.indexOf('#');
296: return hashIndex < 0 ? rawUrl : rawUrl.substring(0, hashIndex);
297: }
298:
299: static class ClasspathEntityResolver implements EntityResolver {
300:
301: public InputSource resolveEntity(String publicID,
302: String systemID) {
303: if (systemID == null)
304: return null;
305:
306: String localName = systemID;
307: if (localName.indexOf("/") > 0) {
308: localName = localName.substring(localName
309: .lastIndexOf("/") + 1, localName.length());
310: }
311:
312: try {
313: return new InputSource(getClass().getClassLoader()
314: .getResourceAsStream(localName));
315: } catch (Exception e) {
316: return null;
317: }
318: }
319: }
320: }
|