SLING-522 Verify the correctness of the key according to symbolic-name

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@641241 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
index 39b992d..e254838 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
@@ -31,7 +31,7 @@
  * <code>java.util.Dictionary</code> which conforms to the requirements laid
  * out by the Configuration Admin Service Specification requiring the property
  * names to keep case but to ignore case when accessing the properties.
- *
+ * 
  * @author fmeschbe
  */
 class CaseInsensitiveDictionary extends Dictionary
@@ -43,8 +43,8 @@
     private Hashtable internalMap;
 
     /**
-     * Mapping of lower case keys to original case keys as last used to set
-     * a property value.
+     * Mapping of lower case keys to original case keys as last used to set a
+     * property value.
      */
     private Hashtable originalKeys;
 
@@ -64,10 +64,9 @@
         while ( keys.hasMoreElements() )
         {
             Object key = keys.nextElement();
-            if ( !( key instanceof String ) )
-            {
-                throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
-            }
+
+            // check the correct syntax of the key
+            checkKey( key );
 
             // check uniqueness of key
             String lowerCase = ( ( String ) key ).toLowerCase();
@@ -94,7 +93,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#elements()
      */
     public Enumeration elements()
@@ -103,7 +104,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#get(java.lang.Object)
      */
     public Object get( Object key )
@@ -118,7 +121,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#isEmpty()
      */
     public boolean isEmpty()
@@ -127,7 +132,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#keys()
      */
     public Enumeration keys()
@@ -136,7 +143,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
      */
     public Object put( Object key, Object value )
@@ -146,11 +155,7 @@
             throw new NullPointerException( "key or value" );
         }
 
-        if ( !( key instanceof String ) )
-        {
-            throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
-        }
-
+        checkKey( key );
         checkValue( value );
 
         String lowerCase = String.valueOf( key ).toLowerCase();
@@ -159,7 +164,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#remove(java.lang.Object)
      */
     public Object remove( Object key )
@@ -175,7 +182,9 @@
     }
 
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see java.util.Dictionary#size()
      */
     public int size()
@@ -184,7 +193,61 @@
     }
 
 
-    //---------- internal -----------------------------------------------------
+    // ---------- internal -----------------------------------------------------
+
+    /**
+     * Ensures the <code>key</code> complies with the <em>symbolic-name</em>
+     * production of the OSGi core specification (1.3.2):
+     * 
+     * <pre>
+     * symbolic-name :: = token('.'token)*
+     * digit    ::= [0..9]
+     * alpha    ::= [a..zA..Z]
+     * alphanum ::= alpha | digit
+     * token    ::= ( alphanum | ’_’ | ’-’ )+
+     * </pre>
+     * 
+     * If the key does not comply an <code>IllegalArgumentException</code> is
+     * thrown.
+     * 
+     * @param key
+     *            The configuration property key to check.
+     * @throws IllegalArgumentException
+     *             if the key does not comply with the symbolic-name production.
+     */
+    static void checkKey( Object keyObject )
+    {
+        if ( !( keyObject instanceof String ) )
+        {
+            throw new IllegalArgumentException( "Key [" + keyObject + "] must be a String" );
+        }
+
+        String key = ( String ) keyObject;
+        if ( key.startsWith( "." ) || key.endsWith( "." ) )
+        {
+            throw new IllegalArgumentException( "Key [" + key + "] must not start or end with a dot" );
+        }
+
+        int lastDot = Integer.MIN_VALUE;
+        for ( int i = 0; i < key.length(); i++ )
+        {
+            char c = key.charAt( i );
+            if ( c == '.' )
+            {
+                if ( lastDot == i - 1 )
+                {
+                    throw new IllegalArgumentException( "Key [" + key + "] must not have consecutive dots" );
+                }
+                lastDot = i;
+            }
+            else if ( ( c < '0' || c > '9' ) && ( c < 'a' || c > 'z' ) && ( c < 'A' || c > 'Z' ) && c != '_'
+                && c != '-' )
+            {
+                throw new IllegalArgumentException( "Key [" + key + "] contains illegal character" );
+            }
+        }
+    }
+
 
     static void checkValue( Object value )
     {
@@ -256,7 +319,7 @@
     }
 
 
-    //---------- Object Overwrites --------------------------------------------
+    // ---------- Object Overwrites --------------------------------------------
 
     public String toString()
     {
diff --git a/configadmin/src/test/java/org/apache/felix/cm/impl/CaseInsensitiveDictionaryTest.java b/configadmin/src/test/java/org/apache/felix/cm/impl/CaseInsensitiveDictionaryTest.java
new file mode 100644
index 0000000..71aa288
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/impl/CaseInsensitiveDictionaryTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.felix.cm.impl;
+
+import junit.framework.TestCase;
+
+public class CaseInsensitiveDictionaryTest extends TestCase
+{
+
+    public void testValidKeys() {
+        CaseInsensitiveDictionary.checkKey( "a" );
+        CaseInsensitiveDictionary.checkKey( "1" );
+        CaseInsensitiveDictionary.checkKey( "-" );
+        CaseInsensitiveDictionary.checkKey( "_" );
+        CaseInsensitiveDictionary.checkKey( "A" );
+        CaseInsensitiveDictionary.checkKey( "a.b.c" );
+        CaseInsensitiveDictionary.checkKey( "a.1.c" );
+        CaseInsensitiveDictionary.checkKey( "a-sample.dotted_key.end" );
+    }
+    
+    public void testKeyDots() {
+        testFailingKey( "." );
+        testFailingKey( ".a.b.c" );
+        testFailingKey( "a.b.c." );
+        testFailingKey( ".a.b.c." );
+        testFailingKey( "a..b" );
+    }
+    
+    public void testKeyIllegalCharacters() {
+        testFailingKey( " " );
+        testFailingKey( "§" );
+        testFailingKey( "${yikes}" );
+        testFailingKey( "a key with spaces" );
+        testFailingKey( "fail:key" );
+    }
+    
+    private void testFailingKey(String key) {
+        try {
+            CaseInsensitiveDictionary.checkKey( key );
+            fail("Expected IllegalArgumentException for key [" + key + "]");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+}