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