Home | History | Annotate | Download | only in nfc
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "device/nfc/nfc_ndef_record.h"
      6 
      7 #include <map>
      8 
      9 #include "base/logging.h"
     10 #include "url/gurl.h"
     11 
     12 using base::DictionaryValue;
     13 using base::ListValue;
     14 
     15 namespace device {
     16 
     17 namespace {
     18 
     19 typedef std::map<std::string, base::Value::Type> FieldValueMap;
     20 
     21 bool ValidateURI(const DictionaryValue* data) {
     22   std::string uri;
     23   if (!data->GetString(NfcNdefRecord::kFieldURI, &uri)) {
     24     VLOG(1) << "No URI entry in data.";
     25     return false;
     26   }
     27   DCHECK(!uri.empty());
     28 
     29   // Use GURL to check validity.
     30   GURL url(uri);
     31   if (!url.is_valid()) {
     32     LOG(ERROR) << "Invalid URI given: " << uri;
     33     return false;
     34   }
     35   return true;
     36 }
     37 
     38 bool CheckFieldsAreValid(
     39     const FieldValueMap& required_fields,
     40     const FieldValueMap& optional_fields,
     41     const DictionaryValue* data) {
     42   size_t required_count = 0;
     43   for (DictionaryValue::Iterator iter(*data);
     44        !iter.IsAtEnd(); iter.Advance()) {
     45     FieldValueMap::const_iterator field_iter =
     46         required_fields.find(iter.key());
     47     if (field_iter == required_fields.end()) {
     48       // Field wasn't one of the required fields. Check if optional.
     49       field_iter = optional_fields.find(iter.key());
     50 
     51       if (field_iter == optional_fields.end()) {
     52         // If the field isn't one of the optional fields either, then it's
     53         // invalid.
     54         VLOG(1) << "Tried to populate record with invalid field: "
     55                 << iter.key();
     56         return false;
     57       }
     58     } else {
     59       required_count++;
     60     }
     61     // The field is invalid, if the type of its value is incorrect.
     62     if (field_iter->second != iter.value().GetType()) {
     63       VLOG(1) << "Provided value for field \"" << iter.key() << "\" has type "
     64               << iter.value().GetType() << ", expected: "
     65               << field_iter->second;
     66       return false;
     67     }
     68     // Make sure that the value is non-empty, if the value is a string.
     69     std::string string_value;
     70     if (iter.value().GetAsString(&string_value) && string_value.empty()) {
     71       VLOG(1) << "Empty value given for field of type string: " << iter.key();
     72       return false;
     73     }
     74   }
     75   // Check for required fields.
     76   if (required_count != required_fields.size()) {
     77     VLOG(1) << "Provided data did not contain all required fields for "
     78             << "requested NDEF type.";
     79     return false;
     80   }
     81   return true;
     82 }
     83 
     84 // Verifies that the contents of |data| conform to the fields of NDEF type
     85 // "Text".
     86 bool HandleTypeText(const DictionaryValue* data) {
     87   VLOG(1) << "Populating record with type \"Text\".";
     88   FieldValueMap required_fields;
     89   required_fields[NfcNdefRecord::kFieldText] = base::Value::TYPE_STRING;
     90   required_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING;
     91   required_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING;
     92   FieldValueMap optional_fields;
     93   if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
     94     VLOG(1) << "Failed to populate record.";
     95     return false;
     96   }
     97 
     98   // Verify that the "Encoding" property has valid values.
     99   std::string encoding;
    100   if (!data->GetString(NfcNdefRecord::kFieldEncoding, &encoding)) {
    101     if (encoding != NfcNdefRecord::kEncodingUtf8 ||
    102         encoding != NfcNdefRecord::kEncodingUtf16) {
    103       VLOG(1) << "Invalid \"Encoding\" value:" << encoding;
    104       return false;
    105     }
    106   }
    107   return true;
    108 }
    109 
    110 // Verifies that the contents of |data| conform to the fields of NDEF type
    111 // "SmartPoster".
    112 bool HandleTypeSmartPoster(const DictionaryValue* data) {
    113   VLOG(1) << "Populating record with type \"SmartPoster\".";
    114   FieldValueMap required_fields;
    115   required_fields[NfcNdefRecord::kFieldURI] = base::Value::TYPE_STRING;
    116   FieldValueMap optional_fields;
    117   optional_fields[NfcNdefRecord::kFieldAction] = base::Value::TYPE_STRING;
    118   optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING;
    119   // base::Value restricts the number types to BOOL, INTEGER, and DOUBLE only.
    120   // uint32 will automatically get converted to a double. "target size" is
    121   // really a uint32 but we define it as a double for this reason.
    122   // (See dbus/values_util.h).
    123   optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE;
    124   optional_fields[NfcNdefRecord::kFieldTitles] = base::Value::TYPE_LIST;
    125   if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
    126     VLOG(1) << "Failed to populate record.";
    127     return false;
    128   }
    129   // Verify that the "titles" field was formatted correctly, if it exists.
    130   const ListValue* titles = NULL;
    131   if (data->GetList(NfcNdefRecord::kFieldTitles, &titles)) {
    132     if (titles->empty()) {
    133       VLOG(1) << "\"titles\" field of SmartPoster is empty.";
    134       return false;
    135     }
    136     for (ListValue::const_iterator iter = titles->begin();
    137          iter != titles->end(); ++iter) {
    138       const DictionaryValue* title_data = NULL;
    139       if (!(*iter)->GetAsDictionary(&title_data)) {
    140         VLOG(1) << "\"title\" entry for SmartPoster contains an invalid value "
    141                 << "type";
    142         return false;
    143       }
    144       if (!HandleTypeText(title_data)) {
    145         VLOG(1) << "Badly formatted \"title\" entry for SmartPoster.";
    146         return false;
    147       }
    148     }
    149   }
    150   return ValidateURI(data);
    151 }
    152 
    153 // Verifies that the contents of |data| conform to the fields of NDEF type
    154 // "URI".
    155 bool HandleTypeUri(const DictionaryValue* data) {
    156   VLOG(1) << "Populating record with type \"URI\".";
    157   FieldValueMap required_fields;
    158   required_fields[NfcNdefRecord::kFieldURI] = base::Value::TYPE_STRING;
    159   FieldValueMap optional_fields;
    160   optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING;
    161   optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE;
    162 
    163   // Allow passing TargetSize as an integer, but convert it to a double.
    164   if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
    165     VLOG(1) << "Failed to populate record.";
    166     return false;
    167   }
    168   return ValidateURI(data);
    169 }
    170 
    171 }  // namespace
    172 
    173 // static
    174 const char NfcNdefRecord::kFieldEncoding[] = "encoding";
    175 // static
    176 const char NfcNdefRecord::kFieldLanguageCode[] = "languageCode";
    177 // static
    178 const char NfcNdefRecord::kFieldText[] = "text";
    179 // static
    180 const char NfcNdefRecord::kFieldURI[] = "uri";
    181 // static
    182 const char NfcNdefRecord::kFieldMimeType[] = "mimeType";
    183 // static
    184 const char NfcNdefRecord::kFieldTargetSize[] = "targetSize";
    185 // static
    186 const char NfcNdefRecord::kFieldTitles[] = "titles";
    187 // static
    188 const char NfcNdefRecord::kFieldAction[] = "action";
    189 // static
    190 const char NfcNdefRecord::kEncodingUtf8[] = "UTF-8";
    191 // static
    192 const char NfcNdefRecord::kEncodingUtf16[] = "UTF-16";
    193 // static
    194 const char NfcNdefRecord::kSmartPosterActionDo[] = "do";
    195 // static
    196 const char NfcNdefRecord::kSmartPosterActionSave[] = "save";
    197 // static
    198 const char NfcNdefRecord::kSmartPosterActionOpen[] = "open";
    199 
    200 NfcNdefRecord::NfcNdefRecord() : type_(kTypeUnknown) {
    201 }
    202 
    203 NfcNdefRecord::~NfcNdefRecord() {
    204 }
    205 
    206 bool NfcNdefRecord::IsPopulated() const {
    207   return type_ != kTypeUnknown;
    208 }
    209 
    210 bool NfcNdefRecord::Populate(Type type, const DictionaryValue* data) {
    211   if (IsPopulated())
    212     return false;
    213 
    214   DCHECK(data_.empty());
    215 
    216   // At this time, only "Text", "URI", and "SmartPoster" are supported.
    217   bool result = false;
    218   switch (type) {
    219     case kTypeText:
    220       result = HandleTypeText(data);
    221       break;
    222     case kTypeSmartPoster:
    223       result = HandleTypeSmartPoster(data);
    224       break;
    225     case kTypeURI:
    226       result = HandleTypeUri(data);
    227       break;
    228     default:
    229       VLOG(1) << "Unsupported NDEF type: " << type;
    230       break;
    231   }
    232   if (!result)
    233     return false;
    234   type_ = type;
    235   data_.MergeDictionary(data);
    236   return true;
    237 }
    238 
    239 NfcNdefMessage::NfcNdefMessage() {
    240 }
    241 
    242 NfcNdefMessage::~NfcNdefMessage() {
    243 }
    244 
    245 void NfcNdefMessage::AddRecord(NfcNdefRecord* record) {
    246   records_.push_back(record);
    247 }
    248 
    249 bool NfcNdefMessage::RemoveRecord(NfcNdefRecord* record) {
    250   for (RecordList::iterator iter = records_.begin();
    251        iter != records_.end(); ++iter) {
    252     if (*iter == record) {
    253       records_.erase(iter);
    254       return true;
    255     }
    256   }
    257   return false;
    258 }
    259 
    260 }  // namespace device
    261