Skip to content

Commit

Permalink
Update Buffer.includes()
Browse files Browse the repository at this point in the history
Make sure that `Buffer.includes()` always return `true` if an
empty string is given as value.
  • Loading branch information
vmx committed Mar 2, 2018
1 parent c04cf6b commit 8b3ab08
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 83 deletions.
21 changes: 14 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -700,27 +700,33 @@ function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
byteOffset = dir ? 0 : (buffer.length - 1)
}

// If the offset is greater than the length of the buffer, it will fail,
// except if the given value is an empty one, then the offset is returned
var offsetGreaterLength = false
// Normalize byteOffset: negative offsets start from the end of the buffer
if (byteOffset < 0) byteOffset = buffer.length + byteOffset
if (byteOffset >= buffer.length) {
if (dir) return -1
if (dir) offsetGreaterLength = true
else byteOffset = buffer.length - 1
} else if (byteOffset < 0) {
if (dir) byteOffset = 0
else return -1
else offsetGreaterLength = true
}

// Normalize val
if (typeof val === 'string') {
val = Buffer.from(val, encoding)
}

// Special case: looking for empty string/buffer always passes
if (Buffer.isBuffer(val) && val.length === 0) {
return byteOffset
} else if (offsetGreaterLength) {
return -1
}

// Finally, search either indexOf (if dir is true) or lastIndexOf
if (Buffer.isBuffer(val)) {
// Special case: looking for empty string/buffer always fails
if (val.length === 0) {
return -1
}
return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
} else if (typeof val === 'number') {
val = val & 0xFF // Search for a byte value [0-255]
Expand All @@ -734,7 +740,8 @@ function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
}

throw new TypeError('val must be string, number or Buffer')
throw new TypeError('The "value" argument must be one of type ' +
'string, Buffer, or Uint8Array. Received type ' + typeof val)
}

function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
Expand Down
153 changes: 77 additions & 76 deletions test/node/test-buffer-includes.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
'use strict';
var Buffer = require('../../').Buffer;
const assert = require('assert');
const common = require('./common');


var assert = require('assert');

var Buffer = require('../../').Buffer;

var b = Buffer.from('abcdef');
var buf_a = Buffer.from('a');
var buf_bc = Buffer.from('bc');
var buf_f = Buffer.from('f');
var buf_z = Buffer.from('z');
var buf_empty = Buffer.from('');
const b = Buffer.from('abcdef');
const buf_a = Buffer.from('a');
const buf_bc = Buffer.from('bc');
const buf_f = Buffer.from('f');
const buf_z = Buffer.from('z');
const buf_empty = Buffer.from('');

assert(b.includes('a'));
assert(!b.includes('a', 1));
Expand All @@ -31,10 +28,10 @@ assert(b.includes('bc', -Infinity));
assert(!b.includes('bc', Infinity));
assert(b.includes('f'), b.length - 1);
assert(!b.includes('z'));
assert(!b.includes(''));
assert(!b.includes('', 1));
assert(!b.includes('', b.length + 1));
assert(!b.includes('', Infinity));
assert(b.includes(''));
assert(b.includes('', 1));
assert(b.includes('', b.length + 1));
assert(b.includes('', Infinity));
assert(b.includes(buf_a));
assert(!b.includes(buf_a, 1));
assert(!b.includes(buf_a, -1));
Expand All @@ -53,10 +50,10 @@ assert(b.includes(buf_bc, -Infinity));
assert(!b.includes(buf_bc, Infinity));
assert(b.includes(buf_f), b.length - 1);
assert(!b.includes(buf_z));
assert(!b.includes(buf_empty));
assert(!b.includes(buf_empty, 1));
assert(!b.includes(buf_empty, b.length + 1));
assert(!b.includes(buf_empty, Infinity));
assert(b.includes(buf_empty));
assert(b.includes(buf_empty, 1));
assert(b.includes(buf_empty, b.length + 1));
assert(b.includes(buf_empty, Infinity));
assert(b.includes(0x61));
assert(!b.includes(0x61, 1));
assert(!b.includes(0x61, -1));
Expand Down Expand Up @@ -141,8 +138,8 @@ assert.strictEqual(
);


