pixl-server-storage 3.0.9 → 3.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/API.md CHANGED
@@ -226,7 +226,12 @@ storage.getStreamRange( 'test1.gif', 0, 99, function(err, readStream, info) {
226
226
  } );
227
227
  ```
228
228
 
229
- As you can see above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`). The `len` is not affected by the `start` and `end` range -- it is always the full byte length of the record.
229
+ As you can see in the example above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`). The `len` is not affected by the `start` and `end` range -- it is always the full byte length of the record.
230
+
231
+ The API supports two special range cases:
232
+
233
+ - If `start` is valid but `end` is `NaN`, it is assumed you want the entire record starting at `start` offset.
234
+ - If `start` is `NaN` but `end` is valid, it is assumed you want `end` bytes from the end of the record.
230
235
 
231
236
  Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `getStreamRange()` method has to load the entire record into memory.
232
237
 
@@ -248,6 +248,20 @@ module.exports = Class.create({
248
248
  return callback( err, null );
249
249
  }
250
250
 
251
+ // validate byte range, now that we have the head info
252
+ if (isNaN(start) && !isNaN(end)) {
253
+ start = buf.length - end;
254
+ end = buf.length ? buf.length - 1 : 0;
255
+ }
256
+ else if (!isNaN(start) && isNaN(end)) {
257
+ end = buf.length ? buf.length - 1 : 0;
258
+ }
259
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
260
+ download.destroy();
261
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
262
+ return;
263
+ }
264
+
251
265
  var range = buf.slice(start, end + 1);
252
266
  var stream = new BufferStream(range);
253
267
  callback(null, stream, { mod: 1, len: buf.length });
@@ -5,7 +5,6 @@
5
5
  var path = require('path');
6
6
  var fs = require('fs');
7
7
  var async = require('async');
8
- var mkdirp = require('mkdirp');
9
8
  var crypto = require('crypto');
10
9
 
11
10
  var Class = require("pixl-class");
@@ -13,6 +12,8 @@ var Component = require("pixl-server/component");
13
12
  var Tools = require("pixl-tools");
14
13
  var Cache = require("pixl-cache");
15
14
 
15
+ var mkdirp = Tools.mkdirp;
16
+
16
17
  module.exports = Class.create({
17
18
 
18
19
  __name: 'Filesystem',
@@ -479,6 +480,20 @@ module.exports = Class.create({
479
480
  return callback( err, null );
480
481
  }
481
482
 
483
+ // validate byte range, now that we have the head info
484
+ if (isNaN(start) && !isNaN(end)) {
485
+ start = stats.size - end;
486
+ end = stats.size ? stats.size - 1 : 0;
487
+ }
488
+ else if (!isNaN(start) && isNaN(end)) {
489
+ end = stats.size ? stats.size - 1 : 0;
490
+ }
491
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= stats.size) || (end < start) || (end >= stats.size)) {
492
+ download.destroy();
493
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + stats.size + ")"), null );
494
+ return;
495
+ }
496
+
482
497
  // create read stream
483
498
  var inp = fs.createReadStream( file, { start, end } );
484
499
 
package/engines/Redis.js CHANGED
@@ -252,6 +252,20 @@ module.exports = Class.create({
252
252
  return callback( err, null );
253
253
  }
254
254
 
255
+ // validate byte range, now that we have the head info
256
+ if (isNaN(start) && !isNaN(end)) {
257
+ start = buf.length - end;
258
+ end = buf.length ? buf.length - 1 : 0;
259
+ }
260
+ else if (!isNaN(start) && isNaN(end)) {
261
+ end = buf.length ? buf.length - 1 : 0;
262
+ }
263
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
264
+ download.destroy();
265
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
266
+ return;
267
+ }
268
+
255
269
  var range = buf.slice(start, end + 1);
256
270
  var stream = new BufferStream(range);
257
271
  callback(null, stream, { mod: 1, len: buf.length });
@@ -269,6 +269,20 @@ module.exports = Class.create({
269
269
  return callback( err, null );
270
270
  }
271
271
 
272
+ // validate byte range, now that we have the head info
273
+ if (isNaN(start) && !isNaN(end)) {
274
+ start = buf.length - end;
275
+ end = buf.length ? buf.length - 1 : 0;
276
+ }
277
+ else if (!isNaN(start) && isNaN(end)) {
278
+ end = buf.length ? buf.length - 1 : 0;
279
+ }
280
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
281
+ download.destroy();
282
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
283
+ return;
284
+ }
285
+
272
286
  var range = buf.slice(start, end + 1);
273
287
  var stream = new BufferStream(range);
274
288
  callback(null, stream, { mod: 1, len: buf.length });
package/engines/S3.js CHANGED
@@ -338,7 +338,14 @@ module.exports = Class.create({
338
338
  this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
339
339
 
340
340
  var params = { Key: this.extKey(key, orig_key) };
341
- var download = self.s3.getObject( Tools.mergeHashes(params, { Range: "bytes=" + start + "-" + end }) ).createReadStream();
341
+
342
+ // convert start/end to HTTP range header string
343
+ var range = "bytes=";
344
+ if (!isNaN(start)) range += start;
345
+ range += '-';
346
+ if (!isNaN(end)) range += end;
347
+
348
+ var download = self.s3.getObject( Tools.mergeHashes(params, { Range: range }) ).createReadStream();
342
349
  var proceed = false;
343
350
 
344
351
  download.on('error', function(err) {
@@ -365,6 +372,20 @@ module.exports = Class.create({
365
372
  return;
366
373
  }
367
374
 
375
+ // validate byte range, now that we have the head info
376
+ if (isNaN(start) && !isNaN(end)) {
377
+ start = data.ContentLength - end;
378
+ end = data.ContentLength ? data.ContentLength - 1 : 0;
379
+ }
380
+ else if (!isNaN(start) && isNaN(end)) {
381
+ end = data.ContentLength ? data.ContentLength - 1 : 0;
382
+ }
383
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= data.ContentLength) || (end < start) || (end >= data.ContentLength)) {
384
+ download.destroy();
385
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + data.ContentLength + ")"), null );
386
+ return;
387
+ }
388
+
368
389
  proceed = true;
369
390
  callback( null, download, {
370
391
  mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.0.9",
3
+ "version": "3.0.13",
4
4
  "description": "A key/value/list storage component for the pixl-server framework.",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-server-storage",
@@ -27,8 +27,7 @@
27
27
  "pixl-perf": "^1.0.0",
28
28
  "pixl-cache": "^1.0.1",
29
29
  "async": "2.6.0",
30
- "mkdirp": "0.5.1",
31
- "nearley": "2.11.0",
30
+ "nearley": "2.20.1",
32
31
  "moo": "0.4.3",
33
32
  "porter-stemmer": "0.9.1",
34
33
  "unidecode": "0.1.8",
package/test/test-main.js CHANGED
@@ -424,6 +424,7 @@ module.exports = {
424
424
  },
425
425
 
426
426
  function testStreamRange(test) {
427
+ // grab a range from within a stream, with both start and end specified
427
428
  test.expect(14);
428
429
  var self = this;
429
430
 
@@ -469,6 +470,100 @@ module.exports = {
469
470
  } ); // putStream
470
471
  },
471
472
 
473
+ function testStreamRangeStart(test) {
474
+ // grab a range from within a stream, with end missing
475
+ test.expect(14);
476
+ var self = this;
477
+
478
+ var key = 'spacer-stream.gif';
479
+ var filename = 'spacer.gif';
480
+ var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
481
+ var spacerStream = fs.createReadStream( __dirname + '/' + filename );
482
+
483
+ test.ok( !!spacerBuf, "Got buffer from file" );
484
+ test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
485
+ test.ok( spacerBuf.length > 0, "Buffer has size" );
486
+ test.ok( !!spacerStream, "Got read stream" );
487
+
488
+ this.storage.putStream( key, spacerStream, function(err) {
489
+ test.ok( !err, "No error creating stream: " + err );
490
+
491
+ var tempFile = __dirname + '/' + filename + '.streamtemp';
492
+ var outStream = fs.createWriteStream( tempFile );
493
+
494
+ self.storage.getStreamRange( key, 20, NaN, function(err, storageStream, streamInfo) {
495
+ test.ok( !err, "No error fetching stream: " + err );
496
+ test.ok( !!storageStream, "Got storage stream as 2nd arg");
497
+ test.ok( !!storageStream.pipe, "Storage stream has a pipe");
498
+ test.ok( !!streamInfo, "Info was provided as the 3rd arg");
499
+ test.ok( streamInfo.len == 43, "Info has correct data length");
500
+ test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
501
+
502
+ outStream.on('finish', function() {
503
+ var newSpacerBuf = fs.readFileSync( tempFile );
504
+ test.ok( newSpacerBuf.length == 23, "Stream range length is correct" );
505
+ test.ok( newSpacerBuf.equals( spacerBuf.slice(20) ), "Range buffer content is correct" );
506
+
507
+ self.storage.delete( key, function(err) {
508
+ test.ok( !err, "No error deleting stream key: " + err );
509
+ fs.unlinkSync( tempFile );
510
+ test.done();
511
+ } ); // delete
512
+ } ); // stream finish
513
+
514
+ storageStream.pipe( outStream );
515
+
516
+ } ); // getStream
517
+ } ); // putStream
518
+ },
519
+
520
+ function testStreamRangeEnd(test) {
521
+ // grab a range from within a stream, with start missing
522
+ test.expect(14);
523
+ var self = this;
524
+
525
+ var key = 'spacer-stream.gif';
526
+ var filename = 'spacer.gif';
527
+ var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
528
+ var spacerStream = fs.createReadStream( __dirname + '/' + filename );
529
+
530
+ test.ok( !!spacerBuf, "Got buffer from file" );
531
+ test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
532
+ test.ok( spacerBuf.length > 0, "Buffer has size" );
533
+ test.ok( !!spacerStream, "Got read stream" );
534
+
535
+ this.storage.putStream( key, spacerStream, function(err) {
536
+ test.ok( !err, "No error creating stream: " + err );
537
+
538
+ var tempFile = __dirname + '/' + filename + '.streamtemp';
539
+ var outStream = fs.createWriteStream( tempFile );
540
+
541
+ self.storage.getStreamRange( key, NaN, 10, function(err, storageStream, streamInfo) {
542
+ test.ok( !err, "No error fetching stream: " + err );
543
+ test.ok( !!storageStream, "Got storage stream as 2nd arg");
544
+ test.ok( !!storageStream.pipe, "Storage stream has a pipe");
545
+ test.ok( !!streamInfo, "Info was provided as the 3rd arg");
546
+ test.ok( streamInfo.len == 43, "Info has correct data length");
547
+ test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
548
+
549
+ outStream.on('finish', function() {
550
+ var newSpacerBuf = fs.readFileSync( tempFile );
551
+ test.ok( newSpacerBuf.length == 10, "Stream range length is correct" );
552
+ test.ok( newSpacerBuf.equals( spacerBuf.slice(43 - 10) ), "Range buffer content is correct" );
553
+
554
+ self.storage.delete( key, function(err) {
555
+ test.ok( !err, "No error deleting stream key: " + err );
556
+ fs.unlinkSync( tempFile );
557
+ test.done();
558
+ } ); // delete
559
+ } ); // stream finish
560
+
561
+ storageStream.pipe( outStream );
562
+
563
+ } ); // getStream
564
+ } ); // putStream
565
+ },
566
+
472
567
  function testPutMulti(test) {
473
568
  // test storing multiple keys at once
474
569
  test.expect(1);
package/transaction.js CHANGED
@@ -8,9 +8,9 @@ var Path = require("path");
8
8
  var cp = require("child_process");
9
9
  var os = require("os");
10
10
  var async = require('async');
11
- var mkdirp = require('mkdirp');
12
11
  var Class = require("pixl-class");
13
12
  var Tools = require("pixl-tools");
13
+ var mkdirp = Tools.mkdirp;
14
14
 
15
15
  // Transaction support is implemented as a mixin to Storage
16
16
  // Config Keys: