Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(190)

Side by Side Diff: mojo/bindings/js/message_file_parser.js

Issue 411553003: Validate incoming JS Message Headers: test message parser (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | mojo/bindings/js/run_js_tests.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
abarth-chromium 2014/07/22 17:04:24 Is this file used in production or for testing? I
Matt Perry 2014/07/22 20:38:28 2014
hansmuller 2014/07/23 00:14:06 Done.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 define([
6 "console",
7 "gin/test/expect",
8 ], function (console, expect) {
abarth-chromium 2014/07/22 17:04:24 You shouldn't need to import either of these modul
9
10 // The little-endian and 64 bit code below was lifted from codec.js.
11
12 var kHostIsLittleEndian = (function () {
13 var endianArrayBuffer = new ArrayBuffer(2);
14 var endianUint8Array = new Uint8Array(endianArrayBuffer);
15 var endianUint16Array = new Uint16Array(endianArrayBuffer);
16 endianUint16Array[0] = 1;
17 return endianUint8Array[0] == 1;
18 })();
abarth-chromium 2014/07/22 17:04:24 Can you refactor the code so we don't have to copy
19
20 var kHighWordMultiplier = 0x100000000;
21
22 function align(size) {
23 return size + (kAlignment - (size % kAlignment)) % kAlignment;
24 }
abarth-chromium 2014/07/22 17:04:24 This is also duplicated from codec.js
25
26 function setInt64(dataView, byteOffset, value) {
27 var hi = Math.floor(value / kHighWordMultiplier);
28 if (kHostIsLittleEndian) {
29 dataView.setInt32(byteOffset, value, kHostIsLittleEndian);
30 dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian);
31 } else {
32 dataView.setInt32(byteOffset, hi, kHostIsLittleEndian);
33 dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian);
34 }
35 }
36
37 function setUint64(dataView, byteOffset, value) {
38 var hi = (value / kHighWordMultiplier) | 0;
39 if (kHostIsLittleEndian) {
40 dataView.setInt32(byteOffset, value, kHostIsLittleEndian);
41 dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian);
42 } else {
43 dataView.setInt32(byteOffset, hi, kHostIsLittleEndian);
44 dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian);
45 }
46 }
47
48 // MessageData contains the sequence of bytes loaded from a ".data" file.
49
50 function MessageData(byteLength) {
51 this.buffer = new ArrayBuffer(byteLength);
52 this.data = new DataView(this.buffer);
53 this.byteLength = byteLength;
54 }
55
56 MessageData.prototype.getU1 = function(offset) {
57 this.data.getUint8(offset);
58 }
59
60 MessageData.prototype.setU1 = function(offset, value) {
61 this.data.setUint8(offset, value);
62 }
63 MessageData.prototype.setU2 = function(offset, value) {
64 this.data.setUint16(offset, value, kHostIsLittleEndian);
65 }
66 MessageData.prototype.setU4 = function(offset, value) {
67 this.data.setUint32(offset, value, kHostIsLittleEndian);
68 }
69 MessageData.prototype.setU8 = function(offset, value) {
70 setUint64(this.data, offset, value, kHostIsLittleEndian);
71 }
72 MessageData.prototype.setS1 = function(offset, value) {
73 this.data.setInt8(offset, value);
74 }
75 MessageData.prototype.setS2 = function(offset, value) {
76 this.data.setInt16(offset, value, kHostIsLittleEndian);
77 }
78 MessageData.prototype.setS4 = function(offset, value) {
79 this.data.setInt32(offset, value, kHostIsLittleEndian);
80 }
81 MessageData.prototype.setS8 = function(offset, value) {
82 setInt64(this.data, offset, value, kHostIsLittleEndian);
83 }
84 MessageData.prototype.setF = function(offset, value) {
85 this.data.setFloat32(offset, value, kHostIsLittleEndian);
86 }
87 MessageData.prototype.setD = function(offset, value) {
88 this.data.setFloat64(offset, value, kHostIsLittleEndian);
89 }
90
91 // Files and Lines represent the raw text from a ".data" file.
92
93 function InputError(message, line) {
94 this.message = message;
95 this.line = line;
96 }
97
98 InputError.prototype.toString = function() {
99 var s = 'Error: ' + this.message;
100 if (this.line)
101 s += ', at line ' +
102 (this.line.number + 1) + ': "' + this.line.contents + '"';
103 return s;
104 }
105
106 function File(contents) {
107 this.contents = contents;
108 this.index = 0;
109 this.lineNumber = 0;
110 }
111
112 File.prototype.isEmpty = function() {
113 return this.index >= this.contents.length;
114 }
115
116 File.prototype.nextLine = function() {
117 if (this.isEmpty())
118 return null;
119 var start = this.index;
120 var end = this.contents.indexOf('\n', start);
121 if (end == -1)
122 end = this.contents.length;
123 this.index = end + 1;
124 return new Line(this.contents.substring(start, end), this.lineNumber++);
125 }
126
127 function Line(contents, number) {
128 var i = contents.indexOf('//');
129 var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim();
130 this.contents = contents;
131 this.items = (s.length > 0) ? s.split(/\s+/) : [];
132 this.index = 0;
133 this.number = number;
134 }
135
136 Line.prototype.isEmpty = function() {
137 return this.index >= this.items.length;
138 }
139
140 var itemTypeSizes = {
141 u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8,
142 dist4: 4, dist8: 8, anchr: 0, handles: 0
143 };
144
145 function isValidItemType(type) {
146 return itemTypeSizes[type] !== undefined;
147 }
148
149 Line.prototype.nextItem = function() {
150 if (this.isEmpty())
151 return null;
152
153 var itemString = this.items[this.index++];
154 var type = 'u1';
155 var value = itemString;
156
157 if (itemString.charAt(0) == '[') {
158 var i = itemString.indexOf(']');
159 if (i != -1 && i + 1 < itemString.length) {
160 type = itemString.substring(1, i);
161 value = itemString.substring(i + 1);
162 } else {
163 throw new InputError('invalid item', this);
164 }
165 }
166 if (!isValidItemType(type))
167 throw new InputError('invalid item type', this);
168
169 return new Item(this, type, value);
170 }
171
172 // The text for Each whitespace delimited binary data "item" loaded
Matt Perry 2014/07/22 20:38:28 each*
hansmuller 2014/07/23 00:14:06 Done.
173 // from a ".data" file is represented by an Item.
174
175 function Item(line, type, value) {
176 this.line = line;
177 this.type = type;
178 this.value = value;
179 this.size = itemTypeSizes[type];
180 }
181
182 Item.prototype.isFloat = function() {
183 return this.type == 'f' || this.type == 'd';
184 }
185
186 Item.prototype.isInteger = function() {
187 return ['u1', 'u2', 'u4', 'u8',
188 's1', 's2', 's4', 's8'].indexOf(this.type) != -1;
189 }
190
191 Item.prototype.isNumber = function() {
192 return this.isFloat() || this.isInteger();
193 }
194
195 Item.prototype.isByte = function() {
196 return this.type == 'b';
197 }
198
199 Item.prototype.isDistance = function() {
200 return this.type == 'dist4' || this.type == 'dist8';
201 }
202
203 Item.prototype.isAnchor = function() {
204 return this.type == 'anchr';
205 }
206
207 Item.prototype.isHandles = function() {
208 return this.type == 'handles';
209 }
210
211 // A TestMessage represents the complete binary message loaded from
212 // ".data" file. The parseTestMessage() function below constructs a
213 // TestMessage from a File.
214
215 function TestMessage(byteLength) {
216 this.index = 0;
217 this.data = new MessageData(byteLength);
218 this.distances = {};
219 this.handleCount = 0;
220 }
221
222 function checkItemNumberValue(item, n, min, max) {
223 if (n < min || n > max)
224 throw new InputError('invalid item value', item.line);
225 }
226
227 TestMessage.prototype.addNumber = function(item) {
228 var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value);
229 if (Number.isNaN(n))
230 throw new InputError("can't parse item value", item.line);
231
232 switch(item.type) {
233 case 'u1':
234 checkItemNumberValue(item, n, 0, 0xFF);
235 this.data.setU1(this.index, n);
236 this.index += 1;
237 break;
238 case 'u2':
239 checkItemNumberValue(item, n, 0, 0xFFFF);
240 this.data.setU2(this.index, n);
241 this.index += 2;
242 break;
243 case 'u4':
244 checkItemNumberValue(item, n, 0, 0xFFFFFFFF);
245 this.data.setU4(this.index, n);
246 this.index += 4;
247 break;
248 case 'u8':
249 checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER);
250 this.data.setU8(this.index, n);
251 this.index += 8;
252 break;
253 case 's1':
254 checkItemNumberValue(item, n, -128, 127);
255 this.data.setS1(this.index, n);
256 this.index += 1;
257 break;
258 case 's2':
259 checkItemNumberValue(item, n, -32768, 32767);
260 this.data.setS2(this.index, n);
261 this.index += 2;
262 break;
263 case 's4':
264 checkItemNumberValue(item, n, -2147483648, 2147483647);
265 this.data.setS4(this.index, n);
266 this.index += 4;
267 break;
268 case 's8':
269 checkItemNumberValue(item, n,
270 Number.MIN_SAFE_INTEGER,
271 Number.MAX_SAFE_INTEGER);
272 this.data.setS8(this.index, n);
273 this.index += 8;
274 break;
275 case 'f':
276 this.data.setF(this.index, n);
277 this.index += 4;
278 break;
279 case 'd':
280 this.data.setD(this.index, n);
281 this.index += 8;
282 break;
283
284 default:
285 throw new InputError('unrecognized item type', item.line);
286 }
287 }
288
289 TestMessage.prototype.addByte = function(item) {
290 if (!/^[01]{8}$/.test(item.value))
291 throw new InputError('invalid byte item value', item.line);
292 function b(i) {
293 return (item.value.charAt(i) == '1') ? 1 << i : 0;
294 }
295 var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7);
296 this.data.setU1(this.index, n);
297 this.index += 1;
298 }
299
300 TestMessage.prototype.addDistance = function(item) {
301 if (this.distances[item.value])
302 throw new InputError('duplicate distance item', item.line);
303 this.distances[item.value] = {index: this.index, item: item};
304 this.index += item.type == 'dist4' ? 4 : 8;
305 }
306
307 TestMessage.prototype.addAnchor = function(item) {
308 var dist = this.distances[item.value];
309 if (!dist)
310 throw new InputError('unmatched anchor item', item.line);
311 delete this.distances[item.value];
312
313 var n = this.index - dist.index;
314 // TODO(hansmuller): validate n
315
316 if (dist.item.type == 'dist4')
317 this.data.setU4(dist.index, n, kHostIsLittleEndian);
318 else if (dist.item.type == 'dist8')
319 this.data.setU8(this.data, dist.index, n);
320 else
321 throw new InputError('unrecognzed distance item type', dist.item.line);
322 }
323
324 TestMessage.prototype.addHandles = function(item) {
325 this.handleCount = parseInt(item.value);
326 if (Number.isNaN(this.handleCount))
327 throw new InputError("can't parse handleCount", item.line);
328 }
329
330 TestMessage.prototype.addItem = function(item) {
331 if (item.isNumber())
332 this.addNumber(item);
333 else if (item.isByte())
334 this.addByte(item);
335 else if (item.isDistance())
336 this.addDistance(item);
337 else if (item.isAnchor())
338 this.addAnchor(item);
339 else if (item.isHandles())
340 this.addHandles(item);
341 else
342 throw new InputError('unrecognized item type', item.line);
343 }
344
345 TestMessage.prototype.unanchoredDistances = function() {
346 var names = null;
347 for (var name in this.distances) {
348 if (this.distances.hasOwnProperty(name))
349 names = (names === null) ? name : names + ' ' + name;
350 }
351 return names;
352 }
353
354 function parseTestMessage(file) {
355 var items = [];
356 var messageLength = 0;
357 while(!file.isEmpty()) {
358 var line = file.nextLine();
359 while (!line.isEmpty()) {
360 var item = line.nextItem();
361 if (item.isHandles() && items.length > 0)
362 throw new InputError('handles item is not first');
363 messageLength += item.size;
364 items.push(item);
365 }
366 }
367
368 var msg = new TestMessage(messageLength);
369 for (var i = 0; i < items.length; i++)
370 msg.addItem(items[i]);
371
372 if (messageLength != msg.index)
373 throw new InputError('failed to compute message length');
374 var names = msg.unanchoredDistances();
375 if (names)
376 throw new InputError('no anchors for ' + names, 0);
377
378 return msg;
379 }
380
381 // Verify that the TestMessage (et al) data file loading code is OK.
382
383 function checkMessageFileParser() {
384 function ParserTestFailure(message, file) {
385 this.message = message;
386 this.file = file;
387 }
388
389 ParserTestFailure.prototype.toString = function() {
390 return 'Error: ' + this.message + ' for "' + this.file.contents + '"';
391 }
392
393 function checkData(data, expectedData, file) {
394 if (data.byteLength != expectedData.byteLength) {
395 var s = "message length (" + data.byteLength + ") doesn't match " +
396 "expected length: " + expectedData.byteLength;
397 throw new ParserTestFailure(s, file);
398 }
399
400 for (var i = 0; i < data.byteLength; i++) {
401 if (data.getU1(i) != expectedData.getU1(i)) {
402 var s = 'message data mismatch at byte offset ' + i;
403 throw new ParserTestFailure(s, file);
404 }
405 }
406 }
407
408 function testFloatItems() {
409 var file = new File('[f]+.3e9 [d]-10.03');
410 var msg = parseTestMessage(file);
411 var expectedData = new MessageData(12);
412 expectedData.setF(0, +.3e9);
413 expectedData.setD(4, -10.03);
414 checkData(msg.data, expectedData, file);
415 }
416
417 function testUnsignedIntegerItems() {
418 var file = new File('[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
419 '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff');
420 var msg = parseTestMessage(file);
421 var expectedData = new MessageData(17);
422 expectedData.setU1(0, 0x10);
423 expectedData.setU2(1, 65535);
424 expectedData.setU4(3, 65536);
425 expectedData.setU8(7, 0xFFFFFFFFFFFFF);
426 expectedData.setU1(15, 0);
427 expectedData.setU1(16, 0xff);
428 checkData(msg.data, expectedData, file);
429 }
430
431 function testSignedIntegerItems() {
432 var file = new File('[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40');
433 var msg = parseTestMessage(file);
434 var expectedData = new MessageData(15);
435 expectedData.setS8(0, -0x800);
436 expectedData.setS1(8, -128);
437 expectedData.setS2(9, 0);
438 expectedData.setS4(11, -40);
439 checkData(msg.data, expectedData, file);
440 }
441
442 function testByteItems() {
443 var file =
444 new File('[b]00001011 [b]10000000 // hello world\n [b]00000000');
445 var msg = parseTestMessage(file);
446 var expectedData = new MessageData(3);
447 expectedData.setU1(0, 11);
448 expectedData.setU1(1, 128);
449 expectedData.setU1(2, 0);
450 checkData(msg.data, expectedData, file);
451 }
452
453 function testAnchors() {
454 var file = new File('[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar');
455 var msg = parseTestMessage(file);
456 var expectedData = new MessageData(14);
457 expectedData.setU4(0, 14);
458 expectedData.setU1(4, 0);
459 expectedData.setU8(5, 9);
460 expectedData.setU1(13, 0);
461 checkData(msg.data, expectedData, file);
462 }
463
464 function testHandles() {
465 var file = new File('// This message has handles! \n[handles]50 [u8]2');
466 var msg = parseTestMessage(file);
467 var expectedData = new MessageData(8);
468 expectedData.setU8(0, 2);
469
470 if (msg.handleCount != 50) {
471 var s = 'wrong handle count (' + msg.handleCount + ')';
472 throw new ParserTestFailure(s, file);
473 }
474 checkData(msg.data, expectedData, file);
475 }
476
477 function testEmptyInput() {
478 var file = new File('');
479 var msg = parseTestMessage(file);
480 if (msg.data.byteLength != 0)
481 throw new ParserTestFailure('expected empty message', file);
482 }
483
484 function testBlankInput() {
485 var file =
486 new File(' \t // hello world \n\r \t// the answer is 42 ');
487 var msg = parseTestMessage(file);
488 if (msg.data.byteLength != 0)
489 throw new ParserTestFailure('expected empty message', file);
490 }
491
492 function testInvalidInput() {
493 function parserShouldFail(fileContents) {
494 var file = new File(fileContents);
495 try {
496 parseTestMessage(file);
497 } catch (e) {
498 if (e instanceof InputError)
499 return;
500 throw new ParserTestFailure(
501 'unexpected exception ' + e.toString(), file);
502 }
503 throw new ParserTestFailure("didn't detect invalid input", file);
504 }
505
506 ['/ hello world',
507 '[u1]x',
508 '[u2]-1000',
509 '[u1]0x100',
510 '[s2]-0x8001',
511 '[b]1',
512 '[b]1111111k',
513 '[dist4]unmatched',
514 '[anchr]hello [dist8]hello',
515 '[dist4]a [dist4]a [anchr]a',
516 // '[dist4]a [anchr]a [dist4]a [anchr]a',
517 '0 [handles]50'
518 ].forEach(parserShouldFail);
519 }
520
521 try {
522 testFloatItems();
523 testUnsignedIntegerItems();
524 testSignedIntegerItems();
525 testByteItems();
526 testInvalidInput();
527 testEmptyInput();
528 testBlankInput();
529 testHandles();
530 testAnchors();
531 } catch (e) {
532 console.log("unexpected exception " + e.toString());
533 return e;
534 }
535 return null;
536 }
537
538 function testMessageParser() {
539 expect(checkMessageFileParser()).toBeNull();
540 }
abarth-chromium 2014/07/22 17:04:24 This word should be done in validation_unittests.j
541
542 var exports = {};
543 exports.testMessageParser = testMessageParser;
544 return exports;
545 });
OLDNEW
« no previous file with comments | « no previous file | mojo/bindings/js/run_js_tests.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698