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

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

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

Powered by Google App Engine
This is Rietveld 408576698