// test usc2 encoding
var twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2');
// test ucs2 encoding
let twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2');

assert(twoByteString.includes('\u0395', 4, 'ucs2'));
assert(twoByteString.includes('\u03a3', -4, 'ucs2'));
Expand All @@ -151,20 +148,18 @@ assert(twoByteString.includes(
Buffer.from('\u03a3', 'ucs2'), -6, 'ucs2'));
assert(!twoByteString.includes('\u03a3', -2, 'ucs2'));

var mixedByteStringUcs2 =
Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2');
const mixedByteStringUcs2 =
Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2');
assert(mixedByteStringUcs2.includes('bc', 0, 'ucs2'));
assert(mixedByteStringUcs2.includes('\u03a3', 0, 'ucs2'));
assert(!mixedByteStringUcs2.includes('\u0396', 0, 'ucs2'));

assert(
6, mixedByteStringUcs2.includes(Buffer.from('bc', 'ucs2'), 0, 'ucs2'));
assert(
10, mixedByteStringUcs2.includes(Buffer.from('\u03a3', 'ucs2'),
0, 'ucs2'));
assert(
-1, mixedByteStringUcs2.includes(Buffer.from('\u0396', 'ucs2'),
0, 'ucs2'));
assert.ok(
mixedByteStringUcs2.includes(Buffer.from('bc', 'ucs2'), 0, 'ucs2'));
assert.ok(
mixedByteStringUcs2.includes(Buffer.from('\u03a3', 'ucs2'), 0, 'ucs2'));
assert.ok(
!mixedByteStringUcs2.includes(Buffer.from('\u0396', 'ucs2'), 0, 'ucs2'));

twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2');

Expand All @@ -182,7 +177,7 @@ assert(twoByteString.includes('\u0391\u03a3', 0, 'ucs2'), 'Alpha Sigma');
assert(twoByteString.includes('\u03a3\u03a3', 0, 'ucs2'), 'Sigma Sigma');
assert(twoByteString.includes('\u03a3\u0395', 0, 'ucs2'), 'Sigma Epsilon');

var mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395');
const mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395');
assert(mixedByteStringUtf8.includes('bc'));
assert(mixedByteStringUtf8.includes('bc', 5));
assert(mixedByteStringUtf8.includes('bc', -8));
Expand All @@ -192,18 +187,18 @@ assert(!mixedByteStringUtf8.includes('\u0396'));

