Coverage Report - org.apache.shiro.web.servlet.ShiroHttpServletResponse
 
Classes in this File Line Coverage Branch Coverage Complexity
ShiroHttpServletResponse
0%
0/119
0%
0/78
4.8
 
 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.shiro.web.servlet;
 20  
 
 21  
 import javax.servlet.ServletContext;
 22  
 import javax.servlet.http.HttpServletRequest;
 23  
 import javax.servlet.http.HttpServletResponse;
 24  
 import javax.servlet.http.HttpServletResponseWrapper;
 25  
 import javax.servlet.http.HttpSession;
 26  
 import java.io.IOException;
 27  
 import java.net.MalformedURLException;
 28  
 import java.net.URL;
 29  
 import java.net.URLEncoder;
 30  
 
 31  
 /**
 32  
  * HttpServletResponse implementation to support URL Encoding of Shiro Session IDs.
 33  
  * <p/>
 34  
  * It is only used when using Shiro's native Session Management configuration (and not when using the Servlet
 35  
  * Container session configuration, which is Shiro's default in a web environment).  Because the servlet container
 36  
  * already performs url encoding of its own session ids, instances of this class are only needed when using Shiro
 37  
  * native sessions.
 38  
  * <p/>
 39  
  * Note that this implementation relies in part on source code from the Tomcat 6.x distribution for
 40  
  * encoding URLs for session ID URL Rewriting (we didn't want to re-invent the wheel).  Since Shiro is also
 41  
  * Apache 2.0 license, all regular licenses and conditions have remained in tact.
 42  
  *
 43  
  * @since 0.2
 44  
  */
 45  
 public class ShiroHttpServletResponse extends HttpServletResponseWrapper {
 46  
 
 47  
     //TODO - complete JavaDoc
 48  
 
 49  
     private static final String DEFAULT_SESSION_ID_PARAMETER_NAME = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
 50  
 
 51  0
     private ServletContext context = null;
 52  
     //the associated request
 53  0
     private ShiroHttpServletRequest request = null;
 54  
 
 55  
     public ShiroHttpServletResponse(HttpServletResponse wrapped, ServletContext context, ShiroHttpServletRequest request) {
 56  0
         super(wrapped);
 57  0
         this.context = context;
 58  0
         this.request = request;
 59  0
     }
 60  
 
 61  
     @SuppressWarnings({"UnusedDeclaration"})
 62  
     public ServletContext getContext() {
 63  0
         return context;
 64  
     }
 65  
 
 66  
     @SuppressWarnings({"UnusedDeclaration"})
 67  
     public void setContext(ServletContext context) {
 68  0
         this.context = context;
 69  0
     }
 70  
 
 71  
     public ShiroHttpServletRequest getRequest() {
 72  0
         return request;
 73  
     }
 74  
 
 75  
     @SuppressWarnings({"UnusedDeclaration"})
 76  
     public void setRequest(ShiroHttpServletRequest request) {
 77  0
         this.request = request;
 78  0
     }
 79  
 
 80  
     /**
 81  
      * Encode the session identifier associated with this response
 82  
      * into the specified redirect URL, if necessary.
 83  
      *
 84  
      * @param url URL to be encoded
 85  
      */
 86  
     public String encodeRedirectURL(String url) {
 87  0
         if (isEncodeable(toAbsolute(url))) {
 88  0
             return toEncoded(url, request.getSession().getId());
 89  
         } else {
 90  0
             return url;
 91  
         }
 92  
     }
 93  
 
 94  
 
 95  
     public String encodeRedirectUrl(String s) {
 96  0
         return encodeRedirectURL(s);
 97  
     }
 98  
 
 99  
 
 100  
     /**
 101  
      * Encode the session identifier associated with this response
 102  
      * into the specified URL, if necessary.
 103  
      *
 104  
      * @param url URL to be encoded
 105  
      */
 106  
     public String encodeURL(String url) {
 107  0
         String absolute = toAbsolute(url);
 108  0
         if (isEncodeable(absolute)) {
 109  
             // W3c spec clearly said
 110  0
             if (url.equalsIgnoreCase("")) {
 111  0
                 url = absolute;
 112  
             }
 113  0
             return toEncoded(url, request.getSession().getId());
 114  
         } else {
 115  0
             return url;
 116  
         }
 117  
     }
 118  
 
 119  
     public String encodeUrl(String s) {
 120  0
         return encodeURL(s);
 121  
     }
 122  
 
 123  
     /**
 124  
      * Return <code>true</code> if the specified URL should be encoded with
 125  
      * a session identifier.  This will be true if all of the following
 126  
      * conditions are met:
 127  
      * <ul>
 128  
      * <li>The request we are responding to asked for a valid session
 129  
      * <li>The requested session ID was not received via a cookie
 130  
      * <li>The specified URL points back to somewhere within the web
 131  
      * application that is responding to this request
 132  
      * </ul>
 133  
      *
 134  
      * @param location Absolute URL to be validated
 135  
      * @return {@code true} if the specified URL should be encoded with a session identifier, {@code false} otherwise.
 136  
      */
 137  
     protected boolean isEncodeable(final String location) {
 138  
 
 139  0
         if (location == null)
 140  0
             return (false);
 141  
 
 142  
         // Is this an intra-document reference?
 143  0
         if (location.startsWith("#"))
 144  0
             return (false);
 145  
 
 146  
         // Are we in a valid session that is not using cookies?
 147  0
         final HttpServletRequest hreq = request;
 148  0
         final HttpSession session = hreq.getSession(false);
 149  0
         if (session == null)
 150  0
             return (false);
 151  0
         if (hreq.isRequestedSessionIdFromCookie())
 152  0
             return (false);
 153  
 
 154  0
         return doIsEncodeable(hreq, session, location);
 155  
     }
 156  
 
 157  
     private boolean doIsEncodeable(HttpServletRequest hreq, HttpSession session, String location) {
 158  
         // Is this a valid absolute URL?
 159  
         URL url;
 160  
         try {
 161  0
             url = new URL(location);
 162  0
         } catch (MalformedURLException e) {
 163  0
             return (false);
 164  0
         }
 165  
 
 166  
         // Does this URL match down to (and including) the context path?
 167  0
         if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
 168  0
             return (false);
 169  0
         if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
 170  0
             return (false);
 171  0
         int serverPort = hreq.getServerPort();
 172  0
         if (serverPort == -1) {
 173  0
             if ("https".equals(hreq.getScheme()))
 174  0
                 serverPort = 443;
 175  
             else
 176  0
                 serverPort = 80;
 177  
         }
 178  0
         int urlPort = url.getPort();
 179  0
         if (urlPort == -1) {
 180  0
             if ("https".equals(url.getProtocol()))
 181  0
                 urlPort = 443;
 182  
             else
 183  0
                 urlPort = 80;
 184  
         }
 185  0
         if (serverPort != urlPort)
 186  0
             return (false);
 187  
 
 188  0
         String contextPath = getRequest().getContextPath();
 189  0
         if (contextPath != null) {
 190  0
             String file = url.getFile();
 191  0
             if ((file == null) || !file.startsWith(contextPath))
 192  0
                 return (false);
 193  0
             String tok = ";" + DEFAULT_SESSION_ID_PARAMETER_NAME + "=" + session.getId();
 194  0
             if (file.indexOf(tok, contextPath.length()) >= 0)
 195  0
                 return (false);
 196  
         }
 197  
 
 198  
         // This URL belongs to our web application, so it is encodeable
 199  0
         return (true);
 200  
 
 201  
     }
 202  
 
 203  
 
 204  
     /**
 205  
      * Convert (if necessary) and return the absolute URL that represents the
 206  
      * resource referenced by this possibly relative URL.  If this URL is
 207  
      * already absolute, return it unchanged.
 208  
      *
 209  
      * @param location URL to be (possibly) converted and then returned
 210  
      * @return resource location as an absolute url
 211  
      * @throws IllegalArgumentException if a MalformedURLException is
 212  
      *                                  thrown when converting the relative URL to an absolute one
 213  
      */
 214  
     private String toAbsolute(String location) {
 215  
 
 216  0
         if (location == null)
 217  0
             return (location);
 218  
 
 219  0
         boolean leadingSlash = location.startsWith("/");
 220  
 
 221  0
         if (leadingSlash || !hasScheme(location)) {
 222  
 
 223  0
             StringBuilder buf = new StringBuilder();
 224  
 
 225  0
             String scheme = request.getScheme();
 226  0
             String name = request.getServerName();
 227  0
             int port = request.getServerPort();
 228  
 
 229  
             try {
 230  0
                 buf.append(scheme).append("://").append(name);
 231  0
                 if ((scheme.equals("http") && port != 80)
 232  
                         || (scheme.equals("https") && port != 443)) {
 233  0
                     buf.append(':').append(port);
 234  
                 }
 235  0
                 if (!leadingSlash) {
 236  0
                     String relativePath = request.getRequestURI();
 237  0
                     int pos = relativePath.lastIndexOf('/');
 238  0
                     relativePath = relativePath.substring(0, pos);
 239  
 
 240  0
                     String encodedURI = URLEncoder.encode(relativePath, getCharacterEncoding());
 241  0
                     buf.append(encodedURI).append('/');
 242  
                 }
 243  0
                 buf.append(location);
 244  0
             } catch (IOException e) {
 245  0
                 IllegalArgumentException iae = new IllegalArgumentException(location);
 246  0
                 iae.initCause(e);
 247  0
                 throw iae;
 248  0
             }
 249  
 
 250  0
             return buf.toString();
 251  
 
 252  
         } else {
 253  0
             return location;
 254  
         }
 255  
     }
 256  
 
 257  
     /**
 258  
      * Determine if the character is allowed in the scheme of a URI.
 259  
      * See RFC 2396, Section 3.1
 260  
      *
 261  
      * @param c the character to check
 262  
      * @return {@code true} if the character is allowed in a URI scheme, {@code false} otherwise.
 263  
      */
 264  
     public static boolean isSchemeChar(char c) {
 265  0
         return Character.isLetterOrDigit(c) ||
 266  
                 c == '+' || c == '-' || c == '.';
 267  
     }
 268  
 
 269  
 
 270  
     /**
 271  
      * Returns {@code true} if the URI string has a {@code scheme} component, {@code false} otherwise.
 272  
      *
 273  
      * @param uri the URI string to check for a scheme component
 274  
      * @return {@code true} if the URI string has a {@code scheme} component, {@code false} otherwise.
 275  
      */
 276  
     private boolean hasScheme(String uri) {
 277  0
         int len = uri.length();
 278  0
         for (int i = 0; i < len; i++) {
 279  0
             char c = uri.charAt(i);
 280  0
             if (c == ':') {
 281  0
                 return i > 0;
 282  0
             } else if (!isSchemeChar(c)) {
 283  0
                 return false;
 284  
             }
 285  
         }
 286  0
         return false;
 287  
     }
 288  
 
 289  
     /**
 290  
      * Return the specified URL with the specified session identifier suitably encoded.
 291  
      *
 292  
      * @param url       URL to be encoded with the session id
 293  
      * @param sessionId Session id to be included in the encoded URL
 294  
      * @return the url with the session identifer properly encoded.
 295  
      */
 296  
     protected String toEncoded(String url, String sessionId) {
 297  
 
 298  0
         if ((url == null) || (sessionId == null))
 299  0
             return (url);
 300  
 
 301  0
         String path = url;
 302  0
         String query = "";
 303  0
         String anchor = "";
 304  0
         int question = url.indexOf('?');
 305  0
         if (question >= 0) {
 306  0
             path = url.substring(0, question);
 307  0
             query = url.substring(question);
 308  
         }
 309  0
         int pound = path.indexOf('#');
 310  0
         if (pound >= 0) {
 311  0
             anchor = path.substring(pound);
 312  0
             path = path.substring(0, pound);
 313  
         }
 314  0
         StringBuilder sb = new StringBuilder(path);
 315  0
         if (sb.length() > 0) { // session id param can't be first.
 316  0
             sb.append(";");
 317  0
             sb.append(DEFAULT_SESSION_ID_PARAMETER_NAME);
 318  0
             sb.append("=");
 319  0
             sb.append(sessionId);
 320  
         }
 321  0
         sb.append(anchor);
 322  0
         sb.append(query);
 323  0
         return (sb.toString());
 324  
 
 325  
     }
 326  
 }