/*
* Copyright 2010-2011 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package blackberry.web.widget.policy;
import java.util.Hashtable;
import net.rim.device.api.io.MalformedURIException;
import net.rim.device.api.io.URI;
import net.rim.device.api.web.WidgetAccess;
import blackberry.web.widget.util.WidgetUtil;
/**
*
*/
public class WidgetPolicy {
private Hashtable _authorityCollection;
private WidgetAccess _localAccess;
/**
* Apply widget security model to the requested URI to retrieve the corresponding <access> element from array.
* http://www.w3.org/TR/2009/WD-widgets-access-20090618/#rfc3987
*
* @param request
* The requested URI.
* @param accessList
* WidgetAccess array to loop through to find matching request.
* @return Return true if the request should be allowed based on WebWorks' config.xml; false otherwise.
*/
public WidgetAccess getElement( String request, WidgetAccess[] accessList ) {
try {
URI requestURI = URI.create( request.trim() );
// require absolute URI's
if( requestURI.isAbsolute() ) {
// Initialize authority collection if it does not yet exist
initializeAuthCollection( accessList );
// Start with the full authority path and check if a WidgetAccess set exists for that path
// If it does not exist, remove the first section of the authority path and try again
String authString = getAuthorityFromString( request );
String schemeString = getSchemeFromString( request );
// Check for an authority string that has an existing key
// Special case: Allow file protocol to proceed without an authority
// Special case: Allow local protocol which is always without an authority
// Special case: Allow data protocol which is always without an authority (let isMatch handle it)
authString = authorityCheck( schemeString, authString );
if( authString.equals( "" ) && !( schemeString.equals( "file" ) || schemeString.equals( "local" ) || schemeString.equals( "data" ) ) ) {
return null;
}
WidgetWebFolderAccess folderAccess;
WidgetAccess fetchedAccess = null;
// Retrieve WidgetAccess set for the specified authority
folderAccess = (WidgetWebFolderAccess) _authorityCollection.get( schemeString + "://" + authString );
// Special case: no access element was found for a file protocol request.
// This is added since file protocol was allowed through the above check
if( schemeString.equals( "file" ) && folderAccess == null ) {
return null;
}
// If no access element is found with local URI, use local access for this request
if ( schemeString.equals( "local" ) && folderAccess == null ) {
return _localAccess;
}
if(folderAccess != null) {
fetchedAccess = folderAccess.getWidgetAccess( requestURI.getPath() + parseNull( requestURI.getQuery() ) );
}
if( !isMatch( fetchedAccess, requestURI ) ) {
fetchedAccess = folderAccess.getWidgetAccess( requestURI.getPath() + "*" );
}
boolean failedToFindAccess = false;
// Make sure we've got the right one
while( fetchedAccess == null || !isMatch( fetchedAccess, requestURI ) ) {
// There was an auth url that matched, but didnt match the folder structure
// Try the next level up
authString = authString.substring( authString.indexOf( '.' ) + 1 );
// Check for an authority string that has an existing key
authString = authorityCheck( schemeString, authString );
if( authString.equals( "" ) ) {
failedToFindAccess = true;
break;
}
// Retrieve WidgetAccess set for the specified authority
folderAccess = (WidgetWebFolderAccess) _authorityCollection.get( schemeString + "://" + authString );
// Special case: no access element was found for a file protocol request.
// This is added since file protocol was allowed through the above check
if( schemeString.equals( "file" ) && folderAccess == null ) {
return null;
}
fetchedAccess = folderAccess.getWidgetAccess( requestURI.getPath() + parseNull( requestURI.getQuery() ) );
}
if( !failedToFindAccess ) {
return fetchedAccess;
} else if ( isMatch( _localAccess, requestURI ) ) {
// If we cannot find a more specific access for this local URI, use local access
return _localAccess;
}
}
} catch( MalformedURIException mue ) {
// invalid request URI - return null
}
return null;
}
private boolean isMatch( WidgetAccess access, URI toMatchURI ) {
if( access == null ) {
return false;
}
// Look for local first
if( WidgetUtil.isLocalURI( toMatchURI ) && access.isLocal() ) {
// local access always allowed
return true;
}
// Check for data url
else if( WidgetUtil.isDataURI( toMatchURI ) ) {
// data urls are allowed
return true;
} else if( access.isLocal() ) {
return false;
}
// Based on widgets 1.0 (access control)
// http://www.w3.org/TR/2009/WD-widgets-access-20090618/#rfc3987
URI referenceURI = access.getURI();
boolean allowSub = access.allowSubDomain();
// Start comparison based on widgets spec.
// 1. Compare scheme
if( !referenceURI.getScheme().equalsIgnoreCase( toMatchURI.getScheme() ) ) {
return false;
}
// 2. Compare host - if subdoman is false, host must match exactly
// (referenceURI MUST HAVE host specified - not null.)
String refHost = referenceURI.getHost();
String matchHost = toMatchURI.getHost();
if( matchHost == null ) {
return false;
}
if( !allowSub && !( refHost.equalsIgnoreCase( matchHost ) ) ) {
return false;
}
// 3. Compare host - if subdomain is true, check for subdomain or match
if( allowSub && !matchHost.toLowerCase().endsWith( "." + refHost.toLowerCase() ) && !matchHost.equalsIgnoreCase( refHost ) ) {
return false;
}
// 4. Compare port
String refPort = parseNull( referenceURI.getPort() );
String toMatchPort = parseNull( toMatchURI.getPort() );
if( !refPort.equals( toMatchPort ) ) {
return false;
}
// 5. Compare path+query
String refPath = referenceURI.getPath() + parseNull( referenceURI.getQuery() );
String toMatchPath = toMatchURI.getPath() + parseNull( toMatchURI.getQuery() );
if( refPath.endsWith( "*" ) ) {
refPath = refPath.substring( 0, refPath.length() - 1 );
}
if( !toMatchPath.startsWith( refPath ) ) {
return false;
}
return true;
}
private String parseNull( String toParse ) {
return toParse == null ? "" : toParse;
}
/**
* Initalizes the collection of authority urls with their proper WidgetAccess elements
*
* @param accessList
* List of WidgetAccess elements to add into the collection
*/
private void initializeAuthCollection( WidgetAccess[] accessList ) {
// Initialize collection if it does not yet exist
if( _authorityCollection == null ) {
_authorityCollection = new Hashtable();
// Loop access elements and add them to the authority collection
for( int i = 0; i < accessList.length; i++ ) {
WidgetAccess currentAccess = accessList[ i ];
URI currentURI = currentAccess.getURI();
// Special case: local access does not go into the collection because it has no URI
if( currentAccess.isLocal() ) {
_localAccess = currentAccess;
} else {
WidgetWebFolderAccess folderAccess;
// Check the authority collection to see if the authority item we want already exists
if( _authorityCollection.containsKey( currentURI.getScheme() + "://" + currentURI.getAuthority() ) ) {
folderAccess = (WidgetWebFolderAccess) _authorityCollection.get( currentURI.getScheme() + "://"
+ currentURI.getAuthority() );
} else {
// Create web folder access
folderAccess = new WidgetWebFolderAccess();
}
// Add folder path access to the authority item
folderAccess.addWidgetAccess( currentURI.getPath() + parseNull( currentURI.getQuery() ), currentAccess );
_authorityCollection.put( currentURI.getScheme() + "://" + currentURI.getAuthority(), folderAccess );
}
}
}
}
/**
* Retrieves the authority portion of a URL string
*
* @param url
* URL to parse for authority
* @return authority URL
*/
private String getAuthorityFromString( String url ) {
try {
URI uriObject = URI.create( url );
return uriObject.getAuthority();
} catch( MalformedURIException mue ) {
// invalid request URI - return null
return null;
}
}
/**
* Retrieves the scheme portion of a URL string
*
* @param url
* URL to parse for authority
* @return scheme of the URL
*/
private String getSchemeFromString( String url ) {
try {
URI uriObject = URI.create( url );
return uriObject.getScheme();
} catch( MalformedURIException mue ) {
// invalid request URI - return null
return null;
}
}
/**
* Process the given scheme and authority and returns an authority which exists in the authority collection
*
* @param scheme
* Scheme of the request url
* @param authString
* Authority of the request url
* @return Authority which exists in the authority collection
*/
private String authorityCheck( String scheme, String authString ) {
if(authString == null) {
authString = "";
}
boolean firstPass = true;
while( !_authorityCollection.containsKey( scheme + "://" + authString ) ) {
// If the authority is empty string, then no access element exists for that subdomain
// Also, if the auth becomes a top level domain and is not found, then stop as well
// First pass will allow computer names to be used
if( authString.equals( "" ) || ( ( authString.indexOf( '.' ) == -1 ) && !firstPass ) ) {
return "";
}
authString = authString.substring( authString.indexOf( '.' ) + 1 );
// Set the flag
if( firstPass ) {
firstPass = false;
}
}
return authString;
}
}