// Test complex string includes algorithms. Only trigger for long strings.
// Long string that isn't a simple repeat of a shorter string.
var longString = 'A';
for (var i = 66; i < 76; i++) { // from 'B' to 'K'
let longString = 'A';
for (let i = 66; i < 76; i++) { // from 'B' to 'K'
longString = longString + String.fromCharCode(i) + longString;
}

var longBufferString = Buffer.from(longString);
const longBufferString = Buffer.from(longString);

// pattern of 15 chars, repeated every 16 chars in long
var pattern = 'ABACABADABACABA';
for (var i = 0; i < longBufferString.length - pattern.length; i += 7) {
var includes = longBufferString.includes(pattern, i);
assert(includes, 'Long ABACABA...-string at index ' + i);
let pattern = 'ABACABADABACABA';
for (let i = 0; i < longBufferString.length - pattern.length; i += 7) {
const includes = longBufferString.includes(pattern, i);
assert(includes, `Long ABACABA...-string at index ${i}`);
}
assert(longBufferString.includes('AJABACA'), 'Long AJABACA, First J');
assert(longBufferString.includes('AJABACA', 511), 'Long AJABACA, Second J');
Expand All @@ -213,30 +208,30 @@ assert(longBufferString.includes(pattern), 'Long JABACABA..., First J');
assert(longBufferString.includes(pattern, 512), 'Long JABACABA..., Second J');

// Search for a non-ASCII string in a pure ASCII string.
var asciiString = Buffer.from(
'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf');
const asciiString = Buffer.from(
'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf');
assert(!asciiString.includes('\x2061'));
assert(asciiString.includes('leb', 0));

// Search in string containing many non-ASCII chars.
var allCodePoints = [];
for (var i = 0; i < 65536; i++) allCodePoints[i] = i;
var allCharsString = String.fromCharCode.apply(String, allCodePoints);
var allCharsBufferUtf8 = Buffer.from(allCharsString);
var allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2');
const allCodePoints = [];
for (let i = 0; i < 65536; i++) allCodePoints[i] = i;
const allCharsString = String.fromCharCode.apply(String, allCodePoints);
const allCharsBufferUtf8 = Buffer.from(allCharsString);
const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2');

// Search for string long enough to trigger complex search with ASCII pattern
// and UC16 subject.
assert(!allCharsBufferUtf8.includes('notfound'));
assert(!allCharsBufferUcs2.includes('notfound'));

// Find substrings in Utf8.
var lengths = [1, 3, 15]; // Single char, simple and complex.
var indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b];
for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
for (var i = 0; i < indices.length; i++) {
var index = indices[i];
var length = lengths[lengthIndex];
let lengths = [1, 3, 15]; // Single char, simple and complex.
let indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b];
for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
let length = lengths[lengthIndex];

if (index + length > 0x7F) {
length = 2 * length;
Expand All @@ -250,46 +245,52 @@ for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
length = 4 * length;
}

var patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length);
const patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length);
assert(index, allCharsBufferUtf8.includes(patternBufferUtf8));

var patternStringUtf8 = patternBufferUtf8.toString();
const patternStringUtf8 = patternBufferUtf8.toString();
assert(index, allCharsBufferUtf8.includes(patternStringUtf8));
}
}

// Find substrings in Usc2.
lengths = [2, 4, 16]; // Single char, simple and complex.
indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0];
for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
for (var i = 0; i < indices.length; i++) {
var index = indices[i] * 2;
var length = lengths[lengthIndex];

var patternBufferUcs2 =
allCharsBufferUcs2.slice(index, index + length);
assert(
index, allCharsBufferUcs2.includes(patternBufferUcs2, 0, 'ucs2'));

var patternStringUcs2 = patternBufferUcs2.toString('ucs2');
assert(
index, allCharsBufferUcs2.includes(patternStringUcs2, 0, 'ucs2'));
for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
for (let i = 0; i < indices.length; i++) {
const index = indices[i] * 2;
const length = lengths[lengthIndex];

const patternBufferUcs2 =
allCharsBufferUcs2.slice(index, index + length);
assert.ok(
allCharsBufferUcs2.includes(patternBufferUcs2, 0, 'ucs2'));

const patternStringUcs2 = patternBufferUcs2.toString('ucs2');
assert.ok(
allCharsBufferUcs2.includes(patternStringUcs2, 0, 'ucs2'));
}
}

assert.throws(function() {
b.includes(function() { });
});
assert.throws(function() {
b.includes({});
});
assert.throws(function() {
b.includes([]);
[
() => { },
{},
[]
].forEach((val) => {
common.expectsError(
() => b.includes(val),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "value" argument must be one of type string, ' +
`Buffer, or Uint8Array. Received type ${typeof val}`
}
);
});

// test truncation of Number arguments to uint8
{
var buf = Buffer.from('this is a test');
const buf = Buffer.from('this is a test');
assert.ok(buf.includes(0x6973));
assert.ok(buf.includes(0x697320));
assert.ok(buf.includes(0x69732069));
Expand Down

0 comments on commit 8b3ab08

Please sign in to comment.