All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.base.net.HttpCookie Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Security, http://www.openmdx.org/
 * Description: Basic Cookie
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 *
 * Copyright (c) 2010-2013, OMEX AG, Switzerland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * ------------------
 *
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.base.net;

import java.net.URI;
import java.text.ParseException;

import org.w3c.format.DateTimeFormat;

/**
 * Basic Cookie
 */
public class HttpCookie implements Comparable {

   /**
    * Constructor
    *
    * @param uri the URI provides the target default values
    * @param responseCookie
    */
   public HttpCookie(URI uri, String responseCookie) {
      String name = null;
      String value = null;
      String version = null;
      String domain = null;
      String port = null;
      String path = null;
      this.secure = false;
      this.discard = false;
      this.expiresAt = Long.MAX_VALUE;
      for (String nameValuePair : responseCookie.split(";")) {
         int e = nameValuePair.indexOf('=');
         String key;
         String argument;
         if (e < 0) {
            key = trim(nameValuePair);
            argument = null;
         } else {
            key = trim(nameValuePair.substring(0, e));
            argument = trim(nameValuePair.substring(e + 1));
         }
         if (name == null) {
            name = key;
            value = argument;
         } else {
            key = key.toLowerCase();
            if ("domain".equals(key)) {
               domain = argument;
            } else if ("path".equals(key)) {
               path = argument;
            } else if ("port".equals(key)) {
               port = argument;
            } else if ("secure".equals(key)) {
               this.secure = true;
            } else if ("version".equals(key)) {
               version = argument;
            } else if ("discard".equals(key)) {
               this.discard = true;
            } else if ("expires".equals(key)) {
               try {
                  this.expiresAt = netscapeDateFormat.parse(argument).getTime();
               } catch (ParseException exception) {
                  throw new IllegalArgumentException("Invalid 'expires' value: " + argument, exception);
               }
            } else if ("max-age".equals(key)) {
               try {
                  this.expiresAt = System.currentTimeMillis() + 1000 * Long.parseLong(argument);
               } catch (NumberFormatException exception) {
                  throw new IllegalArgumentException("Invalid 'max-age' value: " + port, exception);
               }
            } else if (!"httponly".equals(key) && !"comment".equals(key) && !"commenturl".equals(key)) {
               throw new IllegalArgumentException("Unsupported cookie parameter: " + key);
            }
         }
      }
      if (version == null) {
         this.requestCookie = responseCookie;
      } else {
         StringBuilder requestCookie = new StringBuilder(name).append('=');
         if ("0".equals(version)) {
            requestCookie.append(value);
         } else {
            requestCookie.append('"').append(value).append('"');
            if (path != null) {
               requestCookie.append(";$Path=\"").append(path).append('"');
            }
            if (domain != null) {
               requestCookie.append(";$Domain=\"").append(domain).append('"');
            }
            if (port != null) {
               requestCookie.append(";$Port=\"").append(port).append('"');
            }
         }
         this.requestCookie = requestCookie.toString();
      }
      this.name = name.toLowerCase();
      this.domain = (domain == null ? uri.getHost() : domain).toLowerCase();
      if (port == null) {
         this.ports = new int[] { getPort(uri) };
      } else {
         String[] ports = port.split(",");
         this.ports = new int[ports.length];
         for (int i = 0; i < ports.length; i++) {
            try {
               this.ports[i] = Integer.parseInt(ports[i].trim());
            } catch (NumberFormatException exception) {
               throw new IllegalArgumentException("Invalid 'port' value: " + port, exception);
            }
         }
      }
      this.path = path == null ? uri.getPath() : path;
   }

   /**
    * Otherwise even the same Java object does not match itself unless it contains a dot
    */
   private static final Boolean EMBEDDED_DOT_DOMAIN_HANDLING = Boolean.FALSE;

   /**
    * The netscape date format
    */
   private static final DateTimeFormat netscapeDateFormat = DateTimeFormat.getInstance("EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'", "GMT", true // lenient
         );

   /**
    * The RFC 2965 compliant form
    */
   private final String requestCookie;

   /**
    *
    */
   private final String name;

   /**
    * The target domain
    */
   private final String domain;

   /**
    * The target ports
    */
   private int[] ports;

   /**
    * The target domain
    */
   private final String path;

   /**
    * The target transport guarantee
    */
   private boolean secure;

   /**
    * The cookie has to be discarded
    */
   private boolean discard;

   /**
    *
    */
   private long expiresAt;

   /**
    * Trim the string and remove quotes
    *
    * @param text
    *
    * @return
    */
   private static String trim(String text) {
      String value = text.trim();
      return value.startsWith("\"") && value.endsWith("\"") ? value.substring(1, value.length() - 1) : value;
   }

   /**
    * Retrieve the port from the URL.
    *
    * @param uri
    *
    * @return the port specified by the URL port or scheme part
    */
   private static int getPort(URI uri) {
      int port = uri.getPort();
      if (port >= 0)
         return port;
      String protocol = uri.getScheme();
      if ("http".equalsIgnoreCase(protocol))
         return 80; // according to RFC 2965
      if ("https".equalsIgnoreCase(protocol))
         return 443;
      return -1;
   }

