Home | History | Annotate | Download | only in map
      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 
     17 package android.bluetooth.client.map;
     18 
     19 import android.util.Log;
     20 
     21 import com.android.vcard.VCardEntry;
     22 import com.android.vcard.VCardEntryConstructor;
     23 import com.android.vcard.VCardEntryHandler;
     24 import com.android.vcard.VCardParser;
     25 import com.android.vcard.VCardParser_V21;
     26 import com.android.vcard.VCardParser_V30;
     27 import com.android.vcard.exception.VCardException;
     28 import com.android.vcard.exception.VCardVersionException;
     29 import android.bluetooth.client.map.BluetoothMapBmessage.Status;
     30 import android.bluetooth.client.map.BluetoothMapBmessage.Type;
     31 import android.bluetooth.client.map.utils.BmsgTokenizer;
     32 import android.bluetooth.client.map.utils.BmsgTokenizer.Property;
     33 
     34 import java.io.ByteArrayInputStream;
     35 import java.io.IOException;
     36 import java.text.ParseException;
     37 
     38 class 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