1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package com.sun.star.lib.uno.helper;
25 import java.io.UnsupportedEncodingException;
26 import java.util.HashMap;
27 import java.util.Vector;
28 
29 /**
30  * Object representation and parsing of Uno Urls,
31  * which allow to locate a named Uno object in a
32  * different process. An Uno Url consists of the
33  * specification of a connection, protocol and
34  * rootOid delimited with a ';'.
35  * The syntax of an Uno Url is
36  *
37  * <code>
38  * [uno:]connection-type,parameters;protocol-name,parameters;objectname";
39  * </code>
40  *
41  * An example Uno Url will look like this:
42  *
43  * <code>
44  * socket,host=localhost,port=2002;urp;StarOffice.ServiceManager
45  * </code>
46  *
47  * For more information about Uno Url please consult
48  * <a href="http://udk.openoffice.org/common/man/spec/uno-url.html">
49  * http://udk.openoffice.org/common/man/spec/uno-url.html</a>
50  *
51  * Usage:
52  *
53  * <code>
54  *   UnoUrl url = UnoUrl.parseUnoUrl("socket,host=localhost,port=2002;urp;StarOffice.ServiceManager");
55  * </code>
56  *
57  * @author Joerg Brunsmann
58  */
59 public class UnoUrl {
60 
61 	private static final String FORMAT_ERROR =
62 		"syntax: [uno:]connection-type,parameters;protocol-name,parameters;objectname";
63 
64 	private static final String VALUE_CHAR_SET = "!$&'()*+-./:?@_~";
65 	private static final String OID_CHAR_SET = VALUE_CHAR_SET + ",=";
66 
67 	private UnoUrlPart connection;
68 	private UnoUrlPart protocol;
69 	private String rootOid;
70 
71 	static private class UnoUrlPart {
72 
73 		private String partTypeName;
74 		private HashMap partParameters;
75 		private String uninterpretedParameterString;
76 
77 		public UnoUrlPart(
78 			String uninterpretedParameterString,
79 			String partTypeName,
80 			HashMap partParameters) {
81 			this.uninterpretedParameterString = uninterpretedParameterString;
82 			this.partTypeName = partTypeName;
83 			this.partParameters = partParameters;
84 		}
85 
86 		public String getPartTypeName() {
87 			return partTypeName;
88 		}
89 
90 		public HashMap getPartParameters() {
91 			return partParameters;
92 		}
93 
94 		public String getUninterpretedParameterString() {
95 			return uninterpretedParameterString;
96 		}
97 
98         public String getUninterpretedString() {
99             StringBuffer buf = new StringBuffer(partTypeName);
100             if (uninterpretedParameterString.length() > 0) {
101                 buf.append(',');
102                 buf.append(uninterpretedParameterString);
103             }
104             return buf.toString();
105         }
106 	}
107 
108 	private UnoUrl(
109 		UnoUrlPart connectionPart,
110 		UnoUrlPart protocolPart,
111 		String rootOid) {
112 		this.connection = connectionPart;
113 		this.protocol = protocolPart;
114 		this.rootOid = rootOid;
115 	}
116 
117 	/**
118 	 * Returns the name of the connection of this
119 	 * Uno Url. Encoded characters are not allowed.
120 	 *
121 	 * @return The connection name as string.
122 	 */
123 	public String getConnection() {
124 		return connection.getPartTypeName();
125 	}
126 
127 	/**
128 	 * Returns the name of the protocol of this
129 	 * Uno Url. Encoded characters are not allowed.
130 	 *
131 	 * @return The protocol name as string.
132 	 */
133 	public String getProtocol() {
134 		return protocol.getPartTypeName();
135 	}
136 
137 	/**
138 	 * Return the object name. Encoded character are
139 	 * not allowed.
140 	 *
141 	 * @return The object name as String.
142 	 */
143 	public String getRootOid() {
144 		return rootOid;
145 	}
146 
147 	/**
148 	 * Returns the protocol parameters as
149 	 * a Hashmap with key/value pairs. Encoded
150 	 * characters like '%41' are decoded.
151 	 *
152 	 * @return a HashMap with key/value pairs for protocol parameters.
153 	 */
154 	public HashMap getProtocolParameters() {
155 		return protocol.getPartParameters();
156 	}
157 
158 	/**
159 	 * Returns the connection parameters as
160 	 * a Hashmap with key/value pairs. Encoded
161 	 * characters like '%41' are decoded.
162 	 *
163 	 * @return a HashMap with key/value pairs for connection parameters.
164 	 */
165 	public HashMap getConnectionParameters() {
166 		return connection.getPartParameters();
167 	}
168 
169 	/**
170 	 * Returns the raw specification of the protocol
171 	 * parameters. Encoded characters like '%41' are
172 	 * not decoded.
173 	 *
174 	 * @return The uninterpreted protocol parameters as string.
175 	 */
176 	public String getProtocolParametersAsString() {
177 		return protocol.getUninterpretedParameterString();
178 	}
179 
180 	/**
181 	 * Returns the raw specification of the connection
182 	 * parameters. Encoded characters like '%41' are
183 	 * not decoded.
184 	 *
185 	 * @return The uninterpreted connection parameters as string.
186 	 */
187 	public String getConnectionParametersAsString() {
188 		return connection.getUninterpretedParameterString();
189 	}
190 
191     /**
192      * Returns the raw specification of the protocol
193      * name and parameters. Encoded characters like '%41' are
194      * not decoded.
195      *
196      * @return The uninterpreted protocol name and parameters as string.
197      */
198     public String getProtocolAndParametersAsString() {
199         return protocol.getUninterpretedString();
200     }
201 
202     /**
203      * Returns the raw specification of the connection
204      * name and parameters. Encoded characters like '%41' are
205      * not decoded.
206      *
207      * @return The uninterpreted connection name and parameters as string.
208      */
209     public String getConnectionAndParametersAsString() {
210         return connection.getUninterpretedString();
211     }
212 
213 	private static int hexToInt(int ch)
214 		throws com.sun.star.lang.IllegalArgumentException {
215 		int c = Character.toLowerCase((char) ch);
216 		boolean isDigit = ('0' <= c && c <= '9');
217 		boolean isValidChar = ('a' <= c && c <= 'f') || isDigit;
218 
219 		if (!isValidChar)
220 			throw new com.sun.star.lang.IllegalArgumentException(
221 				"Invalid UTF-8 hex byte '" + c + "'.");
222 
223 		return isDigit ? ch - '0' : 10 + ((char) c - 'a') & 0xF;
224 	}
225 
226 	private static String decodeUTF8(String s)
227 		throws com.sun.star.lang.IllegalArgumentException {
228 		Vector v = new Vector();
229 
230 		for (int i = 0; i < s.length(); i++) {
231 			int ch = s.charAt(i);
232 
233 			if (ch == '%') {
234 				int hb = hexToInt(s.charAt(++i));
235 				int lb = hexToInt(s.charAt(++i));
236 				ch = (hb << 4) | lb;
237 			}
238 
239 			v.addElement(new Integer(ch));
240 		}
241 
242 		int size = v.size();
243 		byte[] bytes = new byte[size];
244 		for (int i = 0; i < size; i++) {
245 			Integer anInt = (Integer) v.elementAt(i);
246 			bytes[i] = (byte) (anInt.intValue() & 0xFF);
247 		}
248 
249 		try {
250 			return new String(bytes, "UTF-8");
251 		} catch (UnsupportedEncodingException e) {
252 			throw new com.sun.star.lang.IllegalArgumentException(
253 				"Couldn't convert parameter string to UTF-8 string:" + e.getMessage());
254 		}
255 	}
256 
257 	private static HashMap buildParamHashMap(String paramString)
258 		throws com.sun.star.lang.IllegalArgumentException {
259 		HashMap params = new HashMap();
260 
261 		int pos = 0;
262 
263 		while (true) {
264 			char c = ',';
265 			String aKey = "";
266 			String aValue = "";
267 
268 			while ((pos < paramString.length())
269 				&& ((c = paramString.charAt(pos++)) != '=')) {
270 				aKey += c;
271 			}
272 
273 			while ((pos < paramString.length())
274 				&& ((c = paramString.charAt(pos++)) != ',')
275 				&& c != ';') {
276 				aValue += c;
277 			}
278 
279 			if ((aKey.length() > 0) && (aValue.length() > 0)) {
280 
281 				if (!isAlphaNumeric(aKey)) {
282 					throw new com.sun.star.lang.IllegalArgumentException(
283 						"The parameter key '"
284 							+ aKey
285 							+ "' may only consist of alpha numeric ASCII characters.");
286 				}
287 
288 				if (!isValidString(aValue, VALUE_CHAR_SET + "%")) {
289 					throw new com.sun.star.lang.IllegalArgumentException(
290 						"The parameter value for key '" + aKey + "' contains illegal characters.");
291 				}
292 
293 				params.put(aKey, decodeUTF8(aValue));
294 			}
295 
296 			if ((pos >= paramString.length()) || (c != ','))
297 				break;
298 
299 		}
300 
301 		return params;
302 	}
303 
304 	private static UnoUrlPart parseUnoUrlPart(String thePart)
305 		throws com.sun.star.lang.IllegalArgumentException {
306 		String partName = thePart;
307 		String theParamPart = "";
308 		int index = thePart.indexOf(",");
309 		if (index != -1) {
310 			partName = thePart.substring(0, index).trim();
311 			theParamPart = thePart.substring(index + 1).trim();
312 		}
313 
314 		if (!isAlphaNumeric(partName)) {
315 			throw new com.sun.star.lang.IllegalArgumentException(
316 				"The part name '"
317 					+ partName
318 					+ "' may only consist of alpha numeric ASCII characters.");
319 		}
320 
321 		HashMap params = buildParamHashMap(theParamPart);
322 
323 		return new UnoUrlPart(theParamPart, partName, params);
324 	}
325 
326 	private static boolean isAlphaNumeric(String s) {
327 		return isValidString(s, null);
328 	}
329 
330 	private static boolean isValidString(String identifier, String validCharSet) {
331 
332 		int len = identifier.length();
333 
334 		for (int i = 0; i < len; i++) {
335 
336 			int ch = identifier.charAt(i);
337 
338 			boolean isValidChar =
339 				('A' <= ch && ch <= 'Z')
340 					|| ('a' <= ch && ch <= 'z')
341 					|| ('0' <= ch && ch <= '9');
342 
343 			if (!isValidChar && (validCharSet != null)) {
344 				isValidChar = (validCharSet.indexOf(ch) != -1);
345 			}
346 
347 			if (!isValidChar)
348 				return false;
349 		}
350 
351 		return true;
352 	}
353 
354 	/**
355 	 * Parses the given Uno Url and returns
356 	 * an in memory object representation.
357 	 *
358 	 * @param unoUrl The given uno URl as string.
359 	 * @return Object representation of class UnoUrl.
360 	 * @throws IllegalArgumentException if Url cannot be parsed.
361 	 */
362 	public static UnoUrl parseUnoUrl(String unoUrl)
363 		throws com.sun.star.lang.IllegalArgumentException {
364 
365 		String url = unoUrl;
366 
367 		int index = url.indexOf(':');
368 		if (index != -1) {
369 			String unoStr = url.substring(0, index).trim();
370 			if (!"uno".equals(unoStr)) {
371 				throw new com.sun.star.lang.IllegalArgumentException(
372 					"Uno Urls must start with 'uno:'. " + FORMAT_ERROR);
373 			}
374 		}
375 
376 		url = url.substring(index + 1).trim();
377 
378 		index = url.indexOf(';');
379 		if (index == -1) {
380 			throw new com.sun.star.lang.IllegalArgumentException("'"+unoUrl+"' is an invalid Uno Url. " + FORMAT_ERROR);
381 		}
382 
383 		String connection = url.substring(0, index).trim();
384 		url = url.substring(index + 1).trim();
385 
386 		UnoUrlPart connectionPart = parseUnoUrlPart(connection);
387 
388 		index = url.indexOf(';');
389 		if (index == -1) {
390 			throw new com.sun.star.lang.IllegalArgumentException("'"+unoUrl+"' is an invalid Uno Url. " + FORMAT_ERROR);
391 		}
392 
393 		String protocol = url.substring(0, index).trim();
394 		url = url.substring(index + 1).trim();
395 
396 		UnoUrlPart protocolPart = parseUnoUrlPart(protocol);
397 
398 		String rootOid = url.trim();
399 		if (!isValidString(rootOid, OID_CHAR_SET)) {
400 			throw new com.sun.star.lang.IllegalArgumentException(
401 				"Root OID '"+ rootOid + "' contains illegal characters.");
402 		}
403 
404 		return new UnoUrl(connectionPart, protocolPart, rootOid);
405 
406 	}
407 
408 }
409