001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau;
014
015import static org.apache.juneau.UriRelativity.*;
016import static org.apache.juneau.UriResolution.*;
017import static org.apache.juneau.internal.StringUtils.*;
018
019import java.io.*;
020import java.net.*;
021
022/**
023 * Class used to create absolute and root-relative URIs based on your current URI 'location' and rules about how to
024 * make such resolutions.
025 *
026 * <p>
027 * Combines a {@link UriContext} instance with rules for resolution ({@link UriResolution} and relativity
028 * ({@link UriRelativity}) to define simple {@link #resolve(Object)} and {@link #append(Appendable, Object)} methods.
029 *
030 * <p>
031 * Three special protocols are used to represent context-root-relative, servlet-relative, and request-path-relative
032 * URIs:
033 *    <js>"context:/"</js>, <js>"servlet:/"</js>, and <js>"request:/"</js>.
034 *
035 * <p>
036 * The following list shows the protocols of URLs that can be resolved with this class:
037 * <ul>
038 *    <li><js>"foo://foo"</js> - Absolute URI.
039 *    <li><js>"/foo"</js> - Root-relative URI.
040 *    <li><js>"/"</js> - Root URI.
041 *    <li><js>"context:/foo"</js> - Context-root-relative URI.
042 *    <li><js>"context:/"</js> - Context-root URI.
043 *    <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
044 *    <li><js>"servlet:/"</js> - Servlet-path URI.
045 *    <li><js>"request:/foo"</js> - Request-path-relative URI.
046 *    <li><js>"request:/"</js> - Request-path URI.
047 *    <li><js>"foo"</js> - Path-info-relative URI.
048 *    <li><js>""</js> - Path-info URI.
049 * </ul>
050*/
051public class UriResolver {
052
053   private final UriResolution resolution;
054   private final UriRelativity relativity;
055   private final String authority, contextRoot, servletPath, pathInfo, parentPath;
056
057   /**
058    * Constructor.
059    *
060    * @param resolution Rule on how URIs should be resolved.
061    * @param relativity Rule on what relative URIs are relative to.
062    * @param uriContext Current URI context (i.e. the current URI 'location').
063    */
064   public UriResolver(UriResolution resolution, UriRelativity relativity, UriContext uriContext) {
065      this.resolution = resolution;
066      this.relativity = relativity;
067      this.authority = uriContext.authority;
068      this.contextRoot = uriContext.contextRoot;
069      this.servletPath = uriContext.servletPath;
070      this.pathInfo = uriContext.pathInfo;
071      this.parentPath = uriContext.parentPath;
072   }
073
074   /**
075    * Converts the specified URI to absolute form based on values in this context.
076    *
077    * @param uri
078    *    The URI to convert to absolute form.
079    *    Can be any of the following:
080    *    <ul>
081    *       <li>{@link java.net.URI}
082    *       <li>{@link java.net.URL}
083    *       <li>{@link CharSequence}
084    *    </ul>
085    *    URI can be any of the following forms:
086    *    <ul>
087    *       <li><js>"foo://foo"</js> - Absolute URI.
088    *       <li><js>"/foo"</js> - Root-relative URI.
089    *       <li><js>"/"</js> - Root URI.
090    *       <li><js>"context:/foo"</js> - Context-root-relative URI.
091    *       <li><js>"context:/"</js> - Context-root URI.
092    *       <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
093    *       <li><js>"servlet:/"</js> - Servlet-path URI.
094    *       <li><js>"request:/foo"</js> - Request-path-relative URI.
095    *       <li><js>"request:/"</js> - Request-path URI.
096    *       <li><js>"foo"</js> - Path-info-relative URI.
097    *       <li><js>""</js> - Path-info URI.
098    *    </ul>
099    * @return The converted URI.
100    */
101   public String resolve(Object uri) {
102      return resolve(uri, resolution);
103   }
104
105   private String resolve(Object uri, UriResolution res) {
106      String s = stringify(uri);
107      if (isAbsoluteUri(s))
108         return hasDotSegments(s) && res != NONE ? normalize(s) : s;
109      if (res == ROOT_RELATIVE && startsWith(s, '/'))
110         return hasDotSegments(s) ? normalize(s) : s;
111      if (res == NONE && ! isSpecialUri(s))
112         return s;
113      return append(new StringBuilder(), s).toString();
114   }
115
116   /**
117    * Relativizes a URI.
118    *
119    * <p>
120    * Similar to {@link URI#relativize(URI)}, except supports special protocols (e.g. <js>"servlet:/"</js>) for both
121    * the <c>relativeTo</c> and <c>uri</c> parameters.
122    *
123    * <p>
124    * For example, to relativize a URI to its servlet-relative form:
125    * <p class='bcode w800'>
126    *    <jc>// relativeUri == "path/foo"</jc>
127    *    String relativeUri = resolver.relativize(<js>"servlet:/"</js>, <js>"/context/servlet/path/foo"</js>);
128    * </p>
129    *
130    * @param relativeTo The URI to relativize against.
131    * @param uri The URI to relativize.
132    * @return The relativized URI.
133    */
134   public String relativize(Object relativeTo, Object uri) {
135      String r = resolve(relativeTo, ABSOLUTE);
136      String s = resolve(uri, ABSOLUTE);
137      return URI.create(r).relativize(URI.create(s)).toString();
138   }
139
140   /**
141    * Same as {@link #resolve(Object)} except appends result to the specified appendable.
142    *
143    * @param a The appendable to append the URL to.
144    * @param o The URI to convert to absolute form.
145    * @return The same appendable passed in.
146    */
147   public Appendable append(Appendable a, Object o) {
148
149      try {
150         String uri = stringify(o);
151         uri = nullIfEmpty(uri);
152         boolean needsNormalize = hasDotSegments(uri) && resolution != null;
153
154         // Absolute paths are not changed.
155         if (isAbsoluteUri(uri))
156            return a.append(needsNormalize ? normalize(uri) : uri);
157         if (resolution == NONE && ! isSpecialUri(uri))
158            return a.append(emptyIfNull(uri));
159         if (resolution == ROOT_RELATIVE && startsWith(uri, '/'))
160            return a.append(needsNormalize ? normalize(uri) : uri);
161
162         Appendable a2 = needsNormalize ? new StringBuilder() : a;
163
164         // Root-relative path
165         if (startsWith(uri, '/')) {
166            if (authority != null)
167               a2.append(authority);
168            if (uri.length() != 1)
169               a2.append(uri);
170            else if (authority == null)
171               a2.append('/');
172         }
173
174         // Context-relative path
175         else if (uri != null && uri.startsWith("context:/")) {
176            if (resolution == ABSOLUTE && authority != null)
177               a2.append(authority);
178            if (contextRoot != null)
179               a2.append('/').append(contextRoot);
180            if (uri.length() > 9)
181               a2.append('/').append(uri.substring(9));
182            else if (contextRoot == null && (authority == null || resolution != ABSOLUTE))
183               a2.append('/');
184         }
185
186         // Resource-relative path
187         else if (uri != null && uri.startsWith("servlet:/")) {
188            if (resolution == ABSOLUTE && authority != null)
189               a2.append(authority);
190            if (contextRoot != null)
191               a2.append('/').append(contextRoot);
192            if (servletPath != null)
193               a2.append('/').append(servletPath);
194            if (uri.length() > 9)
195               a2.append('/').append(uri.substring(9));
196            else if (servletPath == null && contextRoot == null && (authority == null || resolution != ABSOLUTE))
197               a2.append('/');
198         }
199
200         // Request-relative path
201         else if (uri != null && uri.startsWith("request:/")) {
202            if (resolution == ABSOLUTE && authority != null)
203               a2.append(authority);
204            if (contextRoot != null)
205               a2.append('/').append(contextRoot);
206            if (servletPath != null)
207               a2.append('/').append(servletPath);
208            if (pathInfo != null)
209               a2.append('/').append(pathInfo);
210            if (uri.length() > 9)
211               a2.append('/').append(uri.substring(9));
212            else if (servletPath == null && contextRoot == null && pathInfo == null && (authority == null || resolution != ABSOLUTE))
213               a2.append('/');
214         }
215
216         // Relative path
217         else {
218            if (resolution == ABSOLUTE && authority != null)
219               a2.append(authority);
220            if (contextRoot != null)
221               a2.append('/').append(contextRoot);
222            if (servletPath != null)
223               a2.append('/').append(servletPath);
224            if (relativity == RESOURCE && uri != null)
225               a2.append('/').append(uri);
226            else if (relativity == PATH_INFO) {
227               if (uri == null) {
228                  if (pathInfo != null)
229                     a2.append('/').append(pathInfo);
230               } else {
231                  if (parentPath != null)
232                     a2.append('/').append(parentPath);
233                  a2.append('/').append(uri);
234               }
235            }
236            else if (uri == null && contextRoot == null && servletPath == null && (authority == null || resolution != ABSOLUTE))
237               a2.append('/');
238         }
239
240         if (needsNormalize)
241            a.append(normalize(a2.toString()));
242
243         return a;
244      } catch (IOException e) {
245         throw new RuntimeException(e);
246      }
247   }
248
249   private static boolean isSpecialUri(String s) {
250      if (s == null || s.length() == 0)
251         return false;
252      char c = s.charAt(0);
253      if (c != 's' && c != 'c' && c != 'r')
254         return false;
255      return s.startsWith("servlet:/") || s.startsWith("context:/") || s.startsWith("request:/");
256   }
257
258   private static String normalize(String s) {
259      s = URI.create(s).normalize().toString();
260      if (s.length() > 1 && s.charAt(s.length()-1) == '/')
261         s = s.substring(0, s.length()-1);
262      return s;
263   }
264
265   private static boolean hasDotSegments(String s) {
266      if (s == null)
267         return false;
268      for (int i = 0; i < s.length()-1; i++) {
269         char c = s.charAt(i);
270         if (i == 0 && c == '/')
271            return true;
272         if (c == '/' && s.charAt(i+1) == '.')
273            return true;
274      }
275      return false;
276   }
277}