Home | History | Annotate | Download | only in x501
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Alexander V. Esin
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.x501;
     24 
     25 import java.io.IOException;
     26 import org.apache.harmony.security.asn1.ASN1StringType;
     27 import org.apache.harmony.security.asn1.DerInputStream;
     28 
     29 /**
     30  * X.501 Attribute Value
     31  */
     32 public final class AttributeValue {
     33 
     34     public final boolean wasEncoded;
     35 
     36     public String escapedString;
     37 
     38     private String hexString;
     39 
     40     private int tag = -1;
     41 
     42     public byte[] encoded;
     43 
     44     public byte[] bytes; //FIXME remove??? bytes to be encoded
     45 
     46     public boolean hasQE; // raw string contains '"' or '\'
     47 
     48     public String rawString;
     49 
     50     public AttributeValue(String parsedString, boolean hasQorE) {
     51         wasEncoded = false;
     52 
     53         this.hasQE = hasQorE;
     54 
     55         this.rawString = parsedString;
     56         this.escapedString = makeEscaped(rawString);
     57     }
     58 
     59     public AttributeValue(String hexString, byte[] encoded) {
     60         wasEncoded = true;
     61 
     62         this.hexString = hexString;
     63         this.encoded = encoded;
     64 
     65         try {
     66             DerInputStream in = new DerInputStream(encoded);
     67 
     68             tag = in.tag;
     69 
     70             if (DirectoryString.ASN1.checkTag(tag)) {
     71                 // has string representation
     72                 this.rawString = (String) DirectoryString.ASN1.decode(in);
     73                 this.escapedString = makeEscaped(rawString);
     74             } else {
     75                 this.rawString = hexString;
     76                 this.escapedString = hexString;
     77             }
     78         } catch (IOException e) {
     79             IllegalArgumentException iae = new IllegalArgumentException(); //FIXME message
     80             iae.initCause(e);
     81             throw iae;
     82         }
     83     }
     84 
     85     public AttributeValue(String rawString, byte[] encoded, int tag) {
     86         wasEncoded = true;
     87 
     88         this.encoded = encoded;
     89         this.tag = tag;
     90 
     91         if (rawString == null) {
     92             this.rawString = getHexString();
     93             this.escapedString = hexString;
     94         } else {
     95             this.rawString = rawString;
     96             this.escapedString = makeEscaped(rawString);
     97         }
     98     }
     99 
    100     /**
    101      * Checks if the string is PrintableString (see X.680)
    102      */
    103     private static boolean isPrintableString(String str) {
    104         for (int i = 0; i< str.length(); ++i) {
    105             char ch = str.charAt(i);
    106             if (!(ch == 0x20
    107             || ch >= 0x27 && ch<= 0x29 // '()
    108             || ch >= 0x2B && ch<= 0x3A // +,-./0-9:
    109             || ch == '='
    110             || ch == '?'
    111             || ch >= 'A' && ch<= 'Z'
    112             || ch >= 'a' && ch<= 'z')) {
    113                 return false;
    114             }
    115         }
    116         return true;
    117     }
    118 
    119     public int getTag() {
    120         if (tag == -1) {
    121             tag = isPrintableString(rawString)
    122                     ? ASN1StringType.PRINTABLESTRING.id
    123                     : ASN1StringType.UTF8STRING.id;
    124         }
    125         return tag;
    126     }
    127 
    128     public String getHexString() {
    129         if (hexString == null) {
    130             if (!wasEncoded) {
    131                 //FIXME optimize me: what about reusable OutputStream???
    132                 encoded = isPrintableString(rawString)
    133                         ? ASN1StringType.PRINTABLESTRING.encode(rawString)
    134                         : ASN1StringType.UTF8STRING.encode(rawString);
    135             }
    136 
    137             StringBuilder buf = new StringBuilder(encoded.length * 2 + 1);
    138             buf.append('#');
    139 
    140             for (int i = 0, c; i < encoded.length; i++) {
    141                 c = (encoded[i] >> 4) & 0x0F;
    142                 if (c < 10) {
    143                     buf.append((char) (c + 48));
    144                 } else {
    145                     buf.append((char) (c + 87));
    146                 }
    147 
    148                 c = encoded[i] & 0x0F;
    149                 if (c < 10) {
    150                     buf.append((char) (c + 48));
    151                 } else {
    152                     buf.append((char) (c + 87));
    153                 }
    154             }
    155             hexString = buf.toString();
    156         }
    157         return hexString;
    158     }
    159 
    160     public void appendQEString(StringBuilder sb) {
    161         sb.append('"');
    162         if (hasQE) {
    163             char c;
    164             for (int i = 0; i < rawString.length(); i++) {
    165                 c = rawString.charAt(i);
    166                 if (c == '"' || c == '\\') {
    167                     sb.append('\\');
    168                 }
    169                 sb.append(c);
    170             }
    171         } else {
    172             sb.append(rawString);
    173         }
    174         sb.append('"');
    175     }
    176 
    177     /**
    178      * Escapes:
    179      * 1) chars ",", "+", """, "\", "<", ">", ";" (RFC 2253)
    180      * 2) chars "#", "=" (required by RFC 1779)
    181      * 3) a space char at the beginning or end
    182      * 4) according to the requirement to be RFC 1779 compatible:
    183      *    '#' char is escaped in any position
    184      */
    185     private String makeEscaped(String name) {
    186         int length = name.length();
    187         if (length == 0) {
    188             return name;
    189         }
    190         StringBuilder buf = new StringBuilder(length * 2);
    191 
    192         for (int index = 0; index < length; index++) {
    193             char ch = name.charAt(index);
    194             switch (ch) {
    195             case ' ':
    196                 if (index == 0 || index == (length - 1)) {
    197                     // escape first or last space
    198                     buf.append('\\');
    199                 }
    200                 buf.append(' ');
    201                 break;
    202 
    203             case '"':
    204             case '\\':
    205                 hasQE = true;
    206                 buf.append('\\');
    207                 buf.append(ch);
    208                 break;
    209 
    210             case ',':
    211             case '+':
    212             case '<':
    213             case '>':
    214             case ';':
    215             case '#': // required by RFC 1779
    216             case '=': // required by RFC 1779
    217                 buf.append('\\');
    218                 buf.append(ch);
    219                 break;
    220 
    221             default:
    222                 buf.append(ch);
    223                 break;
    224             }
    225         }
    226 
    227         return buf.toString();
    228     }
    229 
    230     public String makeCanonical() {
    231         int length = rawString.length();
    232         if (length == 0) {
    233             return rawString;
    234         }
    235         StringBuilder buf = new StringBuilder(length * 2);
    236 
    237         int index = 0;
    238         if (rawString.charAt(0) == '#') {
    239             buf.append('\\');
    240             buf.append('#');
    241             index++;
    242         }
    243 
    244         int bufLength;
    245         for (; index < length; index++) {
    246             char ch = rawString.charAt(index);
    247 
    248             switch (ch) {
    249             case ' ':
    250                 bufLength = buf.length();
    251                 if (bufLength == 0 || buf.charAt(bufLength - 1) == ' ') {
    252                     break;
    253                 }
    254                 buf.append(' ');
    255                 break;
    256 
    257             case '"':
    258             case '\\':
    259             case ',':
    260             case '+':
    261             case '<':
    262             case '>':
    263             case ';':
    264                 buf.append('\\');
    265 
    266             default:
    267                 buf.append(ch);
    268             }
    269         }
    270 
    271         //remove trailing spaces
    272         for (bufLength = buf.length() - 1; bufLength > -1
    273                 && buf.charAt(bufLength) == ' '; bufLength--) {
    274         }
    275         buf.setLength(bufLength + 1);
    276 
    277         return buf.toString();
    278     }
    279 }
    280