001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.util; 020 021import org.apache.commons.lang3.StringUtils; 022import org.apache.log4j.Logger; 023 024import javax.servlet.http.Cookie; 025import javax.servlet.http.HttpServletRequest; 026import java.text.DateFormat; 027import java.text.ParseException; 028import java.text.SimpleDateFormat; 029import java.util.Date; 030 031 032/** 033 * Contains useful utilities for some common HTTP tasks. 034 * 035 * @since 2.1.61. 036 */ 037public final class HttpUtil { 038 039 static Logger log = Logger.getLogger( HttpUtil.class ); 040 041 /** Private constructor to prevent direct instantiation. */ 042 private HttpUtil() { 043 } 044 045 /** 046 * returns the remote address by looking into {@code x-forwarded-for} header or, if unavailable, 047 * into {@link HttpServletRequest#getRemoteAddr()}. 048 * 049 * @param req http request 050 * @return remote address associated to the request. 051 */ 052 public static String getRemoteAddress( HttpServletRequest req ) { 053 return StringUtils.isNotEmpty ( req.getHeader( "X-Forwarded-For" ) ) ? req.getHeader( "X-Forwarded-For" ) : 054 req.getRemoteAddr(); 055 } 056 057 /** 058 * Attempts to retrieve the given cookie value from the request. 059 * Returns the string value (which may or may not be decoded 060 * correctly, depending on browser!), or null if the cookie is 061 * not found. The algorithm will automatically trim leading 062 * and trailing double quotes, if found. 063 * 064 * @param request The current request 065 * @param cookieName The name of the cookie to fetch. 066 * @return Value of the cookie, or null, if there is no such cookie. 067 */ 068 069 public static String retrieveCookieValue( HttpServletRequest request, String cookieName ) { 070 Cookie[] cookies = request.getCookies(); 071 072 if( cookies != null ) { 073 for( int i = 0; i < cookies.length; i++ ) { 074 if( cookies[i].getName().equals( cookieName ) ) { 075 String value = cookies[i].getValue(); 076 if( value.length() == 0 ) { 077 return null; 078 } 079 if( value.charAt( 0 ) == '"' && value.charAt( value.length() - 1 ) == '"' ) { 080 value = value.substring( 1, value.length() - 1 ); 081 } 082 return value; 083 } 084 } 085 } 086 087 return null; 088 } 089 090 /** 091 * Creates an ETag based on page information. An ETag is unique to each page 092 * and version, so it can be used to check if the page has changed. Do not 093 * assume that the ETag is in any particular format. 094 * 095 * @param pageName The page name for which the ETag should be created. 096 * @param lastModified The page last modified date for which the ETag should be created. 097 * @return A String depiction of an ETag. 098 */ 099 public static String createETag( String pageName, Date lastModified ) { 100 return Long.toString( pageName.hashCode() ^ lastModified.getTime() ); 101 } 102 103 /** 104 * If returns true, then should return a 304 (HTTP_NOT_MODIFIED) 105 * @param req the HTTP request 106 * @param pageName the wiki page name to check for 107 * @param lastModified the last modified date of the wiki page to check for 108 * @return the result of the check 109 */ 110 public static boolean checkFor304( HttpServletRequest req, String pageName, Date lastModified ) { 111 // 112 // We'll do some handling for CONDITIONAL GET (and return a 304) 113 // If the client has set the following headers, do not try for a 304. 114 // 115 // pragma: no-cache 116 // cache-control: no-cache 117 // 118 119 if( "no-cache".equalsIgnoreCase( req.getHeader( "Pragma" ) ) 120 || "no-cache".equalsIgnoreCase( req.getHeader( "cache-control" ) ) ) { 121 // Wants specifically a fresh copy 122 } else { 123 // 124 // HTTP 1.1 ETags go first 125 // 126 String thisTag = createETag( pageName, lastModified ); 127 128 String eTag = req.getHeader( "If-None-Match" ); 129 130 if( eTag != null && eTag.equals(thisTag) ) { 131 return true; 132 } 133 134 // 135 // Next, try if-modified-since 136 // 137 DateFormat rfcDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); 138 139 try { 140 long ifModifiedSince = req.getDateHeader( "If-Modified-Since" ); 141 142 //log.info("ifModifiedSince:"+ifModifiedSince); 143 if( ifModifiedSince != -1 ) { 144 long lastModifiedTime = lastModified.getTime(); 145 146 //log.info("lastModifiedTime:" + lastModifiedTime); 147 if( lastModifiedTime <= ifModifiedSince ) { 148 return true; 149 } 150 } else { 151 try { 152 String s = req.getHeader("If-Modified-Since"); 153 154 if( s != null ) { 155 Date ifModifiedSinceDate = rfcDateFormat.parse(s); 156 //log.info("ifModifiedSinceDate:" + ifModifiedSinceDate); 157 if( lastModified.before(ifModifiedSinceDate) ) { 158 return true; 159 } 160 } 161 } catch (ParseException e) { 162 log.warn(e.getLocalizedMessage(), e); 163 } 164 } 165 } catch( IllegalArgumentException e ) { 166 // Illegal date/time header format. 167 // We fail quietly, and return false. 168 // FIXME: Should really move to ETags. 169 } 170 } 171 172 return false; 173 } 174 175 /** 176 * Attempts to form a valid URI based on the string given. Currently 177 * it can guess email addresses (mailto:). If nothing else is given, 178 * it assumes it to be an http:// url. 179 * 180 * @param uri URI to take a poke at 181 * @return Possibly a valid URI 182 * @since 2.2.8 183 */ 184 public static String guessValidURI( String uri ) { 185 if( uri.indexOf( '@' ) != -1 ) { 186 if( !uri.startsWith( "mailto:" ) ) { 187 // Assume this is an email address 188 uri = "mailto:" + uri; 189 } 190 } else if( notBeginningWithHttpOrHttps( uri ) ) { 191 uri = "http://" + uri; 192 } 193 194 return uri; 195 } 196 197 static boolean notBeginningWithHttpOrHttps( String uri ) { 198 return uri.length() > 0 && !( ( uri.startsWith("http://" ) || uri.startsWith( "https://" ) ) ); 199 } 200 201}