001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Anahide Tchertchian
018 *     Florent Guillaume
019 */
020
021package org.nuxeo.common.utils;
022
023import java.io.UnsupportedEncodingException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URLDecoder;
027import java.net.URLEncoder;
028import java.util.ArrayList;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036/**
037 * Helper class to parse a URI or build one given parameters.
038 *
039 * @author Anahide Tchertchian
040 * @author Florent Guillaume
041 */
042public final class URIUtils {
043
044    private static final Log log = LogFactory.getLog(URIUtils.class);
045
046    // This is an utility class.
047    private URIUtils() {
048    }
049
050    /**
051     * Creates an URI query given the request parameters.
052     *
053     * @return an URI query given the request parameters.
054     */
055    public static String getURIQuery(Map<String, String> parameters) {
056        String query = null;
057        if (parameters != null) {
058            try {
059                List<String> items = new ArrayList<String>();
060                for (Map.Entry<String, String> paramInfo : parameters.entrySet()) {
061                    String key = paramInfo.getKey();
062                    String value = paramInfo.getValue();
063                    if (key != null) {
064                        if (value == null) {
065                            value = "";
066                        }
067                        items.add(String.format("%s=%s", URLEncoder.encode(key, "UTF-8"),
068                                URLEncoder.encode(value, "UTF-8")));
069                    }
070                }
071                query = org.apache.commons.lang.StringUtils.join(items, "&");
072            } catch (UnsupportedEncodingException e) {
073                log.error("Failed to get uri query", e);
074            }
075        }
076        return query;
077    }
078
079    /**
080     * Returns an URI path given the uri.
081     */
082    public static String getURIPath(String uri) {
083        if (uri == null) {
084            return null;
085        }
086        String path = uri;
087        int index = uri.indexOf('?');
088        if (index != -1) {
089            path = uri.substring(0, index);
090        }
091        return path;
092    }
093
094    /**
095     * @return a map with request parameters information given an URI query.
096     */
097    public static Map<String, String> getRequestParameters(String uriQuery) {
098        Map<String, String> parameters = null;
099        if (uriQuery != null && uriQuery.length() > 0) {
100            try {
101                String strippedQuery;
102                int index = uriQuery.indexOf('?');
103                if (index != -1) {
104                    strippedQuery = uriQuery.substring(index + 1);
105                } else {
106                    strippedQuery = uriQuery;
107                }
108                String[] items = strippedQuery.split("&");
109                if (items != null && items.length > 0) {
110                    parameters = new LinkedHashMap<String, String>();
111                    for (String item : items) {
112                        String[] param = item.split("=");
113                        if (param != null) {
114                            if (param.length == 2) {
115                                parameters.put(URLDecoder.decode(param[0], "UTF-8"),
116                                        URLDecoder.decode(param[1], "UTF-8"));
117                            } else if (param.length == 1) {
118                                parameters.put(URLDecoder.decode(param[0], "UTF-8"), null);
119                            }
120                        }
121                    }
122                }
123            } catch (UnsupportedEncodingException e) {
124                log.error("Failed to get request parameters from uri", e);
125            }
126        }
127        return parameters;
128    }
129
130    public static String addParametersToURIQuery(String uriString, Map<String, String> parameters) {
131        if (uriString == null || parameters == null || parameters.isEmpty()) {
132            return uriString;
133        }
134        String uriPath = getURIPath(uriString);
135        Map<String, String> existingParams = getRequestParameters(uriString.substring(uriPath.length()));
136        if (existingParams == null) {
137            existingParams = new LinkedHashMap<String, String>();
138        }
139        existingParams.putAll(parameters);
140        String res = null;
141        if (!existingParams.isEmpty()) {
142            String newQuery = getURIQuery(existingParams);
143            res = uriPath + '?' + newQuery;
144        } else {
145            res = uriPath;
146        }
147        return res;
148    }
149
150    public static String quoteURIPathComponent(String s, boolean quoteSlash) {
151        return quoteURIPathComponent(s, quoteSlash, true);
152    }
153
154    /**
155     * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path
156     *
157     * @since 5.6
158     * @param the uri path to quote
159     * @param quoteSlash true if "/" character should be quoted and replaced by %2F
160     * @param quoteAt true if "@" character should be quoted and replaced by %40
161     */
162    public static String quoteURIPathComponent(String s, boolean quoteSlash, boolean quoteAt) {
163        return quoteURIPathComponent(s, quoteSlash ? "%2F" : null, quoteAt ? "%40" : null);
164    }
165
166    /**
167     * Quotes a URI path token. For example, a blob filename. It replaces "/" by "-".
168     *
169     * @since 7.3
170     * @param the uri path token to quote
171     */
172    public static String quoteURIPathToken(String s) {
173        return quoteURIPathComponent(s, "-", "%40");
174    }
175
176    /**
177     * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path
178     *
179     * @since 5.6
180     * @param the uri path to quote
181     * @param slashSequence if null, do not quote "/", otherwise, replace "/" by the given sequence
182     * @param atSequence if null, do not quote "@", otherwise, replace "@" by the given sequence
183     */
184    protected static String quoteURIPathComponent(String s, String slashSequence, String atSequence) {
185        if ("".equals(s)) {
186            return s;
187        }
188        URI uri;
189        try {
190            // fake scheme so that a colon is not mistaken as a scheme
191            uri = new URI("x", s, null);
192        } catch (URISyntaxException e) {
193            throw new IllegalArgumentException("Illegal characters in: " + s, e);
194        }
195        String r = uri.toASCIIString().substring(2);
196        // replace reserved characters ;:$&+=?/[]@
197        // FIXME: find a better way to do this...
198        r = r.replace(";", "%3B");
199        r = r.replace(":", "%3A");
200        r = r.replace("$", "%24");
201        r = r.replace("&", "%26");
202        r = r.replace("+", "%2B");
203        r = r.replace("=", "%3D");
204        r = r.replace("?", "%3F");
205        r = r.replace("[", "%5B");
206        r = r.replace("]", "%5D");
207        if (atSequence != null) {
208            r = r.replace("@", atSequence);
209        }
210        if (slashSequence != null) {
211            r = r.replace("/", slashSequence);
212        }
213        return r;
214    }
215
216    public static String unquoteURIPathComponent(String s) {
217        if ("".equals(s)) {
218            return s;
219        }
220        URI uri;
221        try {
222            uri = new URI("http://x/" + s);
223        } catch (URISyntaxException e) {
224            throw new IllegalArgumentException("Illegal characters in: " + s, e);
225        }
226        String path = uri.getPath();
227        if (path.startsWith("/") && !s.startsWith("/")) {
228            path = path.substring(1);
229        }
230        return path;
231    }
232
233}