birdpack 1.1.0 → 1.1.1
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/lib/core.js +74 -120
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const tools = require('./tools');
|
|
2
2
|
const powered = require('./powered');
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const pathLib = require('path');
|
|
4
5
|
const fileType = require('./fileType');
|
|
5
6
|
|
|
6
7
|
module.exports = class{
|
|
@@ -300,153 +301,106 @@ module.exports = class{
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
file(options){
|
|
303
|
-
if(typeof options
|
|
304
|
-
options = {file: options};
|
|
304
|
+
if (typeof options !== 'object') {
|
|
305
|
+
options = { file: options };
|
|
305
306
|
}
|
|
306
307
|
|
|
307
|
-
if(typeof options.error !== 'function'){
|
|
308
|
-
options.error = (code, text)=>
|
|
309
|
-
this.error(code, text);
|
|
310
|
-
}
|
|
308
|
+
if (typeof options.error !== 'function') {
|
|
309
|
+
options.error = (code, text) => this.error(code, text);
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
const
|
|
314
|
-
const buffer = Buffer.alloc(bufferSize);
|
|
315
|
-
let fileStat = fs.existsSync(options.file) ? fs.statSync(options.file) : false;
|
|
312
|
+
const filePath = options.file;
|
|
316
313
|
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
fs.stat(filePath, (err, stat) => {
|
|
315
|
+
if (err || !stat.isFile()) {
|
|
316
|
+
return options.error(404, 'File not found.');
|
|
317
|
+
}
|
|
319
318
|
|
|
320
|
-
if(
|
|
319
|
+
if (stat.size === 0) {
|
|
321
320
|
return options.error(400, 'This file is empty.');
|
|
322
321
|
}
|
|
323
322
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
fileRange = 'stream';
|
|
329
|
-
}
|
|
330
|
-
options.type = fileType[fileExt];
|
|
331
|
-
}else{
|
|
332
|
-
options.type = 'application';
|
|
323
|
+
const ext = pathLib.extname(filePath).slice(1).toLowerCase();
|
|
324
|
+
|
|
325
|
+
if (!options.type) {
|
|
326
|
+
options.type = fileType?.[ext] || 'application/octet-stream';
|
|
333
327
|
}
|
|
334
328
|
|
|
335
|
-
|
|
336
|
-
|
|
329
|
+
const isMedia = ['mp4','mp3','ogg','weba','webm','avi','wav'].includes(ext);
|
|
330
|
+
const forceStream = options.stream === true;
|
|
331
|
+
const allowStream = isMedia && options.download !== true;
|
|
332
|
+
|
|
333
|
+
const rangeHeader = this.get('range');
|
|
334
|
+
|
|
335
|
+
this.set('accept-ranges', 'bytes')
|
|
336
|
+
.type(options.type);
|
|
337
|
+
|
|
338
|
+
if (options.download === true) {
|
|
339
|
+
this.set(
|
|
340
|
+
'content-disposition',
|
|
341
|
+
`attachment; filename="${options.filename || pathLib.basename(filePath)}"`
|
|
342
|
+
);
|
|
337
343
|
}
|
|
338
344
|
|
|
339
|
-
if(
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
let match = range.slice(6).split('-');
|
|
345
|
+
if ((forceStream || allowStream) && rangeHeader) {
|
|
346
|
+
const total = stat.size;
|
|
347
|
+
const [startStr, endStr] = rangeHeader.replace(/bytes=/, '').split('-');
|
|
343
348
|
|
|
344
|
-
const start = parseInt(
|
|
345
|
-
const end = parseInt(
|
|
349
|
+
const start = parseInt(startStr, 10) || 0;
|
|
350
|
+
const end = endStr ? parseInt(endStr, 10) : total - 1;
|
|
351
|
+
|
|
352
|
+
if (start >= total || end >= total) {
|
|
353
|
+
this.code(416)
|
|
354
|
+
.set('content-range', `bytes */${total}`)
|
|
355
|
+
.writeHead();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
346
358
|
|
|
347
359
|
const chunkSize = end - start + 1;
|
|
348
|
-
const fileDescriptor = fs.openSync(options.file, "r");
|
|
349
360
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
361
|
+
this.code(206)
|
|
362
|
+
.length(chunkSize)
|
|
363
|
+
.set('content-range', `bytes ${start}-${end}/${total}`)
|
|
364
|
+
.writeHead();
|
|
355
365
|
|
|
356
|
-
|
|
357
|
-
const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
|
|
358
|
-
|
|
359
|
-
if(bytesRead > 0 && this.res.writable){
|
|
360
|
-
this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
|
|
361
|
-
if(err){
|
|
362
|
-
fs.closeSync(fileDescriptor);
|
|
363
|
-
}else{
|
|
364
|
-
sendChunk(position + bytesRead);
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
}else{
|
|
368
|
-
fs.closeSync(fileDescriptor);
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
this.code(range == '' ? 200 : 206)
|
|
373
|
-
.length(chunkSize)
|
|
374
|
-
.type(options.type)
|
|
375
|
-
.set('accept-ranges', 'bytes')
|
|
376
|
-
.set('content-range', `bytes ${start}-${end}/${totalSize}`)
|
|
377
|
-
.writeHead();
|
|
378
|
-
sendChunk(start);
|
|
379
|
-
}else if(fileRange == 'file'){
|
|
380
|
-
if(options.parameter){
|
|
381
|
-
let data = fs.readFileSync(options.file, { encoding: 'utf8', flag: 'r' });
|
|
382
|
-
|
|
383
|
-
data = data.replace(/\{\{[a-zA-Z0-9\_]*\}\}/g, (a)=>{
|
|
384
|
-
let key = a.slice(2,-2);
|
|
385
|
-
if(key != ''){
|
|
386
|
-
if(options.parameter.hasOwnProperty(key)){
|
|
387
|
-
return options.parameter[key]
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
return '';
|
|
391
|
-
});
|
|
366
|
+
const stream = fs.createReadStream(filePath, { start, end });
|
|
392
367
|
|
|
393
|
-
|
|
368
|
+
stream.pipe(this.res);
|
|
369
|
+
this.res.on('close', () => stream.destroy());
|
|
370
|
+
stream.on('error', () => stream.destroy());
|
|
371
|
+
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
394
374
|
|
|
395
|
-
|
|
396
|
-
|
|
375
|
+
if (options.parameter) {
|
|
376
|
+
fs.readFile(filePath, 'utf8', (err, data) => {
|
|
377
|
+
if (err) {
|
|
378
|
+
return options.error(500, 'File read error.');
|
|
397
379
|
}
|
|
398
380
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.send(data);
|
|
403
|
-
}else{
|
|
404
|
-
const totalSize = fileStat.size;
|
|
381
|
+
data = data.replace(/\{\{(\w+)\}\}/g, (_, key) =>
|
|
382
|
+
options.parameter?.[key] ?? ''
|
|
383
|
+
);
|
|
405
384
|
|
|
406
|
-
const
|
|
407
|
-
const end = totalSize - 1;
|
|
385
|
+
const buf = Buffer.from(data, 'utf8');
|
|
408
386
|
|
|
409
|
-
|
|
410
|
-
|
|
387
|
+
this.code(200)
|
|
388
|
+
.send(buf);
|
|
389
|
+
});
|
|
411
390
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
fs.closeSync(fileDescriptor);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
417
393
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if(bytesRead > 0 && this.res.writable){
|
|
422
|
-
this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
|
|
423
|
-
if (err) {
|
|
424
|
-
fs.closeSync(fileDescriptor);
|
|
425
|
-
} else {
|
|
426
|
-
sendChunk(position + bytesRead);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}else{
|
|
430
|
-
fs.closeSync(fileDescriptor);
|
|
431
|
-
}
|
|
432
|
-
};
|
|
394
|
+
this.code(200)
|
|
395
|
+
.length(stat.size)
|
|
396
|
+
.writeHead();
|
|
433
397
|
|
|
434
|
-
|
|
435
|
-
.length(chunkSize)
|
|
436
|
-
.type(options.type);
|
|
398
|
+
const stream = fs.createReadStream(filePath);
|
|
437
399
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
sendChunk(start);
|
|
444
|
-
}
|
|
445
|
-
}else{
|
|
446
|
-
options.error(400, 'This file not supported.');
|
|
447
|
-
}
|
|
448
|
-
}else{
|
|
449
|
-
options.error(404, 'File not found.');
|
|
450
|
-
}
|
|
400
|
+
stream.pipe(this.res);
|
|
401
|
+
|
|
402
|
+
this.res.on('close', () => stream.destroy());
|
|
403
|
+
stream.on('error', () => stream.destroy());
|
|
404
|
+
});
|
|
451
405
|
}
|
|
452
406
|
}
|