More explicit thread checking in SafeBrowsingDatabase.
[chromium-blink-merge.git] / mojo / public / js / validator.js
bloba6be692ead7ca92d998b9fb0b26cc06533ec92cf
1 // Copyright 2014 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.
5 define("mojo/public/js/validator", [
6   "mojo/public/js/codec",
7 ], function(codec) {
9   var validationError = {
10     NONE: 'VALIDATION_ERROR_NONE',
11     MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
12     ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
13     UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
14     UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
15     ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
16     UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
17     ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
18     UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
19     MESSAGE_HEADER_INVALID_FLAG_COMBINATION:
20         'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION',
21     MESSAGE_HEADER_MISSING_REQUEST_ID:
22         'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
23     DIFFERENT_SIZED_ARRAYS_IN_MAP:
24         'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
25   };
27   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
29   function isStringClass(cls) {
30     return cls === codec.String || cls === codec.NullableString;
31   }
33   function isHandleClass(cls) {
34     return cls === codec.Handle || cls === codec.NullableHandle;
35   }
37   function isNullable(type) {
38     return type === codec.NullableString || type === codec.NullableHandle ||
39         type instanceof codec.NullableArrayOf ||
40         type instanceof codec.NullablePointerTo;
41   }
43   function Validator(message) {
44     this.message = message;
45     this.offset = 0;
46     this.handleIndex = 0;
47   }
49   Object.defineProperty(Validator.prototype, "offsetLimit", {
50     get: function() { return this.message.buffer.byteLength; }
51   });
53   Object.defineProperty(Validator.prototype, "handleIndexLimit", {
54     get: function() { return this.message.handles.length; }
55   });
57   // True if we can safely allocate a block of bytes from start to
58   // to start + numBytes.
59   Validator.prototype.isValidRange = function(start, numBytes) {
60     // Only positive JavaScript integers that are less than 2^53
61     // (Number.MAX_SAFE_INTEGER) can be represented exactly.
62     if (start < this.offset || numBytes <= 0 ||
63         !Number.isSafeInteger(start) ||
64         !Number.isSafeInteger(numBytes))
65       return false;
67     var newOffset = start + numBytes;
68     if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
69       return false;
71     return true;
72   }
74   Validator.prototype.claimRange = function(start, numBytes) {
75     if (this.isValidRange(start, numBytes)) {
76       this.offset = start + numBytes;
77       return true;
78     }
79     return false;
80   }
82   Validator.prototype.claimHandle = function(index) {
83     if (index === codec.kEncodedInvalidHandleValue)
84       return true;
86     if (index < this.handleIndex || index >= this.handleIndexLimit)
87       return false;
89     // This is safe because handle indices are uint32.
90     this.handleIndex = index + 1;
91     return true;
92   }
94   Validator.prototype.validateHandle = function(offset, nullable) {
95     var index = this.message.buffer.getUint32(offset);
97     if (index === codec.kEncodedInvalidHandleValue)
98       return nullable ?
99           validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
101     if (!this.claimHandle(index))
102       return validationError.ILLEGAL_HANDLE;
103     return validationError.NONE;
104   }
106   Validator.prototype.validateStructHeader =
107       function(offset, minNumBytes, minNumFields) {
108     if (!codec.isAligned(offset))
109       return validationError.MISALIGNED_OBJECT;
111     if (!this.isValidRange(offset, codec.kStructHeaderSize))
112       return validationError.ILLEGAL_MEMORY_RANGE;
114     var numBytes = this.message.buffer.getUint32(offset);
115     var numFields = this.message.buffer.getUint32(offset + 4);
117     if (numBytes < minNumBytes || numFields < minNumFields)
118       return validationError.UNEXPECTED_STRUCT_HEADER;
120     if (!this.claimRange(offset, numBytes))
121       return validationError.ILLEGAL_MEMORY_RANGE;
123     return validationError.NONE;
124   }
126   Validator.prototype.validateMessageHeader = function() {
127     var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
128     if (err != validationError.NONE)
129       return err;
131     var numBytes = this.message.getHeaderNumBytes();
132     var numFields = this.message.getHeaderNumFields();
134     var validNumFieldsAndNumBytes =
135         (numFields == 2 && numBytes == codec.kMessageHeaderSize) ||
136         (numFields == 3 &&
137          numBytes == codec.kMessageWithRequestIDHeaderSize) ||
138         (numFields > 3 &&
139          numBytes >= codec.kMessageWithRequestIDHeaderSize);
140     if (!validNumFieldsAndNumBytes)
141       return validationError.UNEXPECTED_STRUCT_HEADER;
143     var expectsResponse = this.message.expectsResponse();
144     var isResponse = this.message.isResponse();
146     if (numFields == 2 && (expectsResponse || isResponse))
147       return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
149     if (isResponse && expectsResponse)
150       return validationError.MESSAGE_HEADER_INVALID_FLAG_COMBINATION;
152     return validationError.NONE;
153   }
155   // Returns the message.buffer relative offset this pointer "points to",
156   // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
157   // pointer's value is not valid.
158   Validator.prototype.decodePointer = function(offset) {
159     var pointerValue = this.message.buffer.getUint64(offset);
160     if (pointerValue === 0)
161       return NULL_MOJO_POINTER;
162     var bufferOffset = offset + pointerValue;
163     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
164   }
166   Validator.prototype.validateArrayPointer = function(
167       offset, elementSize, elementType, nullable, expectedDimensionSizes,
168       currentDimension) {
169     var arrayOffset = this.decodePointer(offset);
170     if (arrayOffset === null)
171       return validationError.ILLEGAL_POINTER;
173     if (arrayOffset === NULL_MOJO_POINTER)
174       return nullable ?
175           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
177     return this.validateArray(arrayOffset, elementSize, elementType,
178                               expectedDimensionSizes, currentDimension);
179   }
181   Validator.prototype.validateStructPointer = function(
182       offset, structClass, nullable) {
183     var structOffset = this.decodePointer(offset);
184     if (structOffset === null)
185       return validationError.ILLEGAL_POINTER;
187     if (structOffset === NULL_MOJO_POINTER)
188       return nullable ?
189           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
191     return structClass.validate(this, structOffset);
192   }
194   // This method assumes that the array at arrayPointerOffset has
195   // been validated.
197   Validator.prototype.arrayLength = function(arrayPointerOffset) {
198     var arrayOffset = this.decodePointer(arrayPointerOffset);
199     return this.message.buffer.getUint32(arrayOffset + 4);
200   }
202   Validator.prototype.validateMapPointer = function(
203       offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
204     // Validate the implicit map struct:
205     // struct {array<keyClass> keys; array<valueClass> values};
206     var structOffset = this.decodePointer(offset);
207     if (structOffset === null)
208       return validationError.ILLEGAL_POINTER;
210     if (structOffset === NULL_MOJO_POINTER)
211       return mapIsNullable ?
212           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
214     var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
215     var err = this.validateStructHeader(structOffset, mapEncodedSize, 2);
216     if (err !== validationError.NONE)
217         return err;
219     // Validate the keys array.
220     var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
221     err = this.validateArrayPointer(
222         keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
223     if (err !== validationError.NONE)
224         return err;
226     // Validate the values array.
227     var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
228     var valuesArrayDimensions = [0]; // Validate the actual length below.
229     if (valueClass instanceof codec.ArrayOf)
230       valuesArrayDimensions =
231           valuesArrayDimensions.concat(valueClass.dimensions());
232     var err = this.validateArrayPointer(valuesArrayPointerOffset,
233                                         valueClass.encodedSize,
234                                         valueClass,
235                                         valueIsNullable,
236                                         valuesArrayDimensions,
237                                         0);
238     if (err !== validationError.NONE)
239         return err;
241     // Validate the lengths of the keys and values arrays.
242     var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
243     var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
244     if (keysArrayLength != valuesArrayLength)
245       return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
247     return validationError.NONE;
248   }
250   Validator.prototype.validateStringPointer = function(offset, nullable) {
251     return this.validateArrayPointer(
252         offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
253   }
255   // Similar to Array_Data<T>::Validate()
256   // mojo/public/cpp/bindings/lib/array_internal.h
258   Validator.prototype.validateArray =
259       function (offset, elementSize, elementType, expectedDimensionSizes,
260                 currentDimension) {
261     if (!codec.isAligned(offset))
262       return validationError.MISALIGNED_OBJECT;
264     if (!this.isValidRange(offset, codec.kArrayHeaderSize))
265       return validationError.ILLEGAL_MEMORY_RANGE;
267     var numBytes = this.message.buffer.getUint32(offset);
268     var numElements = this.message.buffer.getUint32(offset + 4);
270     // Note: this computation is "safe" because elementSize <= 8 and
271     // numElements is a uint32.
272     var elementsTotalSize = (elementType === codec.PackedBool) ?
273         Math.ceil(numElements / 8) : (elementSize * numElements);
275     if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
276       return validationError.UNEXPECTED_ARRAY_HEADER;
278     if (expectedDimensionSizes[currentDimension] != 0 &&
279         numElements != expectedDimensionSizes[currentDimension]) {
280       return validationError.UNEXPECTED_ARRAY_HEADER;
281     }
283     if (!this.claimRange(offset, numBytes))
284       return validationError.ILLEGAL_MEMORY_RANGE;
286     // Validate the array's elements if they are pointers or handles.
288     var elementsOffset = offset + codec.kArrayHeaderSize;
289     var nullable = isNullable(elementType);
291     if (isHandleClass(elementType))
292       return this.validateHandleElements(elementsOffset, numElements, nullable);
293     if (isStringClass(elementType))
294       return this.validateArrayElements(
295           elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
296     if (elementType instanceof codec.PointerTo)
297       return this.validateStructElements(
298           elementsOffset, numElements, elementType.cls, nullable);
299     if (elementType instanceof codec.ArrayOf)
300       return this.validateArrayElements(
301           elementsOffset, numElements, elementType.cls, nullable,
302           expectedDimensionSizes, currentDimension + 1);
304     return validationError.NONE;
305   }
307   // Note: the |offset + i * elementSize| computation in the validateFooElements
308   // methods below is "safe" because elementSize <= 8, offset and
309   // numElements are uint32, and 0 <= i < numElements.
311   Validator.prototype.validateHandleElements =
312       function(offset, numElements, nullable) {
313     var elementSize = codec.Handle.encodedSize;
314     for (var i = 0; i < numElements; i++) {
315       var elementOffset = offset + i * elementSize;
316       var err = this.validateHandle(elementOffset, nullable);
317       if (err != validationError.NONE)
318         return err;
319     }
320     return validationError.NONE;
321   }
323   // The elementClass parameter is the element type of the element arrays.
324   Validator.prototype.validateArrayElements =
325       function(offset, numElements, elementClass, nullable,
326                expectedDimensionSizes, currentDimension) {
327     var elementSize = codec.PointerTo.prototype.encodedSize;
328     for (var i = 0; i < numElements; i++) {
329       var elementOffset = offset + i * elementSize;
330       var err = this.validateArrayPointer(
331           elementOffset, elementClass.encodedSize, elementClass, nullable,
332           expectedDimensionSizes, currentDimension);
333       if (err != validationError.NONE)
334         return err;
335     }
336     return validationError.NONE;
337   }
339   Validator.prototype.validateStructElements =
340       function(offset, numElements, structClass, nullable) {
341     var elementSize = codec.PointerTo.prototype.encodedSize;
342     for (var i = 0; i < numElements; i++) {
343       var elementOffset = offset + i * elementSize;
344       var err =
345           this.validateStructPointer(elementOffset, structClass, nullable);
346       if (err != validationError.NONE)
347         return err;
348     }
349     return validationError.NONE;
350   }
352   var exports = {};
353   exports.validationError = validationError;
354   exports.Validator = Validator;
355   return exports;