   /**
    * Tells whether the cookie should be sent to the given URI
    *
    * @param uri
    *
    * @return true if the cookie should be sent to the given URI
    */
   public boolean matches(URI uri) {
      if ((!this.secure || "https".equalsIgnoreCase(uri.getScheme())) &&
      /*java.net.*/HttpCookie.domainMatches(this.domain, uri.getHost())) {
         int uriPort = getPort(uri);
         for (int cookiePort : this.ports) {
            if (uriPort == cookiePort) {
               return true;
            }
         }
      }
      return false;
   }

   /**
    * Tells whether the cookie has to be discarded
    *
    * @return true if the cookie has to be discarded
    */
   public boolean isDiscard() {
      return this.discard;
   }

   /**
    * Tells whether the cookie has expired
    *
    * @return true if the cookie has expired
    */
   public boolean isExpired() {
      return this.expiresAt < System.currentTimeMillis();
   }

   /* (non-Javadoc)
    * @see java.lang.Object#toString()
    */
   @Override
   public String toString() {
      return this.requestCookie;
   }

   /* (non-Javadoc)
    * @see java.lang.Object#equals(java.lang.Object)
    */
   @Override
   public boolean equals(Object object) {
      if (object instanceof HttpCookie) {
         HttpCookie that = (HttpCookie) object;
         return this.name.equalsIgnoreCase(that.name) && this.domain.equalsIgnoreCase(that.domain) && this.path.equals(that.path);
      } else {
         return false;
      }
   }

   /* (non-Javadoc)
    * @see java.lang.Object#hashCode()
    */
   @Override
   public int hashCode() {
      return this.name.hashCode() + this.domain.hashCode() + this.path.hashCode();
   }

   // @Override
   public int compareTo(HttpCookie that) {
      if (equals(that))
         return 0;
      int result = this.name.compareTo(that.name);
      if (result != 0)
         return result;
      result = this.domain.compareTo(that.domain);
      if (result != 0)
         return result;
      if (this.path.startsWith(that.path))
         return -1;
      if (that.path.startsWith(this.path))
         return +1;
      return this.path.compareTo(that.path);
   }

   // ------------------------------------------------------------------------
   // Supports JRE 5.0
   // ------------------------------------------------------------------------

   /**
    * The utility method to check whether a host name is in a domain
    * or not.
    *
    * 

This concept is described in the cookie specification. * To understand the concept, some terminologies need to be defined first: *

* effective host name = hostname if host name contains dot
*                  or = hostname.local if not *
*

Host A's name domain-matches host B's if: *

    *
  • their host name strings string-compare equal; or
  • *
  • A is a HDN string and has the form NB, where N is a non-empty * name string, B has the form .B', and B' is a HDN string. (So, * x.y.com domain-matches .Y.com but not Y.com.)
  • *
* *

A host isn't in a domain (RFC 2965 sec. 3.3.2) if: *

    *
  • The value for the Domain attribute contains no embedded dots, * and the value is not .local.
  • *
  • The effective host name that derives from the request-host does * not domain-match the Domain attribute.
  • *
  • The request-host is a HDN (not IP address) and has the form HD, * where D is the value of the Domain attribute, and H is a string * that contains one or more dots.
  • *
* *

Examples: *

    *
  • A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com * would be rejected, because H is y.x and contains a dot.
  • *
  • A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com * would be accepted.
  • *
  • A Set-Cookie2 with Domain=.com or Domain=.com., will always be * rejected, because there is no embedded dot.
  • *
  • A Set-Cookie2 with Domain=ajax.com will be accepted, and the * value for Domain will be taken to be .ajax.com, because a dot * gets prepended to the value.
  • *
  • A Set-Cookie2 from request-host example for Domain=.local will * be accepted, because the effective host name for the request- * host is example.local, and example.local domain-matches .local.
  • *
* * @param domain the domain name to check host name with * @param host the host name in question * @return true if they domain-matches; false if not */ private static boolean domainMatches( String domain, String host ) { if (domain == null || host == null) return false; // if there's no embedded dot in domain and domain is not .local boolean isLocalDomain = ".local".equalsIgnoreCase(domain); if (EMBEDDED_DOT_DOMAIN_HANDLING.booleanValue()) { int embeddedDotInDomain = domain.indexOf('.'); if (embeddedDotInDomain == 0) embeddedDotInDomain = domain.indexOf('.', 1); if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) return false; } // if the host name contains no dot and the domain name is .local int firstDotInHost = host.indexOf('.'); if (firstDotInHost == -1 && isLocalDomain) return true; int domainLength = domain.length(); int lengthDiff = host.length() - domainLength; if (lengthDiff == 0) { // if the host name and the domain name are just string-compare equal return host.equalsIgnoreCase(domain); } else if (lengthDiff > 0) { // need to check H & D component String H = host.substring(0, lengthDiff); String D = host.substring(lengthDiff); return (H.indexOf('.') == -1 && D.equalsIgnoreCase(domain)); } else if (lengthDiff == -1) { // if domain is actually .host return (domain.charAt(0) == '.' && host.equalsIgnoreCase(domain.substring(1))); } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy