View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.util;
20  
21  import java.io.File;
22  import java.io.IOException;
23  
24  import java.io.UnsupportedEncodingException;
25  
26  import java.net.JarURLConnection;
27  import java.net.URL;
28  import java.net.URLConnection;
29  
30  import java.net.URLDecoder;
31  
32  import java.net.URLEncoder;
33  
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  
39  import javax.servlet.http.HttpServletResponse;
40  
41  public final class URLUtils
42  {
43    private URLUtils()
44    {
45    }
46  
47    public static long getLastModified(URL url) throws IOException
48    {
49      if ("file".equals(url.getProtocol()))
50      {
51        String externalForm = url.toExternalForm();
52        // Remove the "file:"
53        File file = new File(externalForm.substring(5));
54  
55        return file.lastModified();
56      }
57      else
58      {
59        return getLastModified(url.openConnection());
60      }
61    }
62  
63    public static long getLastModified(URLConnection connection) throws IOException
64    {
65      long modified;
66      if (connection instanceof JarURLConnection)
67      {
68        // The following hack is required to work-around a JDK bug.
69        // getLastModified() on a JAR entry URL delegates to the actual JAR file
70        // rather than the JAR entry.
71        // This opens internally, and does not close, an input stream to the JAR
72        // file.
73        // In turn, you cannot close it by yourself, because it's internal.
74        // The work-around is to get the modification date of the JAR file
75        // manually,
76        // and then close that connection again.
77  
78        URL jarFileUrl = ((JarURLConnection) connection).getJarFileURL();
79        URLConnection jarFileConnection = jarFileUrl.openConnection();
80  
81        try
82        {
83          modified = jarFileConnection.getLastModified();
84        }
85        finally
86        {
87          try
88          {
89            jarFileConnection.getInputStream().close();
90          }
91          catch (Exception exception)
92          {
93            // Ignored
94          }
95        }
96      }
97      else
98      {
99        modified = connection.getLastModified();
100     }
101 
102     return modified;
103   }
104   
105   /**
106    * Takes a URL that is not escaped for javascript and escapes it   
107    */
108   public static String jsEncodeURL(String url, String charset)
109     throws UnsupportedEncodingException
110   {
111     StringBuilder sb = new StringBuilder(url.length() * 2);
112     for(char c: url.toCharArray())
113     {
114       if ((c >= 'A' && c <= 'Z') ||
115             (c >= 'a' && c <= 'z') ||
116             (c >= '0' && c <= '9') ||
117             _JS_IMMUNE_CHARS.indexOf(c) > -1
118          )
119       {
120         //Valid character.  Just append.
121         sb.append(c);
122       }
123       else
124       {        
125         //This is an invalid character, so we encode need to get the bytes
126         for(byte b: Character.toString(c).getBytes(charset))
127         {
128           sb.append("\\x")
129             .append(String.format("%02X", b));
130         }
131       }
132     }
133     return sb.toString();
134   }
135     
136   /**
137    * Encodes a URL (with or without an existing query string) such that the value in the params map are added to them.
138    * A valid character encoding must be provided to ensure the parameters are encoded properly.
139    * 
140    * @param url the base URL
141    * @param params the map of parameters to add, or <code>null</code>
142    * @param characterResponseEncoding the character response encoding
143    * @return the properly encoded url
144    * 
145    * @throws UnsupportedOperationException if the encoding is not supported.
146    */
147   public static String encodeURL(String url, Map<String, List<String>> params, String characterResponseEncoding)
148   {
149     String fragment = null;
150     String queryString = null;
151     Map<String, List<String>> paramMap = null;
152 
153     //extract any URL fragment
154     int index = url.indexOf(_URL_FRAGMENT_SEPERATOR);
155     if (index != -1)
156     {
157       fragment = url.substring(index+1);
158       url = url.substring(0,index);
159     }
160 
161     //extract the current query string and add the params to the paramMap
162     index = url.indexOf(_URL_QUERY_SEPERATOR);
163     if (index != -1)
164     {
165       queryString = url.substring(index + 1);
166       url = url.substring(0, index);
167       String[] nameValuePairs = queryString.split(_URL_PARAM_SEPERATOR);
168       for (int i = 0; i < nameValuePairs.length; i++)
169       {
170         String[] currentPair = nameValuePairs[i].split(_URL_NAME_VALUE_PAIR_SEPERATOR);
171 
172         ArrayList<String> value = new ArrayList<String>(1);
173         try
174         {
175           value.add(currentPair.length > 1
176                     ? URLDecoder.decode(currentPair[1], characterResponseEncoding)
177                     : "");
178         }
179         catch (UnsupportedEncodingException e)
180         {
181           //shouldn't ever get here
182           throw new UnsupportedOperationException("Encoding type=" + characterResponseEncoding
183                                                           + " not supported", e);
184         }
185         if (paramMap == null)
186         {
187           paramMap = new HashMap<String, List<String>>();
188         }
189         paramMap.put(currentPair[0], value);
190       }
191     }
192 
193     //add/update with new params on the paramMap
194     if (params != null && params.size() > 0)
195     {
196       for (Map.Entry<String, List<String>> pair : params.entrySet())
197       {
198         if (pair.getKey() != null && pair.getKey().trim().length() != 0)
199         {
200           if (paramMap == null)
201           {
202             paramMap = new HashMap<String, List<String>>();
203           }
204           paramMap.put(pair.getKey(), pair.getValue());
205         }
206       }
207     }
208 
209     // start building the new URL
210     StringBuilder newUrl = new StringBuilder(url);
211 
212     //now add the updated param list onto the url
213     if (paramMap != null && paramMap.size()>0)
214     {
215       boolean isFirstPair = true;
216       for (Map.Entry<String, List<String>> pair : paramMap.entrySet())
217       {
218         for (String value : pair.getValue())
219         {
220           if (!isFirstPair)
221           {
222             newUrl.append(_URL_PARAM_SEPERATOR);
223           }
224           else
225           {
226             newUrl.append(_URL_QUERY_SEPERATOR);
227             isFirstPair = false;
228           }
229 
230           newUrl.append(pair.getKey());
231           newUrl.append(_URL_NAME_VALUE_PAIR_SEPERATOR);
232           try
233           {
234             newUrl.append(URLEncoder.encode(value,characterResponseEncoding));
235           }
236           catch (UnsupportedEncodingException e)
237           {
238             //shouldn't ever get here
239             throw new UnsupportedOperationException("Encoding type=" + characterResponseEncoding
240                                                   + " not supported", e);
241           }
242         }
243       }    
244     }
245     
246     //add the fragment back on (if any)
247     if (fragment != null)
248     {
249       newUrl.append(_URL_FRAGMENT_SEPERATOR + fragment);
250     }
251     
252     return newUrl.toString();
253   }
254   
255   private static final String _URL_PARAM_SEPERATOR="&";
256   private static final String _URL_QUERY_SEPERATOR="?";
257   private static final String _URL_FRAGMENT_SEPERATOR="#";
258   private static final String _URL_NAME_VALUE_PAIR_SEPERATOR="=";
259   private static final String _JS_IMMUNE_CHARS=",._";
260 }