001/*
002 * (C) Copyright 2006-2017 Nuxeo (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 *     Kevin Leturc <kleturc@nuxeo.com>
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<>();
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 = String.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.length > 0) {
110                    parameters = new LinkedHashMap<>();
111                    for (String item : items) {
112                        String[] param = item.split("=");
113                        if (param.length == 2) {
114                            parameters.put(URLDecoder.decode(param[0], "UTF-8"),
115                                    URLDecoder.decode(param[1], "UTF-8"));
116                        } else if (param.length == 1) {
117                            parameters.put(URLDecoder.decode(param[0], "UTF-8"), null);
118                        }
119                    }
120                }
121            } catch (UnsupportedEncodingException e) {
122                log.error("Failed to get request parameters from uri", e);
123            }
124        }
125        return parameters;
126    }
127
128    public static String addParametersToURIQuery(String uriString, Map<String, String> parameters) {
129        if (uriString == null || parameters == null || parameters.isEmpty()) {
130            return uriString;
131        }
132        String uriPath = getURIPath(uriString);
133        Map<String, String> existingParams = getRequestParameters(uriString.substring(uriPath.length()));
134        if (existingParams == null) {
135            existingParams = new LinkedHashMap<>();
136        }
137        existingParams.putAll(parameters);
138        String res;
139        if (!existingParams.isEmpty()) {
140            String newQuery = getURIQuery(existingParams);
141            res = uriPath + '?' + newQuery;
142        } else {
143            res = uriPath;
144        }
145        return res;
146    }
147
148    public static String quoteURIPathComponent(String s, boolean quoteSlash) {
149        return quoteURIPathComponent(s, quoteSlash, true);
150    }
151
152    /**
153     * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path
154     *
155     * @since 5.6
156     * @param s the uri path to quote
157     * @param quoteSlash true if "/" character should be quoted and replaced by %2F
158     * @param quoteAt true if "@" character should be quoted and replaced by %40
159     */
160    public static String quoteURIPathComponent(String s, boolean quoteSlash, boolean quoteAt) {
161        return quoteURIPathComponent(s, quoteSlash ? "%2F" : null, quoteAt ? "%40" : null);
162    }
163
164    /**
165     * Quotes a URI path token. For example, a blob filename. It replaces "/" by "-".
166     *
167     * @since 7.3
168     * @param s the uri path token to quote
169     */
170    public static String quoteURIPathToken(String s) {
171        return quoteURIPathComponent(s, "-", "%40");
172    }
173
174    /**
175     * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path
176     *
177     * @since 5.6
178     * @param s the uri path to quote
179     * @param slashSequence if null, do not quote "/", otherwise, replace "/" by the given sequence
180     * @param atSequence if null, do not quote "@", otherwise, replace "@" by the given sequence
181     */
182    protected static String quoteURIPathComponent(String s, String slashSequence, String atSequence) {
183        if ("".equals(s)) {
184            return s;
185        }
186        URI uri;
187        try {
188            // fake scheme so that a colon is not mistaken as a scheme
189            uri = new URI("x", s, null);
190        } catch (URISyntaxException e) {
191            throw new IllegalArgumentException("Illegal characters in: " + s, e);
192        }
193        String r = uri.toASCIIString().substring(2);
194        // replace reserved characters ;:$&+=?/[]@
195        // FIXME: find a better way to do this...
196        r = r.replace(";", "%3B");
197        r = r.replace(":", "%3A");
198        r = r.replace("$", "%24");
199        r = r.replace("&", "%26");
200        r = r.replace("+", "%2B");
201        r = r.replace("=", "%3D");
202        r = r.replace("?", "%3F");
203        r = r.replace("[", "%5B");
204        r = r.replace("]", "%5D");
205        if (atSequence != null) {
206            r = r.replace("@", atSequence);
207        }
208        if (slashSequence != null) {
209            r = r.replace("/", slashSequence);
210        }
211        return r;
212    }
213
214    public static String unquoteURIPathComponent(String s) {
215        if ("".equals(s)) {
216            return s;
217        }
218        URI uri;
219        try {
220            uri = new URI("http://x/" + s);
221        } catch (URISyntaxException e) {
222            throw new IllegalArgumentException("Illegal characters in: " + s, e);
223        }
224        String path = uri.getPath();
225        if (path.startsWith("/") && !s.startsWith("/")) {
226            path = path.substring(1);
227        }
228        return path;
229    }
230
231}