You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@locus.apache.org on 2000/05/03 00:39:59 UTC
cvs commit: jakarta-tomcat/src/share/org/apache/tomcat/util SecurityTools.java
costin 00/05/02 15:39:59
Modified: src/etc server.xml
src/share/org/apache/tomcat/core Container.java Context.java
ContextManager.java Request.java RequestImpl.java
src/share/org/apache/tomcat/request AccessInterceptor.java
SecurityCheck.java SessionInterceptor.java
SimpleMapper1.java
Added: src/share/org/apache/tomcat/util SecurityTools.java
Log:
- All functionality in SimpleMapper is now re-implemented in a clean way
SimpleMapper will be removed after the new code is tested.
- The access control is now cleaner and closer to the spec. We do check
the method, and it's easy to extend. I think it's too early to try to
optimize this code, probably it's more important to integrate with apache
and other web servers.
- Start working on SecurityCheck - it will be replaced with independent
modules, better design, etc.
- added the request notes to the interface. The notes are faster attributes
( indexed array instead of hashtable), and we need to keep internal attribs
separate from servlet-visible attributes.
Revision Changes Path
1.15 +4 -1 jakarta-tomcat/src/etc/server.xml
Index: server.xml
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/etc/server.xml,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- server.xml 2000/05/01 23:19:57 1.14
+++ server.xml 2000/05/02 22:39:56 1.15
@@ -56,8 +56,11 @@
-->
<RequestInterceptor className="org.apache.tomcat.request.SimpleMapper1" debug="0" />
+ <!-- Check if the request requires an authenticated role -->
+ <RequestInterceptor className="org.apache.tomcat.request.AccessInterceptor" debug="0" />
+
<!-- Check permissions -->
- <RequestInterceptor className="org.apache.tomcat.request.SecurityCheck" />
+ <RequestInterceptor className="org.apache.tomcat.request.SecurityCheck" debug="0" />
<!-- Set extra headers - like Date: and ServletEngine, plus version 0 cookies
Most of this is not needed, but it was allways here. There are better
1.19 +16 -0 jakarta-tomcat/src/share/org/apache/tomcat/core/Container.java
Index: Container.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Container.java,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -r1.18 -r1.19
--- Container.java 2000/04/27 21:40:53 1.18
+++ Container.java 2000/05/02 22:39:56 1.19
@@ -138,6 +138,8 @@
/** Security constraints associated with this Container
*/
String roles[]=null;
+
+ String methods[]=null;
public Container() {
}
@@ -253,6 +255,20 @@
this.vhosts=vhosts;
}
+ /** If not null, this container can only be accessed by users
+ * in roles.
+ */
+ public String []getMethods() {
+ return methods;
+ }
+
+ /** If not null, this container can only be accessed by users
+ in roles.
+ */
+ public void setMethods( String m[] ) {
+ this.methods=m;
+ }
+
// -------------------- Mapping RHS --------------------
/** The handler for this container
1.84 +1 -0 jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java
Index: Context.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java,v
retrieving revision 1.83
retrieving revision 1.84
diff -u -r1.83 -r1.84
--- Context.java 2000/05/01 23:23:47 1.83
+++ Context.java 2000/05/02 22:39:56 1.84
@@ -536,6 +536,7 @@
ct.setTransport( transport );
ct.setRoles( roles );
ct.setPath( path[i] );
+ ct.setMethods( methods );
// XXX check if exists, merge if true.
constraints.put( path[i], ct );
1.77 +4 -1 jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java
Index: ContextManager.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java,v
retrieving revision 1.76
retrieving revision 1.77
diff -u -r1.76 -r1.77
--- ContextManager.java 2000/05/01 23:25:44 1.76
+++ ContextManager.java 2000/05/02 22:39:56 1.77
@@ -633,7 +633,10 @@
int authorize( Request req, Response res ) {
for( int i=0; i< requestInterceptors.size(); i++ ) {
int err = ((RequestInterceptor)requestInterceptors.elementAt(i)).authorize( req, res );
- if ( err != 0 ) return err;
+ if ( err != 0 ) {
+ if( debug>0) log( "Authorize rezult " + err );
+ return err;
+ }
}
return 0;
}
1.33 +17 -1 jakarta-tomcat/src/share/org/apache/tomcat/core/Request.java
Index: Request.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Request.java,v
retrieving revision 1.32
retrieving revision 1.33
diff -u -r1.32 -r1.33
--- Request.java 2000/04/21 20:45:03 1.32
+++ Request.java 2000/05/02 22:39:57 1.33
@@ -191,6 +191,16 @@
boolean isSecure() ;
+ // XXX It may be usefull to add few more fields - right now
+ // tomcat use notes, after everything is stable we can
+ // use typed methods
+
+ /** This request has an auth constraint and requires one of
+ * the roles.
+ */
+ // public String []getRequiredRoles();
+ // public void setRequiredRoles(String []);
+
// -------------------- Session --------------------
// Will be set by session interceptors
@@ -298,6 +308,12 @@
*/
public void setMappedPath( String m ) ;
-
+ /** Add a per/request internal attribute.
+ * We keep internal attributes in a separate space to prevent
+ * servlets from accessing them. We also use indexed access for
+ * speed ( as oposed to hashtable lookups ). Get an Id from ContextManager.
+ */
+ public void setNote( int pos, Object value );
+ public Object getNote( int pos );
}
1.31 +4 -4 jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java
Index: RequestImpl.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -r1.30 -r1.31
--- RequestImpl.java 2000/04/25 17:54:14 1.30
+++ RequestImpl.java 2000/05/02 22:39:57 1.31
@@ -1,7 +1,7 @@
/*
- * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v 1.30 2000/04/25 17:54:14 costin Exp $
- * $Revision: 1.30 $
- * $Date: 2000/04/25 17:54:14 $
+ * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v 1.31 2000/05/02 22:39:57 costin Exp $
+ * $Revision: 1.31 $
+ * $Date: 2000/05/02 22:39:57 $
*
* ====================================================================
*
@@ -206,7 +206,7 @@
return serverName;
}
// default to localhost - and warn
- System.out.println("No server name, defaulting to localhost");
+ // System.out.println("No server name, defaulting to localhost");
serverName="localhost";
return serverName;
}
1.2 +89 -40 jakarta-tomcat/src/share/org/apache/tomcat/request/AccessInterceptor.java
Index: AccessInterceptor.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/AccessInterceptor.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- AccessInterceptor.java 2000/05/01 23:15:16 1.1
+++ AccessInterceptor.java 2000/05/02 22:39:58 1.2
@@ -88,6 +88,9 @@
// Security mapping note
int secMapNote;
+
+ // Required roles attribute
+ int reqRolesNote;
public AccessInterceptor() {
}
@@ -112,11 +115,13 @@
* dynamic add/remove for this interceptor.
*/
public void setContextManager( ContextManager cm ) {
- this.cm=cm;
+ super.setContextManager( cm );
+ this.cm=cm;
// set-up a per/container note for maps
try {
secMapNote = cm.getNoteId( ContextManager.CONTAINER_NOTE, "map.security");
+ reqRolesNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.roles");
} catch( TomcatException ex ) {
ex.printStackTrace();
throw new RuntimeException( "Invalid state ");
@@ -127,9 +132,8 @@
*/
public void addContext( ContextManager cm, Context ctx ) throws TomcatException
{
- Hashtable sec=new Hashtable();
Container ct=ctx.getContainer();
- ct.setNote( secMapNote, sec );
+ ct.setNote( secMapNote, new SecurityConstraints() );
}
/** Called when a context is removed from a CM - we must ask the mapper to
@@ -141,33 +145,27 @@
}
+ // XXX not implemented - will deal with that after everything else works.
+ public void removeContainer( Container ct )
+ throws TomcatException
+ {
+ }
+
/**
*/
public void addContainer( Container ct )
throws TomcatException
{
Context ctx=ct.getContext();
- String path=ct.getPath();
- String ctxP=ctx.getPath();
-
- if( ct.getRoles() != null ) {
- return; // XXX - right now we add either security or handler,
- // later we can use a more general/efficient aproach
- }
+ Container ctxCt=ctx.getContainer();
+ SecurityConstraints ctxSecurityC=(SecurityConstraints)ctxCt.getNote( secMapNote );
- if(ct.getHandler() == null ) {
- // it was only a security map
- return;
+ if( ct.getRoles()!=null || ct.getTransport()!=null ) {
+ log( "ACCESS: Adding " + ctx.getHost() + " " + ctx.getPath() + " " + ct.getPath() );
+ ctxSecurityC.addContainer( ct );
}
}
- // XXX not implemented - will deal with that after everything else works.
- public void removeContainer( Container ct )
- throws TomcatException
- {
- }
-
-
/* -------------------- Request mapping -------------------- */
/** Check if this request requires auth, and if so check the roles.
@@ -177,33 +175,84 @@
public int authorize( Request req, Response response )
{
Context ctx=req.getContext();
-
- // first we check if this request _requires_ access control
+ SecurityConstraints ctxSec=(SecurityConstraints)ctx.getContainer().getNote( secMapNote );
+ if( ctxSec.patterns==0 ) return 0; // fast exit
+
+ String reqURI = req.getRequestURI();
+ String ctxPath= ctx.getPath();
+ String path=reqURI.substring( ctxPath.length());
+ String method=req.getMethod();
- // this could be optimized and part of the contextMap, to
- // avoid double parsing and lookup - but in production mode
- // both methods will be no-ops anyway ( the server has already done
- // it ) - and in standalone mode it's not the biggest problem and
- // we can optimize it later if needed.
-
- String roles[]=req.getContainer().getRoles();
- if( roles==null ) {
- return 0;
+ log( "ACCESS: checking " + path );
+
+ for( int i=0; i< ctxSec.patterns ; i++ ) {
+ Container ct=ctxSec.securityPatterns[i];
+ if( match( ct, path, method ) ) {
+ log( "ACCESS: matched " + ct.getPath() + " " + ct.getMethods() + " " +
+ ct.getTransport() + " " + ct.getRoles());
+ String roles[]=ct.getRoles();
+ String transport=ct.getTransport();
+
+ if( transport != null && (
+ "INTEGRAL".equals( transport ) ||
+ "CONFIDENTAIL".equals( transport ))) {
+ // check if SSL is used
+ log( "ACCESS: SSL required " + req );
+ }
+
+ // roles will be checked by a different interceptor
+ req.setNote( reqRolesNote, roles );
+ }
}
-
return 0;
}
+ /** Find if a pattern is matched by a container
+ */
+ boolean match( Container ct, String path, String method ) {
+ String ctPath=ct.getPath();
+ int ctPathL=ctPath.length();
+ String ctMethods[]=ct.getMethods();
+
+ if( ctMethods != null && ctMethods.length > 0 ) {
+ boolean ok=false;
+ for( int i=0; i< ctMethods.length; i++ ) {
+ if( method.equals( ctMethods[i] ) ) {
+ ok=true;
+ break;
+ }
+ }
+ if( ! ok ) return false; // no method matched
+ }
+
+ // either method is any or we matched the method
+
+ switch( ct.getMapType() ) {
+ case Container.PREFIX_MAP:
+ return path.startsWith( ctPath.substring(0, ctPathL - 2 ));
+ case Container.EXTENSION_MAP:
+ return ctPath.substring( 1 ).equals( URLUtil.getExtension( path ));
+ case Container.PATH_MAP:
+ return path.equals( ctPath );
+ }
+ return false;
+ }
// -------------------- Implementation methods --------------------
-
-
}
-class ResourceCollectionPattern {
- String methods[];
- String prefixPatterns[];
- String extPatterns[];
- String exactPatterns[];
+class SecurityConstraints {
+ Container []securityPatterns;
+ int patterns=0;
+ // implement re-sizeable array later
+ static final int MAX_CONSTRAINTS=30;
+
+ public SecurityConstraints() {
+ securityPatterns=new Container[MAX_CONSTRAINTS];
+ }
-
+ // It's called in a single thread anyway
+ public synchronized void addContainer(Container ct) {
+ securityPatterns[ patterns ]= ct;
+ patterns++;
+ }
}
1.15 +45 -9 jakarta-tomcat/src/share/org/apache/tomcat/request/SecurityCheck.java
Index: SecurityCheck.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SecurityCheck.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- SecurityCheck.java 2000/04/07 22:59:01 1.14
+++ SecurityCheck.java 2000/05/02 22:39:58 1.15
@@ -81,10 +81,35 @@
public class SecurityCheck extends BaseInterceptor {
MemoryRealm memoryRealm;
int debug=0;
+ int reqRolesNote;
+ ContextManager cm;
public SecurityCheck() {
}
-
+
+ void log( String s ) {
+ cm.log("SecurityCheck: " + s );
+ }
+
+ /** Set the context manager. To keep it simple we don't support
+ * dynamic add/remove for this interceptor.
+ */
+ public void setContextManager( ContextManager cm ) {
+ super.setContextManager( cm );
+
+ this.cm=cm;
+ // set-up a per/container note for maps
+ try {
+ // XXX make the name a "global" static - after everything is stable!
+ reqRolesNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.roles");
+ } catch( TomcatException ex ) {
+ ex.printStackTrace();
+ throw new RuntimeException( "Invalid state ");
+ }
+ }
+
+
+
public void contextInit( Context ctx)
throws TomcatException
{
@@ -139,7 +164,7 @@
if( req.getRemoteUser() != null) return 0; // already authenticated
String authMethod=ctx.getAuthMethod();
- // if( ctx.getDebug() > 0 ) ctx.log( "Auth: " + authMethod );
+ // if( debug > 0 ) ctx.log( "Auth: " + authMethod );
if( authMethod==null || "BASIC".equals(authMethod) ) {
String authorization = req.getHeader("Authorization");
// XXX we may have multiple headers ?
@@ -147,14 +172,14 @@
authorization = authorization.substring(6).trim();
String unencoded=base64Decode( authorization );
int colon = unencoded.indexOf(':');
- if( ctx.getDebug() > 0 ) ctx.log( "BASIC auth " + authorization + " " + unencoded );
+ if( debug > 0 ) ctx.log( "BASIC auth " + authorization + " " + unencoded );
if (colon < 0)
return 0;
String username = unencoded.substring(0, colon);
String password = unencoded.substring(colon + 1);
if( checkPassword( username, password ) ) {
req.setRemoteUser( username );
- if( ctx.getDebug() > 0 ) ctx.log( "BASIC Auth: " + username );
+ if( debug > 0 ) ctx.log( "BASIC Auth: " + username );
} else {
// wrong password
errorPage( req, response );
@@ -181,7 +206,7 @@
// it wasn't authenticated, maybe it's a new login
String username=(String)session.getAttribute("j_username");
String password=(String)session.getAttribute("j_password");
- if( ctx.getDebug() > 0 ) ctx.log( "Form Auth: " + username + " " + password);
+ if( debug > 0 ) ctx.log( "Form Auth: " + username + " " + password);
if( username!=null && checkPassword( username, password ) ) {
req.setRemoteUser( username );
Credential c=new Credential();
@@ -208,6 +233,7 @@
public int authorize( Request req, Response response )
{
Context ctx=req.getContext();
+ log( "Authorizing " + req );
// Set default RequestSecurityProvider if not set
if (ctx.getRequestSecurityProvider() == null) {
Hashtable roles = memoryRealm.getRoles();
@@ -216,13 +242,23 @@
ctx.setRequestSecurityProvider(rsp);
}
+ // XXX 3.1 compatibility, will go away
String roles[]=req.getContainer().getRoles();
if( roles==null ) {
+ roles=(String[]) req.getNote( reqRolesNote );
+ }
+
+ if( roles==null ) {
+ log( "No roles " + req );
return 0;
}
-
+
+ for( int i=0; i< roles.length; i++ ) {
+ log( "Require " + roles[i]);
+ }
+
String user=req.getRemoteUser();
- if( ctx.getDebug() > 0 ) ctx.log( "Controled access for " + user + " " + req + " " + req.getContainer() );
+ if( debug > 0 ) log( "Controled access for " + user + " " + req + " " + req.getContainer() );
if( user!=null ) {
for( int i=0; i< roles.length; i++ ) {
if( userInRole( user, roles[i] ) )
@@ -230,7 +266,7 @@
}
}
- if( ctx.getDebug() > 0 ) ctx.log( "Unauthorized " + user + " " + req.getContainer());
+ if( debug > 0 ) log( "Unauthorized " + user + " " + req.getContainer());
return HttpServletResponse.SC_UNAUTHORIZED;
// XXX check transport
}
@@ -313,7 +349,7 @@
}
public void addUser(String name, String pass, String groups ) {
- if( debug > 0 ) ctx.log( "Add user " + name + " " + pass + " " + groups );
+ if( ctx.getDebug() > 0 ) ctx.log( "Add user " + name + " " + pass + " " + groups );
passwords.put( name, pass );
groups += ",";
while (true) {
1.17 +1 -1 jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java
Index: SessionInterceptor.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- SessionInterceptor.java 2000/05/01 23:15:16 1.16
+++ SessionInterceptor.java 2000/05/02 22:39:58 1.17
@@ -233,7 +233,7 @@
cookie.setMaxAge(-1);
cookie.setPath(sessionPath);
cookie.setVersion(1);
-
+
response.addHeader( CookieTools.getCookieHeaderName(cookie),
CookieTools.getCookieHeaderValue(cookie));
cookie.setVersion(0);
1.5 +1 -1 jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper1.java
Index: SimpleMapper1.java
===================================================================
RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper1.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- SimpleMapper1.java 2000/05/01 23:15:16 1.4
+++ SimpleMapper1.java 2000/05/02 22:39:58 1.5
@@ -179,7 +179,7 @@
String path=ct.getPath();
String ctxP=ctx.getPath();
- if(ct.getHandler() == null ) {
+ if(ct.getRoles() != null || ct.getTransport() != null ) {
// it was only a security map, no handler defined
return;
}
1.1 jakarta-tomcat/src/share/org/apache/tomcat/util/SecurityTools.java
Index: SecurityTools.java
===================================================================
/*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
* ITS 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 software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.tomcat.util;
import org.apache.tomcat.core.*;
import org.apache.tomcat.util.*;
import org.apache.tomcat.util.xml.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.http.*;
import org.xml.sax.*;
/**
* Various tools used to implement security.
*
*/
public class SecurityTools {
static int base64[]= {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
public static String base64Decode( String orig ) {
char chars[]=orig.toCharArray();
StringBuffer sb=new StringBuffer();
int i=0;
int shift = 0; // # of excess bits stored in accum
int acc = 0;
for (i=0; i<chars.length; i++) {
int v = base64[ chars[i] & 0xFF ];
if ( v >= 64 ) {
if( chars[i] != '=' )
System.out.println("Wrong char in base64: " + chars[i]);
} else {
acc= ( acc << 6 ) | v;
shift += 6;
if ( shift >= 8 ) {
shift -= 8;
sb.append( (char) ((acc >> shift) & 0xff));
}
}
}
return sb.toString();
}