From 17c165a75521a36df7814b4439c717a7b7826d2c Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 18 Sep 2015 13:59:37 +0000
Subject: [PATCH] « given enough eyeballs, all bugs are shallow. »

---
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AuthPasswordSyntaxImpl.java |  238 ++++++++++++++++++++++++++++-------------------------------
 1 files changed, 114 insertions(+), 124 deletions(-)

diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AuthPasswordSyntaxImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AuthPasswordSyntaxImpl.java
index 5e9cedd..304f6f7 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AuthPasswordSyntaxImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AuthPasswordSyntaxImpl.java
@@ -22,16 +22,17 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions Copyright 2015 ForgeRock AS.
+ *      Portions Copyright 2015 ForgeRock AS
  */
 package org.forgerock.opendj.ldap.schema;
 
 import static com.forgerock.opendj.ldap.CoreMessages.*;
+
 import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_AUTH_PASSWORD_EXACT_OID;
 import static org.forgerock.opendj.ldap.schema.SchemaConstants.SYNTAX_AUTH_PASSWORD_NAME;
 
-import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.LocalizableMessageBuilder;
+import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.DecodeException;
 
@@ -41,6 +42,7 @@
  * matching will be allowed by default.
  */
 final class AuthPasswordSyntaxImpl extends AbstractSyntaxImpl {
+
     /**
      * Decodes the provided authentication password value into its component parts.
      *
@@ -51,25 +53,116 @@
      * @throws DecodeException
      *             If a problem is encountered while attempting to decode the value.
      */
-    static String[] decodeAuthPassword(final String authPasswordValue)
-            throws DecodeException {
-        // Create placeholders for the values to return.
-        final StringBuilder scheme = new StringBuilder();
-        final StringBuilder authInfo = new StringBuilder();
-        final StringBuilder authValue = new StringBuilder();
-
+    static String[] decodeAuthPassword(final String authPasswordValue) throws DecodeException {
         // First, ignore any leading whitespace.
         final int length = authPasswordValue.length();
         int pos = 0;
-        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
-            pos++;
-        }
+        pos = readSpaces(authPasswordValue, pos);
 
         // The next set of characters will be the scheme, which must consist
         // only of digits, uppercase alphabetic characters, dash, period,
         // slash, and underscore characters. It must be immediately followed
         // by one or more spaces or a dollar sign.
-    readScheme:
+        final StringBuilder scheme = new StringBuilder();
+        pos = readScheme(authPasswordValue, scheme, pos);
+        // The scheme must consist of at least one character.
+        if (scheme.length() == 0) {
+            throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get());
+        }
+
+        pos = readSpaces(authPasswordValue, pos);
+        throwIfEndReached(authPasswordValue, length, pos, ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR);
+        pos++;
+        pos = readSpaces(authPasswordValue, pos);
+
+        // The next component must be the authInfo element, containing only
+        // printable characters other than the dollar sign and space character.
+        final StringBuilder authInfo = new StringBuilder();
+        pos = readAuthInfo(authPasswordValue, authInfo, pos);
+        // The authInfo element must consist of at least one character.
+        if (scheme.length() == 0) {
+            throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get());
+        }
+
+        pos = readSpaces(authPasswordValue, pos);
+        throwIfEndReached(authPasswordValue, length, pos, ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR);
+        pos++;
+        pos = readSpaces(authPasswordValue, pos);
+
+        // The final component must be the authValue element, containing
+        // only printable characters other than the dollar sign and space character.
+        final StringBuilder authValue = new StringBuilder();
+        pos = readAuthValue(authPasswordValue, length, pos, authValue);
+        // The authValue element must consist of at least one character.
+        if (scheme.length() == 0) {
+            throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get());
+        }
+
+        // The only characters remaining must be whitespace.
+        while (pos < length) {
+            final char c = authPasswordValue.charAt(pos);
+            if (c == ' ') {
+                pos++;
+            } else {
+                throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos));
+            }
+        }
+
+        return new String[] { scheme.toString(), authInfo.toString(), authValue.toString() };
+    }
+
+    private static int readAuthValue(final String authPasswordValue, final int length, int pos,
+        final StringBuilder authValue) throws DecodeException
+    {
+      while (pos < length) {
+          final char c = authPasswordValue.charAt(pos);
+          if (c == ' ' || c == '$') {
+              break;
+          } else if (PrintableStringSyntaxImpl.isPrintableCharacter(c)) {
+              authValue.append(c);
+              pos++;
+          } else {
+              throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos));
+          }
+      }
+      return pos;
+    }
+
+    private static void throwIfEndReached(final String authPasswordValue, final int length, int pos, final Arg0 message)
+            throws DecodeException {
+        if (pos >= length || authPasswordValue.charAt(pos) != '$') {
+            throw DecodeException.error(message.get());
+        }
+    }
+
+    private static int readAuthInfo(final String authPasswordValue, final StringBuilder authInfo, int pos)
+            throws DecodeException {
+        final int length = authPasswordValue.length();
+        while (pos < length) {
+            final char c = authPasswordValue.charAt(pos);
+            if (c == ' ' || c == '$') {
+                break;
+            } else if (PrintableStringSyntaxImpl.isPrintableCharacter(c)) {
+                authInfo.append(c);
+                pos++;
+            } else {
+                throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos));
+            }
+        }
+        return pos;
+    }
+
+    private static int readSpaces(final String authPasswordValue, int pos) {
+        final int length = authPasswordValue.length();
+        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
+            pos++;
+        }
+        return pos;
+    }
+
+    private static int readScheme(final String authPasswordValue, final StringBuilder scheme, int pos)
+            throws DecodeException {
+        final int length = authPasswordValue.length();
         while (pos < length) {
             final char c = authPasswordValue.charAt(pos);
 
@@ -119,120 +212,16 @@
                 break;
             case ' ':
             case '$':
-                break readScheme;
+                return pos;
             default:
-                final LocalizableMessage message =
-                        ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
-                throw DecodeException.error(message);
+                throw DecodeException.error(ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos));
             }
         }
