/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.sun.star.lib.uno.helper; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Vector; /** * Object representation and parsing of Uno Urls, * which allow to locate a named Uno object in a * different process. An Uno Url consists of the * specification of a connection, protocol and * rootOid delimited with a ';'. * The syntax of an Uno Url is * * * [uno:]connection-type,parameters;protocol-name,parameters;objectname"; * * * An example Uno Url will look like this: * * * socket,host=localhost,port=2002;urp;StarOffice.ServiceManager * * * For more information about Uno Url please consult * * http://udk.openoffice.org/common/man/spec/uno-url.html * * Usage: * * * UnoUrl url = UnoUrl.parseUnoUrl("socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"); * * * @author Joerg Brunsmann */ public class UnoUrl { private static final String FORMAT_ERROR = "syntax: [uno:]connection-type,parameters;protocol-name,parameters;objectname"; private static final String VALUE_CHAR_SET = "!$&'()*+-./:?@_~"; private static final String OID_CHAR_SET = VALUE_CHAR_SET + ",="; private UnoUrlPart connection; private UnoUrlPart protocol; private String rootOid; static private class UnoUrlPart { private String partTypeName; private HashMap partParameters; private String uninterpretedParameterString; public UnoUrlPart( String uninterpretedParameterString, String partTypeName, HashMap partParameters) { this.uninterpretedParameterString = uninterpretedParameterString; this.partTypeName = partTypeName; this.partParameters = partParameters; } public String getPartTypeName() { return partTypeName; } public HashMap getPartParameters() { return partParameters; } public String getUninterpretedParameterString() { return uninterpretedParameterString; } public String getUninterpretedString() { StringBuffer buf = new StringBuffer(partTypeName); if (uninterpretedParameterString.length() > 0) { buf.append(','); buf.append(uninterpretedParameterString); } return buf.toString(); } } private UnoUrl( UnoUrlPart connectionPart, UnoUrlPart protocolPart, String rootOid) { this.connection = connectionPart; this.protocol = protocolPart; this.rootOid = rootOid; } /** * Returns the name of the connection of this * Uno Url. Encoded characters are not allowed. * * @return The connection name as string. */ public String getConnection() { return connection.getPartTypeName(); } /** * Returns the name of the protocol of this * Uno Url. Encoded characters are not allowed. * * @return The protocol name as string. */ public String getProtocol() { return protocol.getPartTypeName(); } /** * Return the object name. Encoded character are * not allowed. * * @return The object name as String. */ public String getRootOid() { return rootOid; } /** * Returns the protocol parameters as * a Hashmap with key/value pairs. Encoded * characters like '%41' are decoded. * * @return a HashMap with key/value pairs for protocol parameters. */ public HashMap getProtocolParameters() { return protocol.getPartParameters(); } /** * Returns the connection parameters as * a Hashmap with key/value pairs. Encoded * characters like '%41' are decoded. * * @return a HashMap with key/value pairs for connection parameters. */ public HashMap getConnectionParameters() { return connection.getPartParameters(); } /** * Returns the raw specification of the protocol * parameters. Encoded characters like '%41' are * not decoded. * * @return The uninterpreted protocol parameters as string. */ public String getProtocolParametersAsString() { return protocol.getUninterpretedParameterString(); } /** * Returns the raw specification of the connection * parameters. Encoded characters like '%41' are * not decoded. * * @return The uninterpreted connection parameters as string. */ public String getConnectionParametersAsString() { return connection.getUninterpretedParameterString(); } /** * Returns the raw specification of the protocol * name and parameters. Encoded characters like '%41' are * not decoded. * * @return The uninterpreted protocol name and parameters as string. */ public String getProtocolAndParametersAsString() { return protocol.getUninterpretedString(); } /** * Returns the raw specification of the connection * name and parameters. Encoded characters like '%41' are * not decoded. * * @return The uninterpreted connection name and parameters as string. */ public String getConnectionAndParametersAsString() { return connection.getUninterpretedString(); } private static int hexToInt(int ch) throws com.sun.star.lang.IllegalArgumentException { int c = Character.toLowerCase((char) ch); boolean isDigit = ('0' <= c && c <= '9'); boolean isValidChar = ('a' <= c && c <= 'f') || isDigit; if (!isValidChar) throw new com.sun.star.lang.IllegalArgumentException( "Invalid UTF-8 hex byte '" + c + "'."); return isDigit ? ch - '0' : 10 + ((char) c - 'a') & 0xF; } private static String decodeUTF8(String s) throws com.sun.star.lang.IllegalArgumentException { Vector v = new Vector(); for (int i = 0; i < s.length(); i++) { int ch = s.charAt(i); if (ch == '%') { int hb = hexToInt(s.charAt(++i)); int lb = hexToInt(s.charAt(++i)); ch = (hb << 4) | lb; } v.addElement(ch); } int size = v.size(); byte[] bytes = new byte[size]; for (int i = 0; i < size; i++) { Integer anInt = (Integer) v.elementAt(i); bytes[i] = (byte) (anInt.intValue() & 0xFF); } try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new com.sun.star.lang.IllegalArgumentException( "Couldn't convert parameter string to UTF-8 string:" + e.getMessage()); } } private static HashMap buildParamHashMap(String paramString) throws com.sun.star.lang.IllegalArgumentException { HashMap params = new HashMap(); int pos = 0; while (true) { char c = ','; String aKey = ""; String aValue = ""; while ((pos < paramString.length()) && ((c = paramString.charAt(pos++)) != '=')) { aKey += c; } while ((pos < paramString.length()) && ((c = paramString.charAt(pos++)) != ',') && c != ';') { aValue += c; } if ((aKey.length() > 0) && (aValue.length() > 0)) { if (!isAlphaNumeric(aKey)) { throw new com.sun.star.lang.IllegalArgumentException( "The parameter key '" + aKey + "' may only consist of alpha numeric ASCII characters."); } if (!isValidString(aValue, VALUE_CHAR_SET + "%")) { throw new com.sun.star.lang.IllegalArgumentException( "The parameter value for key '" + aKey + "' contains illegal characters."); } params.put(aKey, decodeUTF8(aValue)); } if ((pos >= paramString.length()) || (c != ',')) break; } return params; } private static UnoUrlPart parseUnoUrlPart(String thePart) throws com.sun.star.lang.IllegalArgumentException { String partName = thePart; String theParamPart = ""; int index = thePart.indexOf(","); if (index != -1) { partName = thePart.substring(0, index).trim(); theParamPart = thePart.substring(index + 1).trim(); } if (!isAlphaNumeric(partName)) { throw new com.sun.star.lang.IllegalArgumentException( "The part name '" + partName + "' may only consist of alpha numeric ASCII characters."); } HashMap params = buildParamHashMap(theParamPart); return new UnoUrlPart(theParamPart, partName, params); } private static boolean isAlphaNumeric(String s) { return isValidString(s, null); } private static boolean isValidString(String identifier, String validCharSet) { int len = identifier.length(); for (int i = 0; i < len; i++) { int ch = identifier.charAt(i); boolean isValidChar = ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9'); if (!isValidChar && (validCharSet != null)) { isValidChar = (validCharSet.indexOf(ch) != -1); } if (!isValidChar) return false; } return true; } /** * Parses the given Uno Url and returns * an in memory object representation. * * @param unoUrl The given uno URl as string. * @return Object representation of class UnoUrl. * @throws IllegalArgumentException if Url cannot be parsed. */ public static UnoUrl parseUnoUrl(String unoUrl) throws com.sun.star.lang.IllegalArgumentException { String url = unoUrl; int index = url.indexOf(':'); if (index != -1) { String unoStr = url.substring(0, index).trim(); if (!"uno".equals(unoStr)) { throw new com.sun.star.lang.IllegalArgumentException( "Uno Urls must start with 'uno:'. " + FORMAT_ERROR); } } url = url.substring(index + 1).trim(); index = url.indexOf(';'); if (index == -1) { throw new com.sun.star.lang.IllegalArgumentException("'"+unoUrl+"' is an invalid Uno Url. " + FORMAT_ERROR); } String connection = url.substring(0, index).trim(); url = url.substring(index + 1).trim(); UnoUrlPart connectionPart = parseUnoUrlPart(connection); index = url.indexOf(';'); if (index == -1) { throw new com.sun.star.lang.IllegalArgumentException("'"+unoUrl+"' is an invalid Uno Url. " + FORMAT_ERROR); } String protocol = url.substring(0, index).trim(); url = url.substring(index + 1).trim(); UnoUrlPart protocolPart = parseUnoUrlPart(protocol); String rootOid = url.trim(); if (!isValidString(rootOid, OID_CHAR_SET)) { throw new com.sun.star.lang.IllegalArgumentException( "Root OID '"+ rootOid + "' contains illegal characters."); } return new UnoUrl(connectionPart, protocolPart, rootOid); } }