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}