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