1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.vcard; 17 18 import android.util.Log; 19 20 import com.android.vcard.exception.VCardException; 21 22 import java.io.IOException; 23 import java.util.Set; 24 25 /** 26 * <p> 27 * Basic implementation achieving vCard 3.0 parsing. 28 * </p> 29 * <p> 30 * This class inherits vCard 2.1 implementation since technically they are similar, 31 * while specifically there's logical no relevance between them. 32 * So that developers are not confused with the inheritance, 33 * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while 34 * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}. 35 * </p> 36 * @hide 37 */ 38 /* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 { 39 private static final String LOG_TAG = VCardConstants.LOG_TAG; 40 41 private String mPreviousLine; 42 private boolean mEmittedAgentWarning = false; 43 44 public VCardParserImpl_V30() { 45 super(); 46 } 47 48 public VCardParserImpl_V30(int vcardType) { 49 super(vcardType); 50 } 51 52 @Override 53 protected int getVersion() { 54 return VCardConfig.VERSION_30; 55 } 56 57 @Override 58 protected String getVersionString() { 59 return VCardConstants.VERSION_V30; 60 } 61 62 @Override 63 protected String getLine() throws IOException { 64 if (mPreviousLine != null) { 65 String ret = mPreviousLine; 66 mPreviousLine = null; 67 return ret; 68 } else { 69 return mReader.readLine(); 70 } 71 } 72 73 /** 74 * vCard 3.0 requires that the line with space at the beginning of the line 75 * must be combined with previous line. 76 */ 77 @Override 78 protected String getNonEmptyLine() throws IOException, VCardException { 79 String line; 80 StringBuilder builder = null; 81 while ((line = mReader.readLine()) != null) { 82 // Skip empty lines in order to accomodate implementations that 83 // send line termination variations such as \r\r\n. 84 if (line.length() == 0) { 85 continue; 86 } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') { 87 // RFC 2425 describes line continuation as \r\n followed by 88 // a single ' ' or '\t' whitespace character. 89 if (builder == null) { 90 builder = new StringBuilder(); 91 } 92 if (mPreviousLine != null) { 93 builder.append(mPreviousLine); 94 mPreviousLine = null; 95 } 96 builder.append(line.substring(1)); 97 } else { 98 if (builder != null || mPreviousLine != null) { 99 break; 100 } 101 mPreviousLine = line; 102 } 103 } 104 105 String ret = null; 106 if (builder != null) { 107 ret = builder.toString(); 108 } else if (mPreviousLine != null) { 109 ret = mPreviousLine; 110 } 111 mPreviousLine = line; 112 if (ret == null) { 113 throw new VCardException("Reached end of buffer."); 114 } 115 return ret; 116 } 117 118 /* 119 * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF 120 * 1 * (contentline) 121 * ;A vCard object MUST include the VERSION, FN and N types. 122 * [group "."] "END" ":" "VCARD" 1 * CRLF 123 */ 124 @Override 125 protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { 126 // TODO: vCard 3.0 supports group. 127 return super.readBeginVCard(allowGarbage); 128 } 129 130 /** 131 * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not. 132 */ 133 @Override 134 protected void handleParams(VCardProperty propertyData, final String params) 135 throws VCardException { 136 try { 137 super.handleParams(propertyData, params); 138 } catch (VCardException e) { 139 // maybe IANA type 140 String[] strArray = params.split("=", 2); 141 if (strArray.length == 2) { 142 handleAnyParam(propertyData, strArray[0], strArray[1]); 143 } else { 144 // Must not come here in the current implementation. 145 throw new VCardException( 146 "Unknown params value: " + params); 147 } 148 } 149 } 150 151 @Override 152 protected void handleAnyParam( 153 VCardProperty propertyData, final String paramName, final String paramValue) { 154 splitAndPutParam(propertyData, paramName, paramValue); 155 } 156 157 @Override 158 protected void handleParamWithoutName(VCardProperty property, final String paramValue) { 159 handleType(property, paramValue); 160 } 161 162 /* 163 * vCard 3.0 defines 164 * 165 * param = param-name "=" param-value *("," param-value) 166 * param-name = iana-token / x-name 167 * param-value = ptext / quoted-string 168 * quoted-string = DQUOTE QSAFE-CHAR DQUOTE 169 * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII 170 * ; Any character except CTLs, DQUOTE 171 * 172 * QSAFE-CHAR must not contain DQUOTE, including escaped one (\"). 173 */ 174 @Override 175 protected void handleType(VCardProperty property, final String paramValue) { 176 splitAndPutParam(property, VCardConstants.PARAM_TYPE, paramValue); 177 } 178 179 /** 180 * Splits parameter values into pieces in accordance with vCard 3.0 specification and 181 * puts pieces into mInterpreter. 182 */ 183 /* 184 * param-value = ptext / quoted-string 185 * quoted-string = DQUOTE QSAFE-CHAR DQUOTE 186 * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII 187 * ; Any character except CTLs, DQUOTE 188 * 189 * QSAFE-CHAR must not contain DQUOTE, including escaped one (\") 190 */ 191 private void splitAndPutParam(VCardProperty property, String paramName, String paramValue) { 192 // "comma,separated:inside.dquote",pref 193 // --> 194 // - comma,separated:inside.dquote 195 // - pref 196 // 197 // Note: Though there's a code, we don't need to take much care of 198 // wrongly-added quotes like the example above, as they induce 199 // parse errors at the top level (when splitting a line into parts). 200 StringBuilder builder = null; // Delay initialization. 201 boolean insideDquote = false; 202 final int length = paramValue.length(); 203 for (int i = 0; i < length; i++) { 204 final char ch = paramValue.charAt(i); 205 if (ch == '"') { 206 if (insideDquote) { 207 // End of Dquote. 208 property.addParameter(paramName, encodeParamValue(builder.toString())); 209 builder = null; 210 insideDquote = false; 211 } else { 212 if (builder != null) { 213 if (builder.length() > 0) { 214 // e.g. 215 // pref"quoted" 216 Log.w(LOG_TAG, "Unexpected Dquote inside property."); 217 } else { 218 // e.g. 219 // pref,"quoted" 220 // "quoted",pref 221 property.addParameter(paramName, encodeParamValue(builder.toString())); 222 } 223 } 224 insideDquote = true; 225 } 226 } else if (ch == ',' && !insideDquote) { 227 if (builder == null) { 228 Log.w(LOG_TAG, "Comma is used before actual string comes. (" + 229 paramValue + ")"); 230 } else { 231 property.addParameter(paramName, encodeParamValue(builder.toString())); 232 builder = null; 233 } 234 } else { 235 // To stop creating empty StringBuffer at the end of parameter, 236 // we delay creating this object until this point. 237 if (builder == null) { 238 builder = new StringBuilder(); 239 } 240 builder.append(ch); 241 } 242 } 243 if (insideDquote) { 244 // e.g. 245 // "non-quote-at-end 246 Log.d(LOG_TAG, "Dangling Dquote."); 247 } 248 if (builder != null) { 249 if (builder.length() == 0) { 250 Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " + 251 "at the end of parameter value parsing."); 252 } else { 253 property.addParameter(paramName, encodeParamValue(builder.toString())); 254 } 255 } 256 } 257 258 /** 259 * Encode a param value using UTF-8. 260 */ 261 protected String encodeParamValue(String paramValue) { 262 return VCardUtils.convertStringCharset( 263 paramValue, VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, "UTF-8"); 264 } 265 266 @Override 267 protected void handleAgent(VCardProperty property) { 268 // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1. 269 // 270 // e.g. 271 // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n 272 // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n 273 // ET:jfriday (at) host.com\nEND:VCARD\n 274 // 275 // TODO: fix this. 276 // 277 // issue: 278 // vCard 3.0 also allows this as an example. 279 // 280 // AGENT;VALUE=uri: 281 // CID:JQPUBLIC.part3.960129T083020.xyzMail (at) host3.com 282 // 283 // This is not vCard. Should we support this? 284 // 285 // Just ignore the line for now, since we cannot know how to handle it... 286 if (!mEmittedAgentWarning) { 287 Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it"); 288 mEmittedAgentWarning = true; 289 } 290 } 291 292 /** 293 * This is only called from handlePropertyValue(), which has already 294 * read the first line of this property. With v3.0, the getNonEmptyLine() 295 * routine has already concatenated all following continuation lines. 296 * The routine is implemented in the V21 parser to concatenate v2.1 style 297 * data blocks, but is unnecessary here. 298 */ 299 @Override 300 protected String getBase64(final String firstString) 301 throws IOException, VCardException { 302 return firstString; 303 } 304 305 /** 306 * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N") 307 * ; \\ encodes \, \n or \N encodes newline 308 * ; \; encodes ;, \, encodes , 309 * 310 * Note: Apple escapes ':' into '\:' while does not escape '\' 311 */ 312 @Override 313 protected String maybeUnescapeText(final String text) { 314 return unescapeText(text); 315 } 316 317 public static String unescapeText(final String text) { 318 StringBuilder builder = new StringBuilder(); 319 final int length = text.length(); 320 for (int i = 0; i < length; i++) { 321 char ch = text.charAt(i); 322 if (ch == '\\' && i < length - 1) { 323 final char next_ch = text.charAt(++i); 324 if (next_ch == 'n' || next_ch == 'N') { 325 builder.append("\n"); 326 } else { 327 builder.append(next_ch); 328 } 329 } else { 330 builder.append(ch); 331 } 332 } 333 return builder.toString(); 334 } 335 336 @Override 337 protected String maybeUnescapeCharacter(final char ch) { 338 return unescapeCharacter(ch); 339 } 340 341 public static String unescapeCharacter(final char ch) { 342 if (ch == 'n' || ch == 'N') { 343 return "\n"; 344 } else { 345 return String.valueOf(ch); 346 } 347 } 348 349 @Override 350 protected Set<String> getKnownPropertyNameSet() { 351 return VCardParser_V30.sKnownPropertyNameSet; 352 } 353 } 354