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
12using base::DictionaryValue;
13using base::ListValue;
14
15namespace device {
16
17namespace {
18
19typedef std::map<std::string, base::Value::Type> FieldValueMap;
20
21bool 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
38bool 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".
86bool 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".
112bool 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".
155bool 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
174const char NfcNdefRecord::kFieldEncoding[] = "encoding";
175// static
176const char NfcNdefRecord::kFieldLanguageCode[] = "languageCode";
177// static
178const char NfcNdefRecord::kFieldText[] = "text";
179// static
180const char NfcNdefRecord::kFieldURI[] = "uri";
181// static
182const char NfcNdefRecord::kFieldMimeType[] = "mimeType";
183// static
184const char NfcNdefRecord::kFieldTargetSize[] = "targetSize";
185// static
186const char NfcNdefRecord::kFieldTitles[] = "titles";
187// static
188const char NfcNdefRecord::kFieldAction[] = "action";
189// static
190const char NfcNdefRecord::kEncodingUtf8[] = "UTF-8";
191// static
192const char NfcNdefRecord::kEncodingUtf16[] = "UTF-16";
193// static
194const char NfcNdefRecord::kSmartPosterActionDo[] = "do";
195// static
196const char NfcNdefRecord::kSmartPosterActionSave[] = "save";
197// static
198const char NfcNdefRecord::kSmartPosterActionOpen[] = "open";
199
200NfcNdefRecord::NfcNdefRecord() : type_(kTypeUnknown) {
201}
202
203NfcNdefRecord::~NfcNdefRecord() {
204}
205
206bool NfcNdefRecord::IsPopulated() const {
207  return type_ != kTypeUnknown;
208}
209
210bool 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
239NfcNdefMessage::NfcNdefMessage() {
240}
241
242NfcNdefMessage::~NfcNdefMessage() {
243}
244
245void NfcNdefMessage::AddRecord(NfcNdefRecord* record) {
246  records_.push_back(record);
247}
248
249bool 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