1/* 2 * Copyright (C) 2014 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 17package android.bluetooth.client.map; 18 19import android.util.Log; 20 21import com.android.vcard.VCardEntry; 22import com.android.vcard.VCardEntryConstructor; 23import com.android.vcard.VCardEntryHandler; 24import com.android.vcard.VCardParser; 25import com.android.vcard.VCardParser_V21; 26import com.android.vcard.VCardParser_V30; 27import com.android.vcard.exception.VCardException; 28import com.android.vcard.exception.VCardVersionException; 29import android.bluetooth.client.map.BluetoothMapBmessage.Status; 30import android.bluetooth.client.map.BluetoothMapBmessage.Type; 31import android.bluetooth.client.map.utils.BmsgTokenizer; 32import android.bluetooth.client.map.utils.BmsgTokenizer.Property; 33 34import java.io.ByteArrayInputStream; 35import java.io.IOException; 36import java.text.ParseException; 37 38class BluetoothMapBmessageParser { 39 40 private final static String TAG = "BluetoothMapBmessageParser"; 41 42 private final static String CRLF = "\r\n"; 43 44 private final static Property BEGIN_BMSG = new Property("BEGIN", "BMSG"); 45 private final static Property END_BMSG = new Property("END", "BMSG"); 46 47 private final static Property BEGIN_VCARD = new Property("BEGIN", "VCARD"); 48 private final static Property END_VCARD = new Property("END", "VCARD"); 49 50 private final static Property BEGIN_BENV = new Property("BEGIN", "BENV"); 51 private final static Property END_BENV = new Property("END", "BENV"); 52 53 private final static Property BEGIN_BBODY = new Property("BEGIN", "BBODY"); 54 private final static Property END_BBODY = new Property("END", "BBODY"); 55 56 private final static Property BEGIN_MSG = new Property("BEGIN", "MSG"); 57 private final static Property END_MSG = new Property("END", "MSG"); 58 59 private final static int CRLF_LEN = 2; 60 61 /* 62 * length of "container" for 'message' in bmessage-body-content: 63 * BEGIN:MSG<CRLF> + <CRLF> + END:MSG<CRFL> 64 */ 65 private final static int MSG_CONTAINER_LEN = 22; 66 67 private BmsgTokenizer mParser; 68 69 private final BluetoothMapBmessage mBmsg; 70 71 private BluetoothMapBmessageParser() { 72 mBmsg = new BluetoothMapBmessage(); 73 } 74 75 static public BluetoothMapBmessage createBmessage(String str) { 76 BluetoothMapBmessageParser p = new BluetoothMapBmessageParser(); 77 78 try { 79 p.parse(str); 80 } catch (IOException e) { 81 Log.e(TAG, "I/O exception when parsing bMessage", e); 82 return null; 83 } catch (ParseException e) { 84 Log.e(TAG, "Cannot parse bMessage", e); 85 return null; 86 } 87 88 return p.mBmsg; 89 } 90 91 private ParseException expected(Property... props) { 92 boolean first = true; 93 StringBuilder sb = new StringBuilder(); 94 95 for (Property prop : props) { 96 if (!first) { 97 sb.append(" or "); 98 } 99 sb.append(prop); 100 first = false; 101 } 102 103 return new ParseException("Expected: " + sb.toString(), mParser.pos()); 104 } 105 106 private void parse(String str) throws IOException, ParseException { 107 108 Property prop; 109 110 /* 111 * <bmessage-object>::= { "BEGIN:BMSG" <CRLF> <bmessage-property> 112 * [<bmessage-originator>]* <bmessage-envelope> "END:BMSG" <CRLF> } 113 */ 114 115 mParser = new BmsgTokenizer(str + CRLF); 116 117 prop = mParser.next(); 118 if (!prop.equals(BEGIN_BMSG)) { 119 throw expected(BEGIN_BMSG); 120 } 121 122 prop = parseProperties(); 123 124 while (prop.equals(BEGIN_VCARD)) { 125 126 /* <bmessage-originator>::= <vcard> <CRLF> */ 127 128 StringBuilder vcard = new StringBuilder(); 129 prop = extractVcard(vcard); 130 131 VCardEntry entry = parseVcard(vcard.toString()); 132 mBmsg.mOriginators.add(entry); 133 } 134 135 if (!prop.equals(BEGIN_BENV)) { 136 throw expected(BEGIN_BENV); 137 } 138 139 prop = parseEnvelope(1); 140 141 if (!prop.equals(END_BMSG)) { 142 throw expected(END_BENV); 143 } 144 145 /* 146 * there should be no meaningful data left in stream here so we just 147 * ignore whatever is left 148 */ 149 150 mParser = null; 151 } 152 153 private Property parseProperties() throws ParseException { 154 155 Property prop; 156 157 /* 158 * <bmessage-property>::=<bmessage-version-property> 159 * <bmessage-readstatus-property> <bmessage-type-property> 160 * <bmessage-folder-property> <bmessage-version-property>::="VERSION:" 161 * <common-digit>*"."<common-digit>* <CRLF> 162 * <bmessage-readstatus-property>::="STATUS:" 'readstatus' <CRLF> 163 * <bmessage-type-property>::="TYPE:" 'type' <CRLF> 164 * <bmessage-folder-property>::="FOLDER:" 'foldername' <CRLF> 165 */ 166 167 do { 168 prop = mParser.next(); 169 170 if (prop.name.equals("VERSION")) { 171 mBmsg.mBmsgVersion = prop.value; 172 173 } else if (prop.name.equals("STATUS")) { 174 for (Status s : Status.values()) { 175 if (prop.value.equals(s.toString())) { 176 mBmsg.mBmsgStatus = s; 177 break; 178 } 179 } 180 181 } else if (prop.name.equals("TYPE")) { 182 for (Type t : Type.values()) { 183 if (prop.value.equals(t.toString())) { 184 mBmsg.mBmsgType = t; 185 break; 186 } 187 } 188 189 } else if (prop.name.equals("FOLDER")) { 190 mBmsg.mBmsgFolder = prop.value; 191 192 } 193 194 } while (!prop.equals(BEGIN_VCARD) && !prop.equals(BEGIN_BENV)); 195 196 return prop; 197 } 198 199 private Property parseEnvelope(int level) throws IOException, ParseException { 200 201 Property prop; 202 203 /* 204 * we can support as many nesting level as we want, but MAP spec clearly 205 * defines that there should be no more than 3 levels. so we verify it 206 * here. 207 */ 208 209 if (level > 3) { 210 throw new ParseException("bEnvelope is nested more than 3 times", mParser.pos()); 211 } 212 213 /* 214 * <bmessage-envelope> ::= { "BEGIN:BENV" <CRLF> [<bmessage-recipient>]* 215 * <bmessage-envelope> | <bmessage-content> "END:BENV" <CRLF> } 216 */ 217 218 prop = mParser.next(); 219 220 while (prop.equals(BEGIN_VCARD)) { 221 222 /* <bmessage-originator>::= <vcard> <CRLF> */ 223 224 StringBuilder vcard = new StringBuilder(); 225 prop = extractVcard(vcard); 226 227 if (level == 1) { 228 VCardEntry entry = parseVcard(vcard.toString()); 229 mBmsg.mRecipients.add(entry); 230 } 231 } 232 233 if (prop.equals(BEGIN_BENV)) { 234 prop = parseEnvelope(level + 1); 235 236 } else if (prop.equals(BEGIN_BBODY)) { 237 prop = parseBody(); 238 239 } else { 240 throw expected(BEGIN_BENV, BEGIN_BBODY); 241 } 242 243 if (!prop.equals(END_BENV)) { 244 throw expected(END_BENV); 245 } 246 247 return mParser.next(); 248 } 249 250 private Property parseBody() throws IOException, ParseException { 251 252 Property prop; 253 254 /* 255 * <bmessage-content>::= { "BEGIN:BBODY"<CRLF> [<bmessage-body-part-ID> 256 * <CRLF>] <bmessage-body-property> <bmessage-body-content>* <CRLF> 257 * "END:BBODY"<CRLF> } <bmessage-body-part-ID>::="PARTID:" 'Part-ID' 258 * <bmessage-body-property>::=[<bmessage-body-encoding-property>] 259 * [<bmessage-body-charset-property>] 260 * [<bmessage-body-language-property>] 261 * <bmessage-body-content-length-property> 262 * <bmessage-body-encoding-property>::="ENCODING:"'encoding' <CRLF> 263 * <bmessage-body-charset-property>::="CHARSET:"'charset' <CRLF> 264 * <bmessage-body-language-property>::="LANGUAGE:"'language' <CRLF> 265 * <bmessage-body-content-length-property>::= "LENGTH:" <common-digit>* 266 * <CRLF> 267 */ 268 269 do { 270 prop = mParser.next(); 271 272 if (prop.name.equals("PARTID")) { 273 } else if (prop.name.equals("ENCODING")) { 274 mBmsg.mBbodyEncoding = prop.value; 275 276 } else if (prop.name.equals("CHARSET")) { 277 mBmsg.mBbodyCharset = prop.value; 278 279 } else if (prop.name.equals("LANGUAGE")) { 280 mBmsg.mBbodyLanguage = prop.value; 281 282 } else if (prop.name.equals("LENGTH")) { 283 try { 284 mBmsg.mBbodyLength = Integer.valueOf(prop.value); 285 } catch (NumberFormatException e) { 286 throw new ParseException("Invalid LENGTH value", mParser.pos()); 287 } 288 289 } 290 291 } while (!prop.equals(BEGIN_MSG)); 292 293 /* 294 * <bmessage-body-content>::={ "BEGIN:MSG"<CRLF> 'message'<CRLF> 295 * "END:MSG"<CRLF> } 296 */ 297 298 int messageLen = mBmsg.mBbodyLength - MSG_CONTAINER_LEN; 299 int offset = messageLen + CRLF_LEN; 300 int restartPos = mParser.pos() + offset; 301 302 /* 303 * length is specified in bytes so we need to convert from unicode 304 * string back to bytes array 305 */ 306 307 String remng = mParser.remaining(); 308 byte[] data = remng.getBytes(); 309 310 /* restart parsing from after 'message'<CRLF> */ 311 mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos); 312 313 prop = mParser.next(true); 314 315 if (prop != null) { 316 if (prop.equals(END_MSG)) { 317 mBmsg.mMessage = new String(data, 0, messageLen); 318 } else { 319 /* Handle possible exception for incorrect LENGTH value 320 * from MSE while parsing GET Message response */ 321 Log.e(TAG, "Prop Invalid: "+ prop.toString()); 322 Log.e(TAG, "Possible Invalid LENGTH value"); 323 throw expected(END_MSG); 324 } 325 } else { 326 327 data = null; 328 329 /* 330 * now we check if bMessage can be parsed if LENGTH is handled as 331 * number of characters instead of number of bytes 332 */ 333 if (offset < 0 || offset > remng.length()) { 334 /* Handle possible exception for incorrect LENGTH value 335 * from MSE while parsing GET Message response */ 336 throw new ParseException("Invalid LENGTH value", mParser.pos()); 337 } 338 339 Log.w(TAG, "byte LENGTH seems to be invalid, trying with char length"); 340 341 mParser = new BmsgTokenizer(remng.substring(offset)); 342 343 prop = mParser.next(); 344 345 if (!prop.equals(END_MSG)) { 346 throw expected(END_MSG); 347 } 348 349 mBmsg.mMessage = remng.substring(0, messageLen); 350 } 351 352 prop = mParser.next(); 353 354 if (!prop.equals(END_BBODY)) { 355 throw expected(END_BBODY); 356 } 357 358 return mParser.next(); 359 } 360 361 private Property extractVcard(StringBuilder out) throws IOException, ParseException { 362 Property prop; 363 364 out.append(BEGIN_VCARD).append(CRLF); 365 366 do { 367 prop = mParser.next(); 368 out.append(prop).append(CRLF); 369 } while (!prop.equals(END_VCARD)); 370 371 return mParser.next(); 372 } 373 374 private class VcardHandler implements VCardEntryHandler { 375 376 VCardEntry vcard; 377 378 @Override 379 public void onStart() { 380 } 381 382 @Override 383 public void onEntryCreated(VCardEntry entry) { 384 vcard = entry; 385 } 386 387 @Override 388 public void onEnd() { 389 } 390 }; 391 392 private VCardEntry parseVcard(String str) throws IOException, ParseException { 393 VCardEntry vcard = null; 394 395 try { 396 VCardParser p = new VCardParser_V21(); 397 VCardEntryConstructor c = new VCardEntryConstructor(); 398 VcardHandler handler = new VcardHandler(); 399 c.addEntryHandler(handler); 400 p.addInterpreter(c); 401 p.parse(new ByteArrayInputStream(str.getBytes())); 402 403 vcard = handler.vcard; 404 405 } catch (VCardVersionException e1) { 406 407 try { 408 VCardParser p = new VCardParser_V30(); 409 VCardEntryConstructor c = new VCardEntryConstructor(); 410 VcardHandler handler = new VcardHandler(); 411 c.addEntryHandler(handler); 412 p.addInterpreter(c); 413 p.parse(new ByteArrayInputStream(str.getBytes())); 414 415 vcard = handler.vcard; 416 417 } catch (VCardVersionException e2) { 418 // will throw below 419 } catch (VCardException e2) { 420 // will throw below 421 } 422 423 } catch (VCardException e1) { 424 // will throw below 425 } 426 427 if (vcard == null) { 428 throw new ParseException("Cannot parse vCard object (neither 2.1 nor 3.0?)", 429 mParser.pos()); 430 } 431 432 return vcard; 433 } 434} 435