Commit 5ab5d1c1 authored by Leon Han's avatar Leon Han Committed by Commit Bot

[webnfc] Implement the algo for validating external types

The spec change:
https://github.com/w3c/web-nfc/pull/474/

BUG=520391

Change-Id: If1e4a6513410f3705bec4da7b2cc514c209a3cd9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1978622
Commit-Queue: Leon Han <leon.han@intel.com>
Reviewed-by: default avatarRijubrata Bhaumik <rijubrata.bhaumik@intel.com>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarFrançois Beaufort <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/master@{#727853}
parent 4f8b5432
...@@ -11,6 +11,7 @@ import android.nfc.FormatException; ...@@ -11,6 +11,7 @@ import android.nfc.FormatException;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.device.mojom.NdefMessage; import org.chromium.device.mojom.NdefMessage;
import org.chromium.device.mojom.NdefRecord; import org.chromium.device.mojom.NdefRecord;
import org.chromium.device.mojom.NdefRecordTypeCategory;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -80,16 +81,6 @@ public final class NdefMessageUtils { ...@@ -80,16 +81,6 @@ public final class NdefMessageUtils {
"urn:nfc:", // 0x23 "urn:nfc:", // 0x23
}; };
private static class PairOfDomainAndType {
private String mDomain;
private String mType;
private PairOfDomainAndType(String domain, String type) {
mDomain = domain;
mType = type;
}
}
/** /**
* Converts mojo NdefMessage to android.nfc.NdefMessage * Converts mojo NdefMessage to android.nfc.NdefMessage
*/ */
...@@ -136,6 +127,7 @@ public final class NdefMessageUtils { ...@@ -136,6 +127,7 @@ public final class NdefMessageUtils {
private static android.nfc.NdefRecord toNdefRecord(NdefRecord record) private static android.nfc.NdefRecord toNdefRecord(NdefRecord record)
throws InvalidNdefMessageException, IllegalArgumentException, throws InvalidNdefMessageException, IllegalArgumentException,
UnsupportedEncodingException { UnsupportedEncodingException {
if (record.category == NdefRecordTypeCategory.STANDARDIZED) {
switch (record.recordType) { switch (record.recordType) {
case RECORD_TYPE_URL: case RECORD_TYPE_URL:
return createPlatformUrlRecord(record.data, record.id, false /* isAbsUrl */); return createPlatformUrlRecord(record.data, record.id, false /* isAbsUrl */);
...@@ -149,20 +141,29 @@ public final class NdefMessageUtils { ...@@ -149,20 +141,29 @@ public final class NdefMessageUtils {
case RECORD_TYPE_UNKNOWN: case RECORD_TYPE_UNKNOWN:
return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_UNKNOWN, return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_UNKNOWN,
null /* type */, null /* type */,
record.id == null ? null : ApiCompatibilityUtils.getBytesUtf8(record.id), record.id == null ? null
: ApiCompatibilityUtils.getBytesUtf8(record.id),
record.data); record.data);
case RECORD_TYPE_EMPTY: case RECORD_TYPE_EMPTY:
return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_EMPTY, null /* type */, return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_EMPTY,
null /* id */, null /* payload */); null /* type */, null /* id */, null /* payload */);
case RECORD_TYPE_SMART_POSTER: case RECORD_TYPE_SMART_POSTER:
// TODO(https://crbug.com/520391): Support 'smart-poster' type records. // TODO(https://crbug.com/520391): Support 'smart-poster' type records.
throw new InvalidNdefMessageException(); throw new InvalidNdefMessageException();
} }
// TODO(https://crbug.com/520391): Need to create an external record for either a custom throw new InvalidNdefMessageException();
// type name or a local type name (for an embedded record). }
PairOfDomainAndType pair = parseDomainAndType(record.recordType);
if (pair != null) { if (record.category == NdefRecordTypeCategory.EXTERNAL) {
return createPlatformExternalRecord(pair.mDomain, pair.mType, record.id, record.data); if (isValidExternalType(record.recordType)) {
return createPlatformExternalRecord(record.recordType, record.id, record.data);
}
throw new InvalidNdefMessageException();
}
if (record.category == NdefRecordTypeCategory.LOCAL) {
// TODO(https://crbug.com/520391): Support local type records.
throw new InvalidNdefMessageException();
} }
throw new InvalidNdefMessageException(); throw new InvalidNdefMessageException();
...@@ -215,6 +216,7 @@ public final class NdefMessageUtils { ...@@ -215,6 +216,7 @@ public final class NdefMessageUtils {
*/ */
private static NdefRecord createEmptyRecord() { private static NdefRecord createEmptyRecord() {
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.STANDARDIZED;
nfcRecord.recordType = RECORD_TYPE_EMPTY; nfcRecord.recordType = RECORD_TYPE_EMPTY;
nfcRecord.data = new byte[0]; nfcRecord.data = new byte[0];
return nfcRecord; return nfcRecord;
...@@ -226,6 +228,7 @@ public final class NdefMessageUtils { ...@@ -226,6 +228,7 @@ public final class NdefMessageUtils {
private static NdefRecord createURLRecord(Uri uri, boolean isAbsUrl) { private static NdefRecord createURLRecord(Uri uri, boolean isAbsUrl) {
if (uri == null) return null; if (uri == null) return null;
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.STANDARDIZED;
if (isAbsUrl) { if (isAbsUrl) {
nfcRecord.recordType = RECORD_TYPE_ABSOLUTE_URL; nfcRecord.recordType = RECORD_TYPE_ABSOLUTE_URL;
} else { } else {
...@@ -241,6 +244,7 @@ public final class NdefMessageUtils { ...@@ -241,6 +244,7 @@ public final class NdefMessageUtils {
*/ */
private static NdefRecord createMIMERecord(String mediaType, byte[] payload) { private static NdefRecord createMIMERecord(String mediaType, byte[] payload) {
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.STANDARDIZED;
nfcRecord.recordType = RECORD_TYPE_MIME; nfcRecord.recordType = RECORD_TYPE_MIME;
nfcRecord.mediaType = mediaType; nfcRecord.mediaType = mediaType;
nfcRecord.data = payload; nfcRecord.data = payload;
...@@ -257,6 +261,7 @@ public final class NdefMessageUtils { ...@@ -257,6 +261,7 @@ public final class NdefMessageUtils {
} }
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.STANDARDIZED;
nfcRecord.recordType = RECORD_TYPE_TEXT; nfcRecord.recordType = RECORD_TYPE_TEXT;
// According to NFCForum-TS-RTD_Text_1.0 specification, section 3.2.1 Syntax. // According to NFCForum-TS-RTD_Text_1.0 specification, section 3.2.1 Syntax.
// First byte of the payload is status byte, defined in Table 3: Status Byte Encodings. // First byte of the payload is status byte, defined in Table 3: Status Byte Encodings.
...@@ -289,6 +294,8 @@ public final class NdefMessageUtils { ...@@ -289,6 +294,8 @@ public final class NdefMessageUtils {
// TODO(https://crbug.com/520391): Support RTD_SMART_POSTER type records. // TODO(https://crbug.com/520391): Support RTD_SMART_POSTER type records.
// TODO(https://crbug.com/520391): Support local type records.
return null; return null;
} }
...@@ -297,6 +304,7 @@ public final class NdefMessageUtils { ...@@ -297,6 +304,7 @@ public final class NdefMessageUtils {
*/ */
private static NdefRecord createUnknownRecord(byte[] payload) { private static NdefRecord createUnknownRecord(byte[] payload) {
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.STANDARDIZED;
nfcRecord.recordType = RECORD_TYPE_UNKNOWN; nfcRecord.recordType = RECORD_TYPE_UNKNOWN;
nfcRecord.data = payload; nfcRecord.data = payload;
return nfcRecord; return nfcRecord;
...@@ -306,8 +314,8 @@ public final class NdefMessageUtils { ...@@ -306,8 +314,8 @@ public final class NdefMessageUtils {
* Constructs External type NdefRecord * Constructs External type NdefRecord
*/ */
private static NdefRecord createExternalTypeRecord(String type, byte[] payload) { private static NdefRecord createExternalTypeRecord(String type, byte[] payload) {
// |type| may be a custom type name or a local type name (for an embedded record).
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.EXTERNAL;
nfcRecord.recordType = type; nfcRecord.recordType = type;
nfcRecord.data = payload; nfcRecord.data = payload;
nfcRecord.payloadMessage = getNdefMessageFromPayload(payload); nfcRecord.payloadMessage = getNdefMessageFromPayload(payload);
...@@ -411,45 +419,39 @@ public final class NdefMessageUtils { ...@@ -411,45 +419,39 @@ public final class NdefMessageUtils {
* Creates a TNF_EXTERNAL_TYPE android.nfc.NdefRecord. * Creates a TNF_EXTERNAL_TYPE android.nfc.NdefRecord.
*/ */
public static android.nfc.NdefRecord createPlatformExternalRecord( public static android.nfc.NdefRecord createPlatformExternalRecord(
String domain, String type, String id, byte[] payload) { String recordType, String id, byte[] payload) {
// Already guaranteed by parseDomainAndType(). // Already guaranteed by the caller.
assert domain != null && !domain.isEmpty(); assert recordType != null && !recordType.isEmpty();
assert type != null && !type.isEmpty();
// NFC Forum requires that the domain and type used in an external record are treated as // NFC Forum requires that the domain and type used in an external record are treated as
// case insensitive, however Android intent filtering is always case sensitive. So we force // case insensitive, however Android intent filtering is always case sensitive. So we force
// the domain and type to lower-case here and later we will compare in a case insensitive // the domain and type to lower-case here and later we will compare in a case insensitive
// way when filtering by them. // way when filtering by them.
String record_type = domain.toLowerCase(Locale.ROOT) + ':' + type.toLowerCase(Locale.ROOT);
return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_EXTERNAL_TYPE, return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_EXTERNAL_TYPE,
ApiCompatibilityUtils.getBytesUtf8(record_type), ApiCompatibilityUtils.getBytesUtf8(recordType.toLowerCase(Locale.ROOT)),
id == null ? null : ApiCompatibilityUtils.getBytesUtf8(id), payload); id == null ? null : ApiCompatibilityUtils.getBytesUtf8(id), payload);
} }
/** /**
* Parses the input custom type to get its domain and type. * Validates external types.
* e.g. returns a pair ('w3.org', 'xyz') for the input 'w3.org:xyz'. * https://w3c.github.io/web-nfc/#dfn-validate-external-type
* Returns null for invalid input.
* https://w3c.github.io/web-nfc/#ndef-record-types
*
* TODO(https://crbug.com/520391): Refine the validation algorithm here accordingly once there
* is a conclusion on some case-sensitive things at https://github.com/w3c/web-nfc/issues/331.
*/ */
private static PairOfDomainAndType parseDomainAndType(String customType) { private static boolean isValidExternalType(String input) {
int colonIndex = customType.indexOf(':'); if (input.isEmpty() || input.length() > 255) return false;
if (colonIndex == -1) return null;
int colonIndex = input.lastIndexOf(':');
if (colonIndex == -1) return false;
// TODO(ThisCL): verify |domain| is a valid FQDN, asking help at String domain = input.substring(0, colonIndex).trim();
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/QN2mHt_WgHo. if (domain.isEmpty()) return false;
String domain = customType.substring(0, colonIndex).trim(); // TODO(https://crbug.com/520391): Make sure |domain| can be converted successfully to ASCII
if (domain.isEmpty()) return null; // using IDN rules and does not contain any forbidden host code point.
String type = customType.substring(colonIndex + 1).trim(); String type = input.substring(colonIndex + 1).trim();
if (type.isEmpty()) return null; if (type.isEmpty()) return false;
if (!type.matches("[a-zA-Z0-9()+,\\-:=@;$_!*'.]+")) return null; if (!type.matches("[a-zA-Z0-9()+,\\-=@;$_*'.]+")) return false;
return new PairOfDomainAndType(domain, type); return true;
} }
/** /**
......
...@@ -29,13 +29,36 @@ enum NDEFPushTarget { ...@@ -29,13 +29,36 @@ enum NDEFPushTarget {
ANY ANY
}; };
// https://w3c.github.io/web-nfc/#the-record-type-string
enum NDEFRecordTypeCategory {
// Standardized well known types, including "empty", "text", "url",
// "smart-poster", "absolute-url", "mime", and "unknown", etc.
// https://w3c.github.io/web-nfc/#dfn-well-known-type-name
kStandardized,
// External types that follow the rule defined by
// https://w3c.github.io/web-nfc/#dfn-external-type-name.
kExternal,
// Local types that follow the rule defined by
// https://w3c.github.io/web-nfc/#dfn-local-type-name.
kLocal
};
struct NDEFError { struct NDEFError {
NDEFErrorType error_type; NDEFErrorType error_type;
}; };
// https://w3c.github.io/web-nfc/#dom-ndefrecord // https://w3c.github.io/web-nfc/#dom-ndefrecord
struct NDEFRecord { struct NDEFRecord {
// The category |record_type| belongs to. This field is not exposed to JS, but
// is used to transfer information internally and make code more readable,
// given that there're some complex logic around external types and local
// types (will be supported in the future).
NDEFRecordTypeCategory category;
// The type of NDEFRecord. // The type of NDEFRecord.
// https://w3c.github.io/web-nfc/#the-record-type-string
string record_type; string record_type;
// Represents the IANA media type of the NDEFRecord data field. // Represents the IANA media type of the NDEFRecord data field.
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h" #include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h" #include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
...@@ -70,43 +69,39 @@ bool GetBytesOfBufferSource(const NDEFRecordDataSource& buffer_source, ...@@ -70,43 +69,39 @@ bool GetBytesOfBufferSource(const NDEFRecordDataSource& buffer_source,
return true; return true;
} }
// https://w3c.github.io/web-nfc/#ndef-record-types // https://w3c.github.io/web-nfc/#dfn-validate-external-type
// Derives a formatted custom type for the external type record from |input|. // Validates |input| as an external type.
// Returns a null string for an invalid |input|. bool IsValidExternalType(const String& input) {
// static const String kOtherCharsForCustomType("()+,-=@;$_*'.");
// TODO(https://crbug.com/520391): Refine the validation algorithm here
// accordingly once there is a conclusion on some case-sensitive things at if (input.IsEmpty() || input.length() > 255)
// https://github.com/w3c/web-nfc/issues/331. return false;
String ValidateCustomRecordType(const String& input) {
static const String kOtherCharsForCustomType("()+,-:=@;$_!*'."); // Finds the last occurrence of ':'.
wtf_size_t colon_index = input.ReverseFind(':');
if (input.IsEmpty())
return String();
// Finds the separator ':'.
wtf_size_t colon_index = input.find(':');
if (colon_index == kNotFound) if (colon_index == kNotFound)
return String(); return false;
// Derives the domain (FQDN) from the part before ':'. // Validates the domain (the part before ':').
String left = input.Left(colon_index); String domain = input.Left(colon_index);
bool success = false; if (domain.IsEmpty())
String domain = SecurityOrigin::CanonicalizeHost(left, &success); return false;
if (!success || domain.IsEmpty()) // TODO(https://crbug.com/520391): Make sure |domain| can be converted
return String(); // successfully to ASCII using IDN rules and does not contain any forbidden
// host code point.
// Validates the part after ':'.
String right = input.Substring(colon_index + 1); // Validates the type (the part after ':').
if (right.length() == 0) String type = input.Substring(colon_index + 1);
return String(); if (type.IsEmpty())
for (wtf_size_t i = 0; i < right.length(); i++) { return false;
if (!IsASCIIAlphanumeric(right[i]) && for (wtf_size_t i = 0; i < type.length(); i++) {
!kOtherCharsForCustomType.Contains(right[i])) { if (!IsASCIIAlphanumeric(type[i]) &&
return String(); !kOtherCharsForCustomType.Contains(type[i])) {
return false;
} }
} }
return domain + ':' + right; return true;
} }
String getDocumentLanguage(const ExecutionContext* execution_context) { String getDocumentLanguage(const ExecutionContext* execution_context) {
...@@ -245,7 +240,9 @@ static NDEFRecord* CreateExternalRecord(const String& custom_type, ...@@ -245,7 +240,9 @@ static NDEFRecord* CreateExternalRecord(const String& custom_type,
if (!GetBytesOfBufferSource(data, &bytes, exception_state)) { if (!GetBytesOfBufferSource(data, &bytes, exception_state)) {
return nullptr; return nullptr;
} }
return MakeGarbageCollected<NDEFRecord>(custom_type, bytes); NDEFRecord* record = MakeGarbageCollected<NDEFRecord>(
device::mojom::NDEFRecordTypeCategory::kExternal, custom_type, bytes);
return record;
} }
} // namespace } // namespace
...@@ -303,20 +300,13 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context, ...@@ -303,20 +300,13 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
// TODO(https://crbug.com/520391): Support creating smart-poster records. // TODO(https://crbug.com/520391): Support creating smart-poster records.
exception_state.ThrowTypeError("smart-poster type is not supported yet"); exception_state.ThrowTypeError("smart-poster type is not supported yet");
return nullptr; return nullptr;
} else if (IsValidExternalType(record_type)) {
instance = CreateExternalRecord(record_type, init->data(), exception_state);
} else { } else {
// TODO(https://crbug.com/520391): Here |record_type| may be a custom type // TODO(https://crbug.com/520391): Support local type records.
// name for an external type record, or a local type name for an external
// type record embedded within a ndef message as payload of a parent record,
// in either case we should try to create an external type record from
// |data|.
String formated_type = ValidateCustomRecordType(record_type);
if (formated_type.IsNull()) {
exception_state.ThrowTypeError("Invalid NDEFRecord type."); exception_state.ThrowTypeError("Invalid NDEFRecord type.");
return nullptr; return nullptr;
} }
instance =
CreateExternalRecord(formated_type, init->data(), exception_state);
}
if (instance && init->hasId()) { if (instance && init->hasId()) {
instance->id_ = init->id(); instance->id_ = init->id();
...@@ -324,30 +314,44 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context, ...@@ -324,30 +314,44 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
return instance; return instance;
} }
NDEFRecord::NDEFRecord(const String& record_type, NDEFRecord::NDEFRecord(const String& record_type, WTF::Vector<uint8_t> data)
WTF::Vector<uint8_t> data) : category_(device::mojom::NDEFRecordTypeCategory::kStandardized),
: record_type_(record_type), record_type_(record_type),
payload_data_(std::move(data)) {} payload_data_(std::move(data)) {}
NDEFRecord::NDEFRecord(const String& record_type, NDEFRecord::NDEFRecord(const String& record_type,
const String& encoding, const String& encoding,
const String& lang, const String& lang,
WTF::Vector<uint8_t> data) WTF::Vector<uint8_t> data)
: record_type_(record_type), : category_(device::mojom::NDEFRecordTypeCategory::kStandardized),
record_type_(record_type),
encoding_(encoding), encoding_(encoding),
lang_(lang), lang_(lang),
payload_data_(std::move(data)) {} payload_data_(std::move(data)) {}
NDEFRecord::NDEFRecord(device::mojom::NDEFRecordTypeCategory category,
const String& record_type,
WTF::Vector<uint8_t> data)
: category_(category),
record_type_(record_type),
payload_data_(std::move(data)) {
DCHECK_EQ(category_ == device::mojom::NDEFRecordTypeCategory::kExternal,
IsValidExternalType(record_type_));
}
NDEFRecord::NDEFRecord(const ExecutionContext* execution_context, NDEFRecord::NDEFRecord(const ExecutionContext* execution_context,
const String& text) const String& text)
: record_type_("text"), : category_(device::mojom::NDEFRecordTypeCategory::kStandardized),
record_type_("text"),
encoding_("utf-8"), encoding_("utf-8"),
lang_(getDocumentLanguage(execution_context)), lang_(getDocumentLanguage(execution_context)),
payload_data_(GetUTF8DataFromString(text)) {} payload_data_(GetUTF8DataFromString(text)) {}
NDEFRecord::NDEFRecord(WTF::Vector<uint8_t> payload_data, NDEFRecord::NDEFRecord(WTF::Vector<uint8_t> payload_data,
const String& media_type) const String& media_type)
: record_type_("mime"), payload_data_(std::move(payload_data)) { : category_(device::mojom::NDEFRecordTypeCategory::kStandardized),
record_type_("mime"),
payload_data_(std::move(payload_data)) {
// ExtractMIMETypeFromMediaType() ignores parameters of the MIME type. // ExtractMIMETypeFromMediaType() ignores parameters of the MIME type.
media_type_ = ExtractMIMETypeFromMediaType(AtomicString(media_type)); media_type_ = ExtractMIMETypeFromMediaType(AtomicString(media_type));
if (media_type_.IsEmpty()) { if (media_type_.IsEmpty()) {
...@@ -356,40 +360,27 @@ NDEFRecord::NDEFRecord(WTF::Vector<uint8_t> payload_data, ...@@ -356,40 +360,27 @@ NDEFRecord::NDEFRecord(WTF::Vector<uint8_t> payload_data,
} }
NDEFRecord::NDEFRecord(const device::mojom::blink::NDEFRecord& record) NDEFRecord::NDEFRecord(const device::mojom::blink::NDEFRecord& record)
: record_type_(record.record_type), : category_(record.category),
record_type_(record.record_type),
media_type_(record.media_type), media_type_(record.media_type),
id_(record.id), id_(record.id),
encoding_(record.encoding), encoding_(record.encoding),
lang_(record.lang), lang_(record.lang),
payload_data_(record.data) { payload_data_(record.data) {
DCHECK_NE(record_type_ == "mime", media_type_.IsNull()); DCHECK_NE(record_type_ == "mime", media_type_.IsNull());
DCHECK_EQ(category_ == device::mojom::NDEFRecordTypeCategory::kExternal,
IsValidExternalType(record_type_));
if (record.payload_message) { if (record.payload_message) {
payload_message_ = payload_message_ =
MakeGarbageCollected<NDEFMessage>(*record.payload_message); MakeGarbageCollected<NDEFMessage>(*record.payload_message);
} }
} }
const String& NDEFRecord::recordType() const {
return record_type_;
}
const String& NDEFRecord::mediaType() const { const String& NDEFRecord::mediaType() const {
DCHECK_NE(record_type_ == "mime", media_type_.IsNull()); DCHECK_NE(record_type_ == "mime", media_type_.IsNull());
return media_type_; return media_type_;
} }
const String& NDEFRecord::id() const {
return id_;
}
const String& NDEFRecord::encoding() const {
return encoding_;
}
const String& NDEFRecord::lang() const {
return lang_;
}
DOMDataView* NDEFRecord::data() const { DOMDataView* NDEFRecord::data() const {
// Step 4 in https://w3c.github.io/web-nfc/#dfn-parse-an-ndef-record // Step 4 in https://w3c.github.io/web-nfc/#dfn-parse-an-ndef-record
if (record_type_ == "empty") { if (record_type_ == "empty") {
...@@ -401,15 +392,11 @@ DOMDataView* NDEFRecord::data() const { ...@@ -401,15 +392,11 @@ DOMDataView* NDEFRecord::data() const {
return DOMDataView::Create(dom_buffer, 0, payload_data_.size()); return DOMDataView::Create(dom_buffer, 0, payload_data_.size());
} }
const WTF::Vector<uint8_t>& NDEFRecord::payloadData() const {
return payload_data_;
}
// https://w3c.github.io/web-nfc/#dfn-convert-ndefrecord-payloaddata-bytes // https://w3c.github.io/web-nfc/#dfn-convert-ndefrecord-payloaddata-bytes
base::Optional<HeapVector<Member<NDEFRecord>>> NDEFRecord::toRecords( base::Optional<HeapVector<Member<NDEFRecord>>> NDEFRecord::toRecords(
ExceptionState& exception_state) const { ExceptionState& exception_state) const {
if (record_type_ != "smart-poster" && if (record_type_ != "smart-poster" &&
ValidateCustomRecordType(record_type_).IsNull()) { category_ != device::mojom::NDEFRecordTypeCategory::kExternal) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError, DOMExceptionCode::kNotSupportedError,
"Only smart-poster records and external type records could have a ndef " "Only smart-poster records and external type records could have a ndef "
...@@ -423,10 +410,6 @@ base::Optional<HeapVector<Member<NDEFRecord>>> NDEFRecord::toRecords( ...@@ -423,10 +410,6 @@ base::Optional<HeapVector<Member<NDEFRecord>>> NDEFRecord::toRecords(
return payload_message_->records(); return payload_message_->records();
} }
const NDEFMessage* NDEFRecord::payload_message() const {
return payload_message_.Get();
}
void NDEFRecord::Trace(blink::Visitor* visitor) { void NDEFRecord::Trace(blink::Visitor* visitor) {
visitor->Trace(payload_message_); visitor->Trace(payload_message_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
......
...@@ -35,29 +35,39 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable { ...@@ -35,29 +35,39 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
explicit NDEFRecord(WTF::Vector<uint8_t> /* payload_data */, explicit NDEFRecord(WTF::Vector<uint8_t> /* payload_data */,
const String& /* media_type */); const String& /* media_type */);
NDEFRecord(const String& /* record_type */, WTF::Vector<uint8_t> /* data */); explicit NDEFRecord(const String& /* record_type */,
NDEFRecord(const String& /* record_type */, WTF::Vector<uint8_t> /* data */);
explicit NDEFRecord(const String& /* record_type */,
const String& /* encoding */, const String& /* encoding */,
const String& /* lang */, const String& /* lang */,
WTF::Vector<uint8_t> /* data */); WTF::Vector<uint8_t> /* data */);
// All above Ctors by default set the type category to
// device::mojom::NDEFRecordTypeCategory::kStandardized.
explicit NDEFRecord(device::mojom::NDEFRecordTypeCategory /* category */,
const String& /* record_type */,
WTF::Vector<uint8_t> /* data */);
explicit NDEFRecord(const device::mojom::blink::NDEFRecord&); explicit NDEFRecord(const device::mojom::blink::NDEFRecord&);
const String& recordType() const; const String& recordType() const { return record_type_; }
const String& mediaType() const; const String& mediaType() const;
const String& id() const; const String& id() const { return id_; }
const String& encoding() const; const String& encoding() const { return encoding_; }
const String& lang() const; const String& lang() const { return lang_; }
DOMDataView* data() const; DOMDataView* data() const;
base::Optional<HeapVector<Member<NDEFRecord>>> toRecords( base::Optional<HeapVector<Member<NDEFRecord>>> toRecords(
ExceptionState& exception_state) const; ExceptionState& exception_state) const;
const WTF::Vector<uint8_t>& payloadData() const; device::mojom::NDEFRecordTypeCategory category() const { return category_; }
const NDEFMessage* payload_message() const; const WTF::Vector<uint8_t>& payloadData() const { return payload_data_; }
const NDEFMessage* payload_message() const { return payload_message_.Get(); }
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
private: private:
String record_type_; const device::mojom::NDEFRecordTypeCategory category_;
const String record_type_;
String media_type_; String media_type_;
String id_; String id_;
String encoding_; String encoding_;
......
...@@ -30,8 +30,8 @@ namespace mojo { ...@@ -30,8 +30,8 @@ namespace mojo {
NDEFRecordPtr TypeConverter<NDEFRecordPtr, blink::NDEFRecord*>::Convert( NDEFRecordPtr TypeConverter<NDEFRecordPtr, blink::NDEFRecord*>::Convert(
const blink::NDEFRecord* record) { const blink::NDEFRecord* record) {
return NDEFRecord::New( return NDEFRecord::New(
record->recordType(), record->mediaType(), record->id(), record->category(), record->recordType(), record->mediaType(),
record->encoding(), record->lang(), record->payloadData(), record->id(), record->encoding(), record->lang(), record->payloadData(),
TypeConverter<NDEFMessagePtr, blink::NDEFMessage*>::Convert( TypeConverter<NDEFMessagePtr, blink::NDEFMessage*>::Convert(
record->payload_message())); record->payload_message()));
} }
......
...@@ -24,6 +24,14 @@ function toMojoNDEFMessage(message) { ...@@ -24,6 +24,14 @@ function toMojoNDEFMessage(message) {
function toMojoNDEFRecord(record) { function toMojoNDEFRecord(record) {
let nfcRecord = new device.mojom.NDEFRecord(); let nfcRecord = new device.mojom.NDEFRecord();
if (record.recordType.search(':') != -1) {
// Simply checks the existence of ':' to decide whether it's an external
// type. As a mock, no need to really implement the validation algo at
// https://w3c.github.io/web-nfc/#dfn-validate-external-type.
nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kExternal;
} else {
nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kStandardized;
}
nfcRecord.recordType = record.recordType; nfcRecord.recordType = record.recordType;
nfcRecord.mediaType = record.mediaType; nfcRecord.mediaType = record.mediaType;
nfcRecord.id = record.id; nfcRecord.id = record.id;
......
...@@ -260,15 +260,15 @@ ...@@ -260,15 +260,15 @@
test(() => { test(() => {
assert_throws(new TypeError, () => new NDEFRecord( assert_throws(new TypeError, () => new NDEFRecord(
createRecord('foo.eXamPle.coM:bAr*-', "A string is not a BufferSource or NDEFMessage")), createRecord('foo.eXamPle.com:bAr*-', "A string is not a BufferSource or NDEFMessage")),
'Only BufferSource and NDEFMessage are allowed to be the record data.'); 'Only BufferSource and NDEFMessage are allowed to be the record data.');
let buffer = new ArrayBuffer(4); let buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([1, 2, 3, 4]); new Uint8Array(buffer).set([1, 2, 3, 4]);
// Feed ArrayBuffer. // Feed ArrayBuffer.
{ {
const record = new NDEFRecord(createRecord('foo.eXamPle.coM:bAr*-', buffer, test_record_id)); const record = new NDEFRecord(createRecord('foo.eXamPle.com:bAr*-', buffer, test_record_id));
assert_equals(record.recordType, 'foo.example.com:bAr*-', 'recordType'); assert_equals(record.recordType, 'foo.eXamPle.com:bAr*-', 'recordType');
assert_equals(record.mediaType, null, 'mediaType'); assert_equals(record.mediaType, null, 'mediaType');
assert_equals(record.id, test_record_id, 'id'); assert_equals(record.id, test_record_id, 'id');
assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4],
...@@ -280,8 +280,8 @@ ...@@ -280,8 +280,8 @@
{ {
let buffer_view = new Uint8Array(buffer, 1); let buffer_view = new Uint8Array(buffer, 1);
const record = new NDEFRecord(createRecord( const record = new NDEFRecord(createRecord(
'foo.eXamPle.coM:bAr*-', buffer_view, test_record_id)); 'foo.eXamPle.com:bAr*-', buffer_view, test_record_id));
assert_equals(record.recordType, 'foo.example.com:bAr*-', 'recordType'); assert_equals(record.recordType, 'foo.eXamPle.com:bAr*-', 'recordType');
assert_equals(record.mediaType, null, 'mediaType'); assert_equals(record.mediaType, null, 'mediaType');
assert_equals(record.id, test_record_id, 'id'); assert_equals(record.id, test_record_id, 'id');
assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4],
...@@ -305,12 +305,22 @@ ...@@ -305,12 +305,22 @@
}, 'NDEFRecord constructor with record type string being treated as case sensitive'); }, 'NDEFRecord constructor with record type string being treated as case sensitive');
test(() => { test(() => {
// Length of the external type is 255, OK.
const record = new NDEFRecord(createRecord(
[...Array(251)].map(_ => 'a').join('') + ':xyz', test_buffer_data));
// Exceeding 255, Throws.
assert_throws(new TypeError, () => new NDEFRecord(createRecord( assert_throws(new TypeError, () => new NDEFRecord(createRecord(
':xyz', test_buffer_data)), 'The domain should not be empty.'); [...Array(252)].map(_ => 'a').join('') + ':xyz', test_buffer_data)),
'The external type should not be longer than 255.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord( assert_throws(new TypeError, () => new NDEFRecord(createRecord(
'[:xyz', test_buffer_data)), '"[" is not a valid FQDN.'); 'xyz', test_buffer_data)), 'The external type must have a \':\'.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord(
':xyz', test_buffer_data)), 'The domain should not be empty.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord( assert_throws(new TypeError, () => new NDEFRecord(createRecord(
'example.com:', test_buffer_data)), 'The type should not be empty.'); 'example.com:', test_buffer_data)), 'The type should not be empty.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord(
'example.com:xyz[', test_buffer_data)), 'The type should not contain \'[\'.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord( assert_throws(new TypeError, () => new NDEFRecord(createRecord(
'example.com:xyz~', test_buffer_data)), 'The type should not contain \'~\'.'); 'example.com:xyz~', test_buffer_data)), 'The type should not contain \'~\'.');
assert_throws(new TypeError, () => new NDEFRecord(createRecord( assert_throws(new TypeError, () => new NDEFRecord(createRecord(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment