001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.web.servlet;
007    
008    import java.util.*;
009    import java.io.*;
010    import java.security.*;
011    import javax.servlet.*;
012    import javax.servlet.http.*;
013    
014    import fc.io.*;
015    
016    /**
017    Misc utility methods for sessions and cookies.
018    
019    @author hursh jain
020    **/
021    public class SessionUtil 
022    {
023    static final String TestCookieName = "eikooc";
024    static final Random random = new java.util.Random();
025    static MessageDigest digester = null;
026    
027    static {
028      try {
029        digester = MessageDigest.getInstance("SHA-1");
030        }
031      catch (Exception e) {
032        e.printStackTrace();
033        }
034      }
035      
036    /**
037    Sets a test cookies. The user should be redirected to this or some other
038    page after calling this method and the presence of this cookie can be
039    checked via {@link hasTestCookie} from that page. The lifetime of the
040    test cookie is set to the browsers lifetime (will be lost when the
041    browser exits).
042    */
043    public static void setTestCookie(
044      HttpServletRequest req, HttpServletResponse res)
045      {
046      Cookie c = new Cookie(TestCookieName, "scotchAndsoda");
047      c.setMaxAge(-1);
048      res.addCookie(c);
049      }
050    
051    /**
052    Returns true if the browser accepts cookies. The {@link setTestCookie()}
053    method must have been invoked by some page prior to calling this method
054    AND the user must have been either redirected to that or some other page
055    AND this method must be invoked from that redirected page.
056    <p>
057    <b>Note:</b>
058    Test cookies are entirely self contained. They are meant to figure out if
059    a browser accepts cookies. Their presence or absence does not mean that
060    <b>other</b> cookies will not be sent by the browser. These other cookies
061    may point to a expired session in memory or database and a seperate check
062    for that should be made.
063    */
064    public static boolean hasTestCookie(
065      HttpServletRequest req, HttpServletResponse res)
066      {
067      Cookie[] cookies = req.getCookies();
068      if (cookies == null)
069        return false;
070    
071      for (int n = 0; n < cookies.length; n++) {
072        if (cookies[n].getName().equals(TestCookieName))
073          return true;
074        }
075    
076      return false;
077      }
078    
079    /**
080    Removes the test cookies if previously set. Normally, the
081    test cookie set by the {@link #setTestCookie} method is
082    automatically removed when the browser exists, but this
083    method allows removing it before such time.
084    */
085    public static void deleteTestCookie(
086      HttpServletRequest req, HttpServletResponse res)
087      {
088      Cookie[] cookies = req.getCookies();
089      if (cookies == null)
090        return;
091    
092      for (int n = 0; n < cookies.length; n++) 
093        {
094        if (cookies[n].getName().equals(TestCookieName))
095          {
096          cookies[n].setMaxAge(0);
097          res.addCookie(cookies[n]);
098          break;
099          }
100        }
101      }
102    
103    /**
104    Returns a new session identifier.
105    <pre>
106    See: cookies.lcs.mit.edu
107    See: www.across.si
108    
109    There are 2 issues with generating sessionid's. 
110    
111    1) uniqueness - 2 or more sessionid's should not end up being 
112       the same.
113    2) hard-to-guess - For example, sequential values like 
114       1, 2, 3 are unique but easy to guess and therefore easy
115       to session hijack.
116    
117    Our sessionid's have 2 parts:
118    a) a timestamp for guaranteed uniqueness  (easy to guess)
119    b) random data (hard to guess)
120    </pre>
121    */
122    public static String newSessionID() 
123      {
124      //although this function has been written for some time,
125      //i recently looked this over with webscarab (around aug 2007)
126      //and it visually appears fairly scattered/random. (9999 sid's)
127      //dunno, maybe hotbits retrieved/cached every minute via a background
128      //thread is good if one wants to be hardcore but it's pretty good
129      //as-is 
130      
131      //there is also no visual difference between nanos & millis. intuitively
132      //nanos should have more differences in the lower bits but after
133      //all the bits get churned/digested, there is no diff that i can tell,
134      //at least visually via webscarab. so i've left this as millis.
135      
136      long time = System.currentTimeMillis();   
137      
138      //String remoteip = req.getRemoteAddr();
139      int rand = random.nextInt(); //presuming thread safe ?
140      String cookie = String.valueOf(time) + rand;
141    
142      byte[] digestbuf = null;
143      
144      //digests cookiestring to 16 bytes          
145      synchronized (SessionUtil.class)
146        {   
147        digestbuf = digester.digest(cookie.getBytes());
148          }
149          
150      /*
151      we simply convert bytes toHex. note, we don't use Base64
152      because '/' and '+' are valid Base64 chars and they can
153      mess up url's. 
154      */
155        
156        char[] encoded = null;
157        encoded = encode(digestbuf);
158        return new String(encoded);
159        
160        //note, we don't ever need to use the decode() method
161        //but it's there for testing foo->encode->decode->foo
162        }
163    
164    //well, i was very...young...when I wrote this... :-]
165    //
166    //                1..............16 [or 0-15, 16 things]  
167    //                0123456789ABCDEF
168    static final char[] lookup="cUntSNIFfER21690".toCharArray();
169    
170    /*
171    base16 encoding
172         0 -> some ascii code
173           1 -> some ascii code
174           ...
175           15-> some ascii code
176           
177    when ascii codes are for 0...F it's called "hex" encoding. 
178    In this case, the set of ascii codes chosen together happen 
179    to spell out a word and this sequence is henceforth known as 
180    "hursh" encoding.
181    */
182    
183    //we need a reverse lookup table since are ain't
184    //using straight hexadecimal to encode. 
185    static final int[] reverselookup = new int[128];
186      
187    static  
188      {
189      for (int n = 0; n < 16; n++) {
190        int x =  lookup[n] & 0xFF; //x ='c'('c'&0xff->99), 'n'(110) etc
191        reverselookup[x] = n;  //this is why reverselookup[128] above, not 16
192        }
193      }
194      
195    private static final char[] encode(byte[] buf) 
196      {
197      final int len = buf.length;
198    
199      char[] tmp = new char[len * 2];  
200      
201      for (int n = 0, i = 0; n < len ; n++, i+=2)
202        {
203        int high4 =  (buf[n] >> 4) & 0x0F;
204        int low4  =  buf[n] & 0x0F;
205        
206        tmp[i]   = lookup [high4];
207        tmp[i+1] = lookup [low4];
208        }
209      return tmp;
210      }
211    
212    private static final byte[] decode(char[] buf) 
213      {
214      final int len = buf.length;
215    
216      byte[] tmp = new byte[(len/2) + (len%2)];
217    
218      for (int n = 0, tmp_n = 0; n < len; n+=2, tmp_n++) 
219        {
220        //0xFF makes it an int. we could cast to (int)
221        //too but then that would sign extend the char
222        //in the general case [but since all are chars
223        //are < 127], it would be ok to do so in this
224        //case. Used 0xFF because that's the right thing
225        //generally speaking
226    
227        int c1 = buf[n] & 0xFF;   //'c', 'u', 'n', 't' etc..
228        int c2 = buf[n+1] & 0xFF;
229    
230        if ( c1 > 127 || c2 > 127)
231          throw new IllegalArgumentException("malformed bufing, encoded with characters I don't understand [" + c1 + "] and [" + c2 + "]");
232            
233        int i = (reverselookup[c1] << 4  | reverselookup [c2]); 
234        //System.out.println( (reverselookup[c1] << 4) + ", " + reverselookup[c2] + ",->" + i);
235    
236        tmp[tmp_n] = (byte) i;
237        }
238      return tmp;
239      }
240    
241    }