001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.util;
018    
019    import java.io.UnsupportedEncodingException;
020    import java.net.URI;
021    import java.net.URISyntaxException;
022    import java.net.URLDecoder;
023    import java.net.URLEncoder;
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.LinkedHashMap;
027    import java.util.List;
028    import java.util.Map;
029    
030    /**
031     * URI utilities.
032     *
033     * @version $Revision: 795369 $
034     */
035    public final class URISupport {
036    
037        private static final String CHARSET = "UTF-8";
038    
039        private URISupport() {
040            // Helper class
041        }
042    
043        /**
044         * Holder to get parts of the URI.
045         */
046        public static class CompositeData {
047            public String host;
048    
049            String scheme;
050            String path;
051            URI components[];
052            Map parameters;
053            String fragment;
054    
055            public URI[] getComponents() {
056                return components;
057            }
058    
059            public String getFragment() {
060                return fragment;
061            }
062    
063            public Map getParameters() {
064                return parameters;
065            }
066    
067            public String getScheme() {
068                return scheme;
069            }
070    
071            public String getPath() {
072                return path;
073            }
074    
075            public String getHost() {
076                return host;
077            }
078    
079            public URI toURI() throws URISyntaxException {
080                StringBuffer sb = new StringBuffer();
081                if (scheme != null) {
082                    sb.append(scheme);
083                    sb.append(':');
084                }
085    
086                if (host != null && host.length() != 0) {
087                    sb.append(host);
088                } else {
089                    sb.append('(');
090                    for (int i = 0; i < components.length; i++) {
091                        if (i != 0) {
092                            sb.append(',');
093                        }
094                        sb.append(components[i].toString());
095                    }
096                    sb.append(')');
097                }
098    
099                if (path != null) {
100                    sb.append('/');
101                    sb.append(path);
102                }
103                if (!parameters.isEmpty()) {
104                    sb.append("?");
105                    sb.append(createQueryString(parameters));
106                }
107                if (fragment != null) {
108                    sb.append("#");
109                    sb.append(fragment);
110                }
111                return new URI(sb.toString());
112            }
113        }
114    
115        @SuppressWarnings("unchecked")
116        public static Map parseQuery(String uri) throws URISyntaxException {
117            try {
118                // use a linked map so the parameters is in the same order
119                Map rc = new LinkedHashMap();
120                if (uri != null) {
121                    String[] parameters = uri.split("&");
122                    for (String parameter : parameters) {
123                        int p = parameter.indexOf("=");
124                        if (p >= 0) {
125                            String name = URLDecoder.decode(parameter.substring(0, p), CHARSET);
126                            String value = URLDecoder.decode(parameter.substring(p + 1), CHARSET);
127                            rc.put(name, value);
128                        } else {
129                            rc.put(parameter, null);
130                        }
131                    }
132                }
133                return rc;
134            } catch (UnsupportedEncodingException e) {
135                URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
136                se.initCause(e);
137                throw se;
138            }
139        }
140    
141        public static Map parseParameters(URI uri) throws URISyntaxException {
142            String query = uri.getQuery();
143            if (query == null) {
144                String schemeSpecificPart = uri.getSchemeSpecificPart();
145                int idx = schemeSpecificPart.lastIndexOf('?');
146                if (idx < 0) {
147                    return Collections.EMPTY_MAP;
148                } else {
149                    query = schemeSpecificPart.substring(idx + 1);
150                }
151            } else {
152                query = stripPrefix(query, "?");
153            }
154            return parseQuery(query);
155        }
156    
157        /**
158         * Removes any URI query from the given uri
159         */
160        public static URI removeQuery(URI uri) throws URISyntaxException {
161            return createURIWithQuery(uri, null);
162        }
163    
164        /**
165         * Creates a URI with the given query
166         */
167        public static URI createURIWithQuery(URI uri, String query) throws URISyntaxException {
168            return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(),
169                           query, uri.getFragment());
170        }
171    
172        public static CompositeData parseComposite(URI uri) throws URISyntaxException {
173    
174            CompositeData rc = new CompositeData();
175            rc.scheme = uri.getScheme();
176            String ssp = stripPrefix(uri.getSchemeSpecificPart().trim(), "//").trim();
177    
178            parseComposite(uri, rc, ssp);
179    
180            rc.fragment = uri.getFragment();
181            return rc;
182        }
183    
184        private static void parseComposite(URI uri, CompositeData rc, String ssp) throws URISyntaxException {
185            String componentString;
186            String params;
187    
188            if (!checkParenthesis(ssp)) {
189                throw new URISyntaxException(uri.toString(), "Not a matching number of '(' and ')' parenthesis");
190            }
191    
192            int p;
193            int intialParen = ssp.indexOf('(');
194            if (intialParen == 0) {
195                rc.host = ssp.substring(0, intialParen);
196                p = rc.host.indexOf("/");
197                if (p >= 0) {
198                    rc.path = rc.host.substring(p);
199                    rc.host = rc.host.substring(0, p);
200                }
201                p = ssp.lastIndexOf(')');
202                componentString = ssp.substring(intialParen + 1, p);
203                params = ssp.substring(p + 1).trim();
204            } else {
205                componentString = ssp;
206                params = "";
207            }
208    
209            String components[] = splitComponents(componentString);
210            rc.components = new URI[components.length];
211            for (int i = 0; i < components.length; i++) {
212                rc.components[i] = new URI(components[i].trim());
213            }
214    
215            p = params.indexOf('?');
216            if (p >= 0) {
217                if (p > 0) {
218                    rc.path = stripPrefix(params.substring(0, p), "/");
219                }
220                rc.parameters = parseQuery(params.substring(p + 1));
221            } else {
222                if (params.length() > 0) {
223                    rc.path = stripPrefix(params, "/");
224                }
225                rc.parameters = Collections.EMPTY_MAP;
226            }
227        }
228    
229        @SuppressWarnings("unchecked")
230        private static String[] splitComponents(String str) {
231            ArrayList l = new ArrayList();
232    
233            int last = 0;
234            int depth = 0;
235            char chars[] = str.toCharArray();
236            for (int i = 0; i < chars.length; i++) {
237                switch (chars[i]) {
238                case '(':
239                    depth++;
240                    break;
241                case ')':
242                    depth--;
243                    break;
244                case ',':
245                    if (depth == 0) {
246                        String s = str.substring(last, i);
247                        l.add(s);
248                        last = i + 1;
249                    }
250                    break;
251                default:
252                }
253            }
254    
255            String s = str.substring(last);
256            if (s.length() != 0) {
257                l.add(s);
258            }
259    
260            String rc[] = new String[l.size()];
261            l.toArray(rc);
262            return rc;
263        }
264    
265        public static String stripPrefix(String value, String prefix) {
266            if (value.startsWith(prefix)) {
267                return value.substring(prefix.length());
268            }
269            return value;
270        }
271    
272        public static URI stripScheme(URI uri) throws URISyntaxException {
273            return new URI(stripPrefix(uri.getSchemeSpecificPart().trim(), "//"));
274        }
275    
276        public static String createQueryString(Map options) throws URISyntaxException {
277            try {
278                if (options.size() > 0) {
279                    StringBuffer rc = new StringBuffer();
280                    boolean first = true;
281                    for (Object o : options.keySet()) {
282                        if (first) {
283                            first = false;
284                        } else {
285                            rc.append("&");
286                        }
287    
288                        String key = (String) o;
289                        String value = (String) options.get(key);
290                        rc.append(URLEncoder.encode(key, CHARSET));
291                        // only append if value is not null
292                        if (value != null) {
293                            rc.append("=");
294                            rc.append(URLEncoder.encode(value, CHARSET));
295                        }
296                    }
297                    return rc.toString();
298                } else {
299                    return "";
300                }
301            } catch (UnsupportedEncodingException e) {
302                URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
303                se.initCause(e);
304                throw se;
305            }
306        }
307    
308        /**
309         * Creates a URI from the original URI and the remaining parameters
310         */
311        public static URI createRemainingURI(URI originalURI, Map params) throws URISyntaxException {
312            String s = createQueryString(params);
313            if (s.length() == 0) {
314                s = null;
315            }
316            return createURIWithQuery(originalURI, s);
317        }
318    
319        public static URI changeScheme(URI bindAddr, String scheme) throws URISyntaxException {
320            return new URI(scheme, bindAddr.getUserInfo(), bindAddr.getHost(), bindAddr.getPort(), bindAddr
321                .getPath(), bindAddr.getQuery(), bindAddr.getFragment());
322        }
323    
324        public static boolean checkParenthesis(String str) {
325            boolean result = true;
326            if (str != null) {
327                int open = 0;
328                int closed = 0;
329    
330                int i = 0;
331                while ((i = str.indexOf('(', i)) >= 0) {
332                    i++;
333                    open++;
334                }
335                i = 0;
336                while ((i = str.indexOf(')', i)) >= 0) {
337                    i++;
338                    closed++;
339                }
340                result = open == closed;
341            }
342            return result;
343        }
344    
345        /**
346         * Normalizes the uri by reordering the parameters so they are sorted and thus
347         * we can use the uris for endpoint matching.
348         *
349         * @param uri the uri
350         * @return the normalized uri
351         * @throws URISyntaxException in thrown if the uri syntax is invalid
352         */
353        @SuppressWarnings("unchecked")
354        public static String normalizeUri(String uri) throws URISyntaxException {
355    
356            URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
357            String path = u.getSchemeSpecificPart();
358            String scheme = u.getScheme();
359    
360            // not possible to normalize
361            if (scheme == null || path == null) {
362                return uri;
363            }
364    
365            // lets trim off any query arguments
366            if (path.startsWith("//")) {
367                path = path.substring(2);
368            }
369            int idx = path.indexOf('?');
370            if (idx > 0) {
371                path = path.substring(0, idx);
372            }
373    
374            // in case there are parameters we should reorder them
375            Map parameters = URISupport.parseParameters(u);
376            if (parameters.isEmpty()) {
377                // no parameters then just return
378                return buildUri(scheme, path, null);
379            } else {
380                // reorder parameters a..z
381                List<String> keys = new ArrayList<String>(parameters.keySet());
382                Collections.sort(keys);
383    
384                Map<String, Object> sorted = new LinkedHashMap<String, Object>(parameters.size());
385                for (String key : keys) {
386                    sorted.put(key, parameters.get(key));
387                }
388    
389                // build uri object with sorted parameters
390                String query = URISupport.createQueryString(sorted);
391                return buildUri(scheme, path, query);
392            }
393        }
394    
395        private static String buildUri(String scheme, String path, String query) {
396            // must include :// to do a correct URI all components can work with
397            return scheme + "://" + path + (query != null ? "?" + query : "");
398        }
399       
400    }