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