-
-        // The scheme must consist of at least one character.
-        if (scheme.length() == 0) {
-            final LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
-            throw DecodeException.error(message);
-        }
-
-        // Ignore any spaces before the dollar sign separator. Then read the
-        // dollar sign and ignore any trailing spaces.
-        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
-            pos++;
-        }
-
-        if (pos < length && authPasswordValue.charAt(pos) == '$') {
-            pos++;
-        } else {
-            final LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
-            throw DecodeException.error(message);
-        }
-
-        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
-            pos++;
-        }
-
-        // The next component must be the authInfo element, containing only
-        // printable characters other than the dollar sign and space
-        // character.
-    readAuthInfo:
-        while (pos < length) {
-            final char c = authPasswordValue.charAt(pos);
-            if (c == ' ' || c == '$') {
-                break readAuthInfo;
-            } else if (PrintableStringSyntaxImpl.isPrintableCharacter(c)) {
-                authInfo.append(c);
-                pos++;
-            } else {
-                final LocalizableMessage message =
-                        ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
-                throw DecodeException.error(message);
-            }
-        }
-
-        // The authInfo element must consist of at least one character.
-        if (scheme.length() == 0) {
-            final LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
-            throw DecodeException.error(message);
-        }
-
-        // Ignore any spaces before the dollar sign separator. Then read the
-        // dollar sign and ignore any trailing spaces.
-        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
-            pos++;
-        }
-
-        if (pos < length && authPasswordValue.charAt(pos) == '$') {
-            pos++;
-        } else {
-            final LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
-            throw DecodeException.error(message);
-        }
-
-        while (pos < length && authPasswordValue.charAt(pos) == ' ') {
-            pos++;
-        }
-
-        // The final component must be the authValue element, containing
-        // only printable characters other than the dollar sign and space
-        // character.
-        while (pos < length) {
-            final char c = authPasswordValue.charAt(pos);
-            if (c == ' ' || c == '$') {
-                break;
-            } else if (PrintableStringSyntaxImpl.isPrintableCharacter(c)) {
-                authValue.append(c);
-                pos++;
-            } else {
-                final LocalizableMessage message =
-                        ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
-                throw DecodeException.error(message);
-            }
-        }
-
-        // The authValue element must consist of at least one character.
-        if (scheme.length() == 0) {
-            final LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
-            throw DecodeException.error(message);
-        }
-
-        // The only characters remaining must be whitespace.
-        while (pos < length) {
-            final char c = authPasswordValue.charAt(pos);
-            if (c == ' ') {
-                pos++;
-            } else {
-                final LocalizableMessage message =
-                        ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
-                throw DecodeException.error(message);
-            }
-        }
-
-        // If we've gotten here, then everything must be OK.
-        return new String[] { scheme.toString(), authInfo.toString(), authValue.toString() };
+        return pos;
     }
 
     /**
-     * Indicates whether the provided value is encoded using the auth password
-     * syntax.
+     * Indicates whether the provided value is encoded using the auth password syntax.
      *
      * @param value
      *            The value for which to make the determination.
@@ -240,9 +229,7 @@
      *         auth password syntax, or <CODE>false</CODE> if not.
      */
     static boolean isEncoded(final ByteSequence value) {
-        // FIXME -- Make this more efficient, and don't use exceptions for
-        // flow control.
-
+        // FIXME -- Make this more efficient, and don't use exceptions for flow control.
         try {
             decodeAuthPassword(value.toString());
             return true;
@@ -256,14 +243,17 @@
         return EMR_AUTH_PASSWORD_EXACT_OID;
     }
 
+    @Override
     public String getName() {
         return SYNTAX_AUTH_PASSWORD_NAME;
     }
 
+    @Override
     public boolean isHumanReadable() {
         return true;
     }
 
+    @Override
     public boolean valueIsAcceptable(final Schema schema, final ByteSequence value,
             final LocalizableMessageBuilder invalidReason) {
         try {

--
Gitblit v1.10.0