intgrasi fungsi edit dan tambah serta upload pada admin
|
@ -20,6 +20,11 @@ const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
|
// Serve gambar dari folder image_hama
|
||||||
|
app.use('/image_hama', express.static(path.join(__dirname, 'image_hama')));
|
||||||
|
// Serve gambar dari folder image_penyakit
|
||||||
|
app.use('/image_penyakit', express.static(path.join(__dirname, 'image_penyakit')));
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.use("/api/users", userRoutes);
|
app.use("/api/users", userRoutes);
|
||||||
app.use("/api/auth", authRoutes);
|
app.use("/api/auth", authRoutes);
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
const {Hama} = require('../models');
|
const {Hama} = require('../models');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
// 🔹 Fungsi untuk mendapatkan semua data hama
|
// 🔹 Fungsi untuk mendapatkan semua data hama
|
||||||
exports.getAllHama = async (req, res) => {
|
exports.getAllHama = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const dataHama = await Hama.findAll({
|
const dataHama = await Hama.findAll({
|
||||||
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan']
|
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan', 'foto']
|
||||||
});
|
});
|
||||||
res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama });
|
res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -17,17 +19,36 @@ exports.getHamaById = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const hama = await Hama.findByPk(id);
|
const hama = await Hama.findByPk(id);
|
||||||
if (!hama) {
|
|
||||||
return res.status(404).json({ message: 'Hama tidak ditemukan' });
|
if (!hama || !hama.foto) {
|
||||||
|
return res.status(404).json({ message: 'Gambar tidak ditemukan' });
|
||||||
}
|
}
|
||||||
res.status(200).json({ message: 'Data hama ditemukan', data: hama });
|
|
||||||
|
console.log('Nama file gambar dari database:', hama.foto);
|
||||||
|
|
||||||
|
// Naik 1 level dari 'controller' ke 'backend'
|
||||||
|
const imagePath = path.resolve(__dirname, '..', 'image_hama', hama.foto);
|
||||||
|
console.log('Path absolut file gambar:', imagePath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(imagePath)) {
|
||||||
|
return res.status(404).json({ message: 'File gambar tidak ditemukan di path tersebut' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = path.extname(hama.foto).toLowerCase();
|
||||||
|
let contentType = 'image/jpeg';
|
||||||
|
|
||||||
|
if (ext === '.png') contentType = 'image/png';
|
||||||
|
else if (ext === '.gif') contentType = 'image/gif';
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', contentType);
|
||||||
|
res.sendFile(imagePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: 'Gagal mengambil data hama', error });
|
console.error('Error saat mengambil gambar:', error.stack);
|
||||||
|
res.status(500).json({ message: 'Gagal mengambil gambar', error: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pastikan sudah import 'Hama' model dan multer middleware sebelumnya
|
// Pastikan sudah import 'Hama' model dan multer middleware sebelumnya
|
||||||
|
|
||||||
exports.createHama = async (req, res) => {
|
exports.createHama = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { nama, deskripsi, penanganan } = req.body;
|
const { nama, deskripsi, penanganan } = req.body;
|
||||||
|
@ -74,7 +95,13 @@ exports.updateHama = async (req, res) => {
|
||||||
return res.status(404).json({ message: 'Hama tidak ditemukan' });
|
return res.status(404).json({ message: 'Hama tidak ditemukan' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await hama.update({ nama, kategori, deskripsi, penanganan });
|
// Ambil nama file jika ada file foto yang diunggah
|
||||||
|
let foto = hama.foto; // default: tetap gunakan yang lama
|
||||||
|
if (req.file) {
|
||||||
|
foto = req.file.filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
await hama.update({ nama, kategori, deskripsi, penanganan, foto });
|
||||||
|
|
||||||
res.status(200).json({ message: 'Hama berhasil diperbarui', data: hama });
|
res.status(200).json({ message: 'Hama berhasil diperbarui', data: hama });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
const {Penyakit} = require('../models');
|
const {Penyakit} = require('../models');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
// 🔹 Fungsi untuk mendapatkan semua data penyakit
|
// 🔹 Fungsi untuk mendapatkan semua data penyakit
|
||||||
exports.getAllPenyakit = async (req, res) => {
|
exports.getAllPenyakit = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const dataPenyakit = await Penyakit.findAll({
|
const dataPenyakit = await Penyakit.findAll({
|
||||||
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan']
|
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan' , 'foto']
|
||||||
});
|
});
|
||||||
res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit });
|
res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -15,21 +17,41 @@ exports.getAllPenyakit = async (req, res) => {
|
||||||
// 🔹 Fungsi untuk mendapatkan detail penyakit berdasarkan ID
|
// 🔹 Fungsi untuk mendapatkan detail penyakit berdasarkan ID
|
||||||
exports.getPenyakitById = async (req, res) => {
|
exports.getPenyakitById = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const penyakit = await Penyakit.findByPk(id);
|
const penyakit = await Penyakit.findByPk(id);
|
||||||
if (!penyakit) {
|
|
||||||
return res.status(404).json({ message: 'Penyakit tidak ditemukan' });
|
if (!penyakit || !penyakit.foto) {
|
||||||
|
return res.status(404).json({ message: 'Gambar tidak ditemukan' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Nama file gambar dari database:', penyakit.foto);
|
||||||
|
|
||||||
|
const imagePath = path.resolve(__dirname, '..', 'image_penyakit', penyakit.foto);
|
||||||
|
console.log('Path absolut file gambar:', imagePath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(imagePath)) {
|
||||||
|
return res.status(404).json({ message: 'File gambar tidak ditemukan di path tersebut' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = path.extname(penyakit.foto).toLowerCase();
|
||||||
|
let contentType = 'image/jpeg';
|
||||||
|
|
||||||
|
if (ext === '.png') contentType = 'image/png';
|
||||||
|
else if (ext === '.gif') contentType = 'image/gif';
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', contentType);
|
||||||
|
res.sendFile(imagePath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saat mengambil gambar:', error.stack);
|
||||||
|
res.status(500).json({ message: 'Gagal mengambil gambar', error: error.message });
|
||||||
}
|
}
|
||||||
res.status(200).json({ message: 'Data penyakit ditemukan', data: penyakit });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ message: 'Gagal mengambil data penyakit', error });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default)
|
// 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default)
|
||||||
exports.createPenyakit = async (req, res) => {
|
exports.createPenyakit = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { nama, deskripsi, penanganan } = req.body;
|
const { nama, deskripsi, penanganan } = req.body;
|
||||||
|
const file = req.file;
|
||||||
|
|
||||||
// Cek kode terakhir
|
// Cek kode terakhir
|
||||||
const lastPenyakit = await Penyakit.findOne({ order: [['id', 'DESC']] });
|
const lastPenyakit = await Penyakit.findOne({ order: [['id', 'DESC']] });
|
||||||
|
@ -39,12 +61,19 @@ exports.createPenyakit = async (req, res) => {
|
||||||
newKode = `P${lastNumber.toString().padStart(2, '0')}`;
|
newKode = `P${lastNumber.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cek kalau ada file yang diupload
|
||||||
|
let fotoPath = '';
|
||||||
|
if (file) {
|
||||||
|
fotoPath = file.filename;
|
||||||
|
}
|
||||||
|
|
||||||
const newPenyakit = await Penyakit.create({
|
const newPenyakit = await Penyakit.create({
|
||||||
kode: newKode,
|
kode: newKode,
|
||||||
nama,
|
nama,
|
||||||
kategori: 'penyakit', // Default kategori
|
kategori: 'penyakit', // Default kategori
|
||||||
deskripsi,
|
deskripsi,
|
||||||
penanganan,
|
penanganan,
|
||||||
|
foto: fotoPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit });
|
res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit });
|
||||||
|
@ -64,7 +93,13 @@ exports.updatePenyakit = async (req, res) => {
|
||||||
return res.status(404).json({ message: 'Penyakit tidak ditemukan' });
|
return res.status(404).json({ message: 'Penyakit tidak ditemukan' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await penyakit.update({ nama, kategori, deskripsi, penanganan });
|
// Ambil nama file jika ada file foto yang diunggah
|
||||||
|
let foto = penyakit.foto; // default: tetap gunakan yang lama
|
||||||
|
if (req.file) {
|
||||||
|
foto = req.file.filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
await penyakit.update({ nama, kategori, deskripsi, penanganan, foto });
|
||||||
|
|
||||||
res.status(200).json({ message: 'Penyakit berhasil diperbarui', data: penyakit });
|
res.status(200).json({ message: 'Penyakit berhasil diperbarui', data: penyakit });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
const multer = require('multer');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Path folder penyimpanan gambar
|
||||||
|
const uploadPath = path.join(__dirname, '../image_hama');
|
||||||
|
|
||||||
|
// Pastikan folder sudah ada, jika belum maka buat
|
||||||
|
if (!fs.existsSync(uploadPath)) {
|
||||||
|
fs.mkdirSync(uploadPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konfigurasi storage untuk multer
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function(req, file, cb) {
|
||||||
|
cb(null, uploadPath);
|
||||||
|
},
|
||||||
|
filename: function(req, file, cb) {
|
||||||
|
// Format nama file: hama-timestamp.extension
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
cb(null, `hama-${timestamp}${ext}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter untuk memastikan hanya file gambar yang diupload
|
||||||
|
const fileFilter = (req, file, cb) => {
|
||||||
|
// Izinkan hanya format gambar yang umum
|
||||||
|
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
|
||||||
|
|
||||||
|
if (allowedTypes.includes(file.mimetype)) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Format file tidak didukung! Hanya file JPG, JPEG, PNG, dan GIF yang diizinkan.'), false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inisialisasi multer dengan konfigurasi
|
||||||
|
const uploadHamaGambar = multer({
|
||||||
|
storage: storage,
|
||||||
|
fileFilter: fileFilter,
|
||||||
|
limits: {
|
||||||
|
fileSize: 5 * 1024 * 1024 // Batasi ukuran file maksimal 5MB
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = uploadHamaGambar;
|
|
@ -0,0 +1,47 @@
|
||||||
|
const multer = require('multer');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Path folder penyimpanan gambar
|
||||||
|
const uploadPath = path.join(__dirname, '../image_penyakit');
|
||||||
|
|
||||||
|
// Pastikan folder sudah ada, jika belum maka buat
|
||||||
|
if (!fs.existsSync(uploadPath)) {
|
||||||
|
fs.mkdirSync(uploadPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konfigurasi storage untuk multer
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function(req, file, cb) {
|
||||||
|
cb(null, uploadPath);
|
||||||
|
},
|
||||||
|
filename: function(req, file, cb) {
|
||||||
|
// Format nama file: hama-timestamp.extension
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
cb(null, `penyakit-${timestamp}${ext}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter untuk memastikan hanya file gambar yang diupload
|
||||||
|
const fileFilter = (req, file, cb) => {
|
||||||
|
// Izinkan hanya format gambar yang umum
|
||||||
|
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
|
||||||
|
|
||||||
|
if (allowedTypes.includes(file.mimetype)) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Format file tidak didukung! Hanya file JPG, JPEG, PNG, dan GIF yang diizinkan.'), false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inisialisasi multer dengan konfigurasi
|
||||||
|
const uploadHamaGambar = multer({
|
||||||
|
storage: storage,
|
||||||
|
fileFilter: fileFilter,
|
||||||
|
limits: {
|
||||||
|
fileSize: 5 * 1024 * 1024 // Batasi ukuran file maksimal 5MB
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = uploadHamaGambar;
|
|
@ -0,0 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.addColumn('penyakit', 'foto', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.removeColumn('penyakit', 'foto');
|
||||||
|
}
|
||||||
|
};
|
|
@ -28,6 +28,10 @@ module.exports =(sequelize) => {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
foto: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
|
||||||
|
fi
|
|
@ -0,0 +1,17 @@
|
||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Linus Unnebäck
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,44 @@
|
||||||
|
# `append-field`
|
||||||
|
|
||||||
|
A [W3C HTML JSON forms spec](http://www.w3.org/TR/html-json-forms/) compliant
|
||||||
|
field appender (for lack of a better name). Useful for people implementing
|
||||||
|
`application/x-www-form-urlencoded` and `multipart/form-data` parsers.
|
||||||
|
|
||||||
|
It works best on objects created with `Object.create(null)`. Otherwise it might
|
||||||
|
conflict with variables from the prototype (e.g. `hasOwnProperty`).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save append-field
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var appendField = require('append-field')
|
||||||
|
var obj = Object.create(null)
|
||||||
|
|
||||||
|
appendField(obj, 'pets[0][species]', 'Dahut')
|
||||||
|
appendField(obj, 'pets[0][name]', 'Hypatia')
|
||||||
|
appendField(obj, 'pets[1][species]', 'Felis Stultus')
|
||||||
|
appendField(obj, 'pets[1][name]', 'Billie')
|
||||||
|
|
||||||
|
console.log(obj)
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
{ pets:
|
||||||
|
[ { species: 'Dahut', name: 'Hypatia' },
|
||||||
|
{ species: 'Felis Stultus', name: 'Billie' } ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `appendField(store, key, value)`
|
||||||
|
|
||||||
|
Adds the field named `key` with the value `value` to the object `store`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
|
@ -0,0 +1,12 @@
|
||||||
|
var parsePath = require('./lib/parse-path')
|
||||||
|
var setValue = require('./lib/set-value')
|
||||||
|
|
||||||
|
function appendField (store, key, value) {
|
||||||
|
var steps = parsePath(key)
|
||||||
|
|
||||||
|
steps.reduce(function (context, step) {
|
||||||
|
return setValue(context, step, context[step.key], value)
|
||||||
|
}, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = appendField
|
|
@ -0,0 +1,53 @@
|
||||||
|
var reFirstKey = /^[^\[]*/
|
||||||
|
var reDigitPath = /^\[(\d+)\]/
|
||||||
|
var reNormalPath = /^\[([^\]]+)\]/
|
||||||
|
|
||||||
|
function parsePath (key) {
|
||||||
|
function failure () {
|
||||||
|
return [{ type: 'object', key: key, last: true }]
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstKey = reFirstKey.exec(key)[0]
|
||||||
|
if (!firstKey) return failure()
|
||||||
|
|
||||||
|
var len = key.length
|
||||||
|
var pos = firstKey.length
|
||||||
|
var tail = { type: 'object', key: firstKey }
|
||||||
|
var steps = [tail]
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
var m
|
||||||
|
|
||||||
|
if (key[pos] === '[' && key[pos + 1] === ']') {
|
||||||
|
pos += 2
|
||||||
|
tail.append = true
|
||||||
|
if (pos !== len) return failure()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m = reDigitPath.exec(key.substring(pos))
|
||||||
|
if (m !== null) {
|
||||||
|
pos += m[0].length
|
||||||
|
tail.nextType = 'array'
|
||||||
|
tail = { type: 'array', key: parseInt(m[1], 10) }
|
||||||
|
steps.push(tail)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m = reNormalPath.exec(key.substring(pos))
|
||||||
|
if (m !== null) {
|
||||||
|
pos += m[0].length
|
||||||
|
tail.nextType = 'object'
|
||||||
|
tail = { type: 'object', key: m[1] }
|
||||||
|
steps.push(tail)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
tail.last = true
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parsePath
|
|
@ -0,0 +1,64 @@
|
||||||
|
function valueType (value) {
|
||||||
|
if (value === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(value)) return 'array'
|
||||||
|
if (typeof value === 'object') return 'object'
|
||||||
|
return 'scalar'
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLastValue (context, step, currentValue, entryValue) {
|
||||||
|
switch (valueType(currentValue)) {
|
||||||
|
case 'undefined':
|
||||||
|
if (step.append) {
|
||||||
|
context[step.key] = [entryValue]
|
||||||
|
} else {
|
||||||
|
context[step.key] = entryValue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'array':
|
||||||
|
context[step.key].push(entryValue)
|
||||||
|
break
|
||||||
|
case 'object':
|
||||||
|
return setLastValue(currentValue, { type: 'object', key: '', last: true }, currentValue[''], entryValue)
|
||||||
|
case 'scalar':
|
||||||
|
context[step.key] = [context[step.key], entryValue]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue (context, step, currentValue, entryValue) {
|
||||||
|
if (step.last) return setLastValue(context, step, currentValue, entryValue)
|
||||||
|
|
||||||
|
var obj
|
||||||
|
switch (valueType(currentValue)) {
|
||||||
|
case 'undefined':
|
||||||
|
if (step.nextType === 'array') {
|
||||||
|
context[step.key] = []
|
||||||
|
} else {
|
||||||
|
context[step.key] = Object.create(null)
|
||||||
|
}
|
||||||
|
return context[step.key]
|
||||||
|
case 'object':
|
||||||
|
return context[step.key]
|
||||||
|
case 'array':
|
||||||
|
if (step.nextType === 'array') {
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = Object.create(null)
|
||||||
|
context[step.key] = obj
|
||||||
|
currentValue.forEach(function (item, i) {
|
||||||
|
if (item !== undefined) obj['' + i] = item
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
|
case 'scalar':
|
||||||
|
obj = Object.create(null)
|
||||||
|
obj[''] = currentValue
|
||||||
|
context[step.key] = obj
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = setValue
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "append-field",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Linus Unnebäck <linus@folkdatorn.se>",
|
||||||
|
"main": "index.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "^2.2.4",
|
||||||
|
"standard": "^6.0.5",
|
||||||
|
"testdata-w3c-json-form": "^0.2.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "standard && mocha"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "http://github.com/LinusU/node-append-field.git"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
|
||||||
|
var assert = require('assert')
|
||||||
|
var appendField = require('../')
|
||||||
|
var testData = require('testdata-w3c-json-form')
|
||||||
|
|
||||||
|
describe('Append Field', function () {
|
||||||
|
for (var test of testData) {
|
||||||
|
it('handles ' + test.name, function () {
|
||||||
|
var store = Object.create(null)
|
||||||
|
|
||||||
|
for (var field of test.fields) {
|
||||||
|
appendField(store, field.key, field.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(store, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016, 2018 Linus Unnebäck
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* eslint-disable node/no-deprecated-api */
|
||||||
|
|
||||||
|
var toString = Object.prototype.toString
|
||||||
|
|
||||||
|
var isModern = (
|
||||||
|
typeof Buffer !== 'undefined' &&
|
||||||
|
typeof Buffer.alloc === 'function' &&
|
||||||
|
typeof Buffer.allocUnsafe === 'function' &&
|
||||||
|
typeof Buffer.from === 'function'
|
||||||
|
)
|
||||||
|
|
||||||
|
function isArrayBuffer (input) {
|
||||||
|
return toString.call(input).slice(8, -1) === 'ArrayBuffer'
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromArrayBuffer (obj, byteOffset, length) {
|
||||||
|
byteOffset >>>= 0
|
||||||
|
|
||||||
|
var maxLength = obj.byteLength - byteOffset
|
||||||
|
|
||||||
|
if (maxLength < 0) {
|
||||||
|
throw new RangeError("'offset' is out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length === undefined) {
|
||||||
|
length = maxLength
|
||||||
|
} else {
|
||||||
|
length >>>= 0
|
||||||
|
|
||||||
|
if (length > maxLength) {
|
||||||
|
throw new RangeError("'length' is out of bounds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isModern
|
||||||
|
? Buffer.from(obj.slice(byteOffset, byteOffset + length))
|
||||||
|
: new Buffer(new Uint8Array(obj.slice(byteOffset, byteOffset + length)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromString (string, encoding) {
|
||||||
|
if (typeof encoding !== 'string' || encoding === '') {
|
||||||
|
encoding = 'utf8'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Buffer.isEncoding(encoding)) {
|
||||||
|
throw new TypeError('"encoding" must be a valid string encoding')
|
||||||
|
}
|
||||||
|
|
||||||
|
return isModern
|
||||||
|
? Buffer.from(string, encoding)
|
||||||
|
: new Buffer(string, encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bufferFrom (value, encodingOrOffset, length) {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
throw new TypeError('"value" argument must not be a number')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArrayBuffer(value)) {
|
||||||
|
return fromArrayBuffer(value, encodingOrOffset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return fromString(value, encodingOrOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isModern
|
||||||
|
? Buffer.from(value)
|
||||||
|
: new Buffer(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = bufferFrom
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "buffer-from",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "LinusU/buffer-from",
|
||||||
|
"files": [
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "standard && node test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"standard": "^12.0.1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"buffer",
|
||||||
|
"buffer from"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Buffer From
|
||||||
|
|
||||||
|
A [ponyfill](https://ponyfill.com) for `Buffer.from`, uses native implementation if available.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save buffer-from
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const bufferFrom = require('buffer-from')
|
||||||
|
|
||||||
|
console.log(bufferFrom([1, 2, 3, 4]))
|
||||||
|
//=> <Buffer 01 02 03 04>
|
||||||
|
|
||||||
|
const arr = new Uint8Array([1, 2, 3, 4])
|
||||||
|
console.log(bufferFrom(arr.buffer, 1, 2))
|
||||||
|
//=> <Buffer 02 03>
|
||||||
|
|
||||||
|
console.log(bufferFrom('test', 'utf8'))
|
||||||
|
//=> <Buffer 74 65 73 74>
|
||||||
|
|
||||||
|
const buf = bufferFrom('test')
|
||||||
|
console.log(bufferFrom(buf))
|
||||||
|
//=> <Buffer 74 65 73 74>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### bufferFrom(array)
|
||||||
|
|
||||||
|
- `array` <Array>
|
||||||
|
|
||||||
|
Allocates a new `Buffer` using an `array` of octets.
|
||||||
|
|
||||||
|
### bufferFrom(arrayBuffer[, byteOffset[, length]])
|
||||||
|
|
||||||
|
- `arrayBuffer` <ArrayBuffer> The `.buffer` property of a TypedArray or ArrayBuffer
|
||||||
|
- `byteOffset` <Integer> Where to start copying from `arrayBuffer`. **Default:** `0`
|
||||||
|
- `length` <Integer> How many bytes to copy from `arrayBuffer`. **Default:** `arrayBuffer.length - byteOffset`
|
||||||
|
|
||||||
|
When passed a reference to the `.buffer` property of a TypedArray instance, the
|
||||||
|
newly created `Buffer` will share the same allocated memory as the TypedArray.
|
||||||
|
|
||||||
|
The optional `byteOffset` and `length` arguments specify a memory range within
|
||||||
|
the `arrayBuffer` that will be shared by the `Buffer`.
|
||||||
|
|
||||||
|
### bufferFrom(buffer)
|
||||||
|
|
||||||
|
- `buffer` <Buffer> An existing `Buffer` to copy data from
|
||||||
|
|
||||||
|
Copies the passed `buffer` data onto a new `Buffer` instance.
|
||||||
|
|
||||||
|
### bufferFrom(string[, encoding])
|
||||||
|
|
||||||
|
- `string` <String> A string to encode.
|
||||||
|
- `encoding` <String> The encoding of `string`. **Default:** `'utf8'`
|
||||||
|
|
||||||
|
Creates a new `Buffer` containing the given JavaScript string `string`. If
|
||||||
|
provided, the `encoding` parameter identifies the character encoding of
|
||||||
|
`string`.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [buffer-alloc](https://github.com/LinusU/buffer-alloc) A ponyfill for `Buffer.alloc`
|
||||||
|
- [buffer-alloc-unsafe](https://github.com/LinusU/buffer-alloc-unsafe) A ponyfill for `Buffer.allocUnsafe`
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: '@mscdex/eslint-config',
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node-version: [10.16.0, 10.x, 12.x, 14.x, 16.x]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Install module
|
||||||
|
run: npm install
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
|
@ -0,0 +1,23 @@
|
||||||
|
name: lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: 16.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-js:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
- name: Install ESLint + ESLint configs/plugins
|
||||||
|
run: npm install --only=dev
|
||||||
|
- name: Lint files
|
||||||
|
run: npm run lint
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright Brian White. All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
|
@ -0,0 +1,191 @@
|
||||||
|
# Description
|
||||||
|
|
||||||
|
A node.js module for parsing incoming HTML form data.
|
||||||
|
|
||||||
|
Changes (breaking or otherwise) in v1.0.0 can be found [here](https://github.com/mscdex/busboy/issues/266).
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
* [node.js](http://nodejs.org/) -- v10.16.0 or newer
|
||||||
|
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
npm install busboy
|
||||||
|
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
* Parsing (multipart) with default options:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
http.createServer((req, res) => {
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
console.log('POST request');
|
||||||
|
const bb = busboy({ headers: req.headers });
|
||||||
|
bb.on('file', (name, file, info) => {
|
||||||
|
const { filename, encoding, mimeType } = info;
|
||||||
|
console.log(
|
||||||
|
`File [${name}]: filename: %j, encoding: %j, mimeType: %j`,
|
||||||
|
filename,
|
||||||
|
encoding,
|
||||||
|
mimeType
|
||||||
|
);
|
||||||
|
file.on('data', (data) => {
|
||||||
|
console.log(`File [${name}] got ${data.length} bytes`);
|
||||||
|
}).on('close', () => {
|
||||||
|
console.log(`File [${name}] done`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bb.on('field', (name, val, info) => {
|
||||||
|
console.log(`Field [${name}]: value: %j`, val);
|
||||||
|
});
|
||||||
|
bb.on('close', () => {
|
||||||
|
console.log('Done parsing form!');
|
||||||
|
res.writeHead(303, { Connection: 'close', Location: '/' });
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
req.pipe(bb);
|
||||||
|
} else if (req.method === 'GET') {
|
||||||
|
res.writeHead(200, { Connection: 'close' });
|
||||||
|
res.end(`
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="filefield"><br />
|
||||||
|
<input type="text" name="textfield"><br />
|
||||||
|
<input type="submit">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}).listen(8000, () => {
|
||||||
|
console.log('Listening for requests');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Example output:
|
||||||
|
//
|
||||||
|
// Listening for requests
|
||||||
|
// < ... form submitted ... >
|
||||||
|
// POST request
|
||||||
|
// File [filefield]: filename: "logo.jpg", encoding: "binary", mime: "image/jpeg"
|
||||||
|
// File [filefield] got 11912 bytes
|
||||||
|
// Field [textfield]: value: "testing! :-)"
|
||||||
|
// File [filefield] done
|
||||||
|
// Done parsing form!
|
||||||
|
```
|
||||||
|
|
||||||
|
* Save all incoming files to disk:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { randomFillSync } = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
const http = require('http');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
const random = (() => {
|
||||||
|
const buf = Buffer.alloc(16);
|
||||||
|
return () => randomFillSync(buf).toString('hex');
|
||||||
|
})();
|
||||||
|
|
||||||
|
http.createServer((req, res) => {
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const bb = busboy({ headers: req.headers });
|
||||||
|
bb.on('file', (name, file, info) => {
|
||||||
|
const saveTo = path.join(os.tmpdir(), `busboy-upload-${random()}`);
|
||||||
|
file.pipe(fs.createWriteStream(saveTo));
|
||||||
|
});
|
||||||
|
bb.on('close', () => {
|
||||||
|
res.writeHead(200, { 'Connection': 'close' });
|
||||||
|
res.end(`That's all folks!`);
|
||||||
|
});
|
||||||
|
req.pipe(bb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end();
|
||||||
|
}).listen(8000, () => {
|
||||||
|
console.log('Listening for requests');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
`busboy` exports a single function:
|
||||||
|
|
||||||
|
**( _function_ )**(< _object_ >config) - Creates and returns a new _Writable_ form parser stream.
|
||||||
|
|
||||||
|
* Valid `config` properties:
|
||||||
|
|
||||||
|
* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers.
|
||||||
|
|
||||||
|
* **highWaterMark** - _integer_ - highWaterMark to use for the parser stream. **Default:** node's _stream.Writable_ default.
|
||||||
|
|
||||||
|
* **fileHwm** - _integer_ - highWaterMark to use for individual file streams. **Default:** node's _stream.Readable_ default.
|
||||||
|
|
||||||
|
* **defCharset** - _string_ - Default character set to use when one isn't defined. **Default:** `'utf8'`.
|
||||||
|
|
||||||
|
* **defParamCharset** - _string_ - For multipart forms, the default character set to use for values of part header parameters (e.g. filename) that are not extended parameters (that contain an explicit charset). **Default:** `'latin1'`.
|
||||||
|
|
||||||
|
* **preservePath** - _boolean_ - If paths in filenames from file parts in a `'multipart/form-data'` request shall be preserved. **Default:** `false`.
|
||||||
|
|
||||||
|
* **limits** - _object_ - Various limits on incoming data. Valid properties are:
|
||||||
|
|
||||||
|
* **fieldNameSize** - _integer_ - Max field name size (in bytes). **Default:** `100`.
|
||||||
|
|
||||||
|
* **fieldSize** - _integer_ - Max field value size (in bytes). **Default:** `1048576` (1MB).
|
||||||
|
|
||||||
|
* **fields** - _integer_ - Max number of non-file fields. **Default:** `Infinity`.
|
||||||
|
|
||||||
|
* **fileSize** - _integer_ - For multipart forms, the max file size (in bytes). **Default:** `Infinity`.
|
||||||
|
|
||||||
|
* **files** - _integer_ - For multipart forms, the max number of file fields. **Default:** `Infinity`.
|
||||||
|
|
||||||
|
* **parts** - _integer_ - For multipart forms, the max number of parts (fields + files). **Default:** `Infinity`.
|
||||||
|
|
||||||
|
* **headerPairs** - _integer_ - For multipart forms, the max number of header key-value pairs to parse. **Default:** `2000` (same as node's http module).
|
||||||
|
|
||||||
|
This function can throw exceptions if there is something wrong with the values in `config`. For example, if the Content-Type in `headers` is missing entirely, is not a supported type, or is missing the boundary for `'multipart/form-data'` requests.
|
||||||
|
|
||||||
|
## (Special) Parser stream events
|
||||||
|
|
||||||
|
* **file**(< _string_ >name, < _Readable_ >stream, < _object_ >info) - Emitted for each new file found. `name` contains the form field name. `stream` is a _Readable_ stream containing the file's data. No transformations/conversions (e.g. base64 to raw binary) are done on the file's data. `info` contains the following properties:
|
||||||
|
|
||||||
|
* `filename` - _string_ - If supplied, this contains the file's filename. **WARNING:** You should almost _never_ use this value as-is (especially if you are using `preservePath: true` in your `config`) as it could contain malicious input. You are better off generating your own (safe) filenames, or at the very least using a hash of the filename.
|
||||||
|
|
||||||
|
* `encoding` - _string_ - The file's `'Content-Transfer-Encoding'` value.
|
||||||
|
|
||||||
|
* `mimeType` - _string_ - The file's `'Content-Type'` value.
|
||||||
|
|
||||||
|
**Note:** If you listen for this event, you should always consume the `stream` whether you care about its contents or not (you can simply do `stream.resume();` if you want to discard/skip the contents), otherwise the `'finish'`/`'close'` event will never fire on the busboy parser stream.
|
||||||
|
However, if you aren't accepting files, you can either simply not listen for the `'file'` event at all or set `limits.files` to `0`, and any/all files will be automatically skipped (these skipped files will still count towards any configured `limits.files` and `limits.parts` limits though).
|
||||||
|
|
||||||
|
**Note:** If a configured `limits.fileSize` limit was reached for a file, `stream` will both have a boolean property `truncated` set to `true` (best checked at the end of the stream) and emit a `'limit'` event to notify you when this happens.
|
||||||
|
|
||||||
|
* **field**(< _string_ >name, < _string_ >value, < _object_ >info) - Emitted for each new non-file field found. `name` contains the form field name. `value` contains the string value of the field. `info` contains the following properties:
|
||||||
|
|
||||||
|
* `nameTruncated` - _boolean_ - Whether `name` was truncated or not (due to a configured `limits.fieldNameSize` limit)
|
||||||
|
|
||||||
|
* `valueTruncated` - _boolean_ - Whether `value` was truncated or not (due to a configured `limits.fieldSize` limit)
|
||||||
|
|
||||||
|
* `encoding` - _string_ - The field's `'Content-Transfer-Encoding'` value.
|
||||||
|
|
||||||
|
* `mimeType` - _string_ - The field's `'Content-Type'` value.
|
||||||
|
|
||||||
|
* **partsLimit**() - Emitted when the configured `limits.parts` limit has been reached. No more `'file'` or `'field'` events will be emitted.
|
||||||
|
|
||||||
|
* **filesLimit**() - Emitted when the configured `limits.files` limit has been reached. No more `'file'` events will be emitted.
|
||||||
|
|
||||||
|
* **fieldsLimit**() - Emitted when the configured `limits.fields` limit has been reached. No more `'field'` events will be emitted.
|
149
backend/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createMultipartBuffers(boundary, sizes) {
|
||||||
|
const bufs = [];
|
||||||
|
for (let i = 0; i < sizes.length; ++i) {
|
||||||
|
const mb = sizes[i] * 1024 * 1024;
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}`,
|
||||||
|
`content-disposition: form-data; name="field${i + 1}"`,
|
||||||
|
'',
|
||||||
|
'0'.repeat(mb),
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
}
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}--`,
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
return bufs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary = '-----------------------------168072824752491622650073';
|
||||||
|
const buffers = createMultipartBuffers(boundary, [
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
20,
|
||||||
|
50,
|
||||||
|
]);
|
||||||
|
const calls = {
|
||||||
|
partBegin: 0,
|
||||||
|
headerField: 0,
|
||||||
|
headerValue: 0,
|
||||||
|
headerEnd: 0,
|
||||||
|
headersEnd: 0,
|
||||||
|
partData: 0,
|
||||||
|
partEnd: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('field', (name, val, info) => {
|
||||||
|
++calls.partBegin;
|
||||||
|
++calls.partData;
|
||||||
|
++calls.partEnd;
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const { MultipartParser } = require('formidable');
|
||||||
|
|
||||||
|
const parser = new MultipartParser();
|
||||||
|
parser.initWithBoundary(boundary);
|
||||||
|
parser.on('data', ({ name }) => {
|
||||||
|
++calls[name];
|
||||||
|
if (name === 'end')
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiparty': {
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
|
const { Form } = require('multiparty');
|
||||||
|
|
||||||
|
const form = new Form({
|
||||||
|
maxFieldsSize: Infinity,
|
||||||
|
maxFields: Infinity,
|
||||||
|
maxFilesSize: Infinity,
|
||||||
|
autoFields: false,
|
||||||
|
autoFiles: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = new Readable({ read: () => {} });
|
||||||
|
req.headers = {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function hijack(name, fn) {
|
||||||
|
const oldFn = form[name];
|
||||||
|
form[name] = function() {
|
||||||
|
fn();
|
||||||
|
return oldFn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hijack('onParseHeaderField', () => {
|
||||||
|
++calls.headerField;
|
||||||
|
});
|
||||||
|
hijack('onParseHeaderValue', () => {
|
||||||
|
++calls.headerValue;
|
||||||
|
});
|
||||||
|
hijack('onParsePartBegin', () => {
|
||||||
|
++calls.partBegin;
|
||||||
|
});
|
||||||
|
hijack('onParsePartData', () => {
|
||||||
|
++calls.partData;
|
||||||
|
});
|
||||||
|
hijack('onParsePartEnd', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
}).on('part', (p) => p.resume());
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
form.parse(req);
|
||||||
|
for (const buf of buffers)
|
||||||
|
req.push(buf);
|
||||||
|
req.push(null);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
143
backend/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createMultipartBuffers(boundary, sizes) {
|
||||||
|
const bufs = [];
|
||||||
|
for (let i = 0; i < sizes.length; ++i) {
|
||||||
|
const mb = sizes[i] * 1024 * 1024;
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}`,
|
||||||
|
`content-disposition: form-data; name="field${i + 1}"`,
|
||||||
|
'',
|
||||||
|
'0'.repeat(mb),
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
}
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}--`,
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
return bufs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary = '-----------------------------168072824752491622650073';
|
||||||
|
const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1));
|
||||||
|
const calls = {
|
||||||
|
partBegin: 0,
|
||||||
|
headerField: 0,
|
||||||
|
headerValue: 0,
|
||||||
|
headerEnd: 0,
|
||||||
|
headersEnd: 0,
|
||||||
|
partData: 0,
|
||||||
|
partEnd: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('field', (name, val, info) => {
|
||||||
|
++calls.partBegin;
|
||||||
|
++calls.partData;
|
||||||
|
++calls.partEnd;
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const { MultipartParser } = require('formidable');
|
||||||
|
|
||||||
|
const parser = new MultipartParser();
|
||||||
|
parser.initWithBoundary(boundary);
|
||||||
|
parser.on('data', ({ name }) => {
|
||||||
|
++calls[name];
|
||||||
|
if (name === 'end')
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiparty': {
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
|
const { Form } = require('multiparty');
|
||||||
|
|
||||||
|
const form = new Form({
|
||||||
|
maxFieldsSize: Infinity,
|
||||||
|
maxFields: Infinity,
|
||||||
|
maxFilesSize: Infinity,
|
||||||
|
autoFields: false,
|
||||||
|
autoFiles: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = new Readable({ read: () => {} });
|
||||||
|
req.headers = {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function hijack(name, fn) {
|
||||||
|
const oldFn = form[name];
|
||||||
|
form[name] = function() {
|
||||||
|
fn();
|
||||||
|
return oldFn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hijack('onParseHeaderField', () => {
|
||||||
|
++calls.headerField;
|
||||||
|
});
|
||||||
|
hijack('onParseHeaderValue', () => {
|
||||||
|
++calls.headerValue;
|
||||||
|
});
|
||||||
|
hijack('onParsePartBegin', () => {
|
||||||
|
++calls.partBegin;
|
||||||
|
});
|
||||||
|
hijack('onParsePartData', () => {
|
||||||
|
++calls.partData;
|
||||||
|
});
|
||||||
|
hijack('onParsePartEnd', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
}).on('part', (p) => p.resume());
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
form.parse(req);
|
||||||
|
for (const buf of buffers)
|
||||||
|
req.push(buf);
|
||||||
|
req.push(null);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
154
backend/node_modules/busboy/bench/bench-multipart-files-100mb-big.js
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createMultipartBuffers(boundary, sizes) {
|
||||||
|
const bufs = [];
|
||||||
|
for (let i = 0; i < sizes.length; ++i) {
|
||||||
|
const mb = sizes[i] * 1024 * 1024;
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}`,
|
||||||
|
`content-disposition: form-data; name="file${i + 1}"; `
|
||||||
|
+ `filename="random${i + 1}.bin"`,
|
||||||
|
'content-type: application/octet-stream',
|
||||||
|
'',
|
||||||
|
'0'.repeat(mb),
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
}
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}--`,
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
return bufs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary = '-----------------------------168072824752491622650073';
|
||||||
|
const buffers = createMultipartBuffers(boundary, [
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
20,
|
||||||
|
50,
|
||||||
|
]);
|
||||||
|
const calls = {
|
||||||
|
partBegin: 0,
|
||||||
|
headerField: 0,
|
||||||
|
headerValue: 0,
|
||||||
|
headerEnd: 0,
|
||||||
|
headersEnd: 0,
|
||||||
|
partData: 0,
|
||||||
|
partEnd: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('file', (name, stream, info) => {
|
||||||
|
++calls.partBegin;
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
++calls.partData;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const { MultipartParser } = require('formidable');
|
||||||
|
|
||||||
|
const parser = new MultipartParser();
|
||||||
|
parser.initWithBoundary(boundary);
|
||||||
|
parser.on('data', ({ name }) => {
|
||||||
|
++calls[name];
|
||||||
|
if (name === 'end')
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiparty': {
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
|
const { Form } = require('multiparty');
|
||||||
|
|
||||||
|
const form = new Form({
|
||||||
|
maxFieldsSize: Infinity,
|
||||||
|
maxFields: Infinity,
|
||||||
|
maxFilesSize: Infinity,
|
||||||
|
autoFields: false,
|
||||||
|
autoFiles: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = new Readable({ read: () => {} });
|
||||||
|
req.headers = {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function hijack(name, fn) {
|
||||||
|
const oldFn = form[name];
|
||||||
|
form[name] = function() {
|
||||||
|
fn();
|
||||||
|
return oldFn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hijack('onParseHeaderField', () => {
|
||||||
|
++calls.headerField;
|
||||||
|
});
|
||||||
|
hijack('onParseHeaderValue', () => {
|
||||||
|
++calls.headerValue;
|
||||||
|
});
|
||||||
|
hijack('onParsePartBegin', () => {
|
||||||
|
++calls.partBegin;
|
||||||
|
});
|
||||||
|
hijack('onParsePartData', () => {
|
||||||
|
++calls.partData;
|
||||||
|
});
|
||||||
|
hijack('onParsePartEnd', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
}).on('part', (p) => p.resume());
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
form.parse(req);
|
||||||
|
for (const buf of buffers)
|
||||||
|
req.push(buf);
|
||||||
|
req.push(null);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
148
backend/node_modules/busboy/bench/bench-multipart-files-100mb-small.js
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createMultipartBuffers(boundary, sizes) {
|
||||||
|
const bufs = [];
|
||||||
|
for (let i = 0; i < sizes.length; ++i) {
|
||||||
|
const mb = sizes[i] * 1024 * 1024;
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}`,
|
||||||
|
`content-disposition: form-data; name="file${i + 1}"; `
|
||||||
|
+ `filename="random${i + 1}.bin"`,
|
||||||
|
'content-type: application/octet-stream',
|
||||||
|
'',
|
||||||
|
'0'.repeat(mb),
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
}
|
||||||
|
bufs.push(Buffer.from([
|
||||||
|
`--${boundary}--`,
|
||||||
|
'',
|
||||||
|
].join('\r\n')));
|
||||||
|
return bufs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary = '-----------------------------168072824752491622650073';
|
||||||
|
const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1));
|
||||||
|
const calls = {
|
||||||
|
partBegin: 0,
|
||||||
|
headerField: 0,
|
||||||
|
headerValue: 0,
|
||||||
|
headerEnd: 0,
|
||||||
|
headersEnd: 0,
|
||||||
|
partData: 0,
|
||||||
|
partEnd: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('file', (name, stream, info) => {
|
||||||
|
++calls.partBegin;
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
++calls.partData;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const { MultipartParser } = require('formidable');
|
||||||
|
|
||||||
|
const parser = new MultipartParser();
|
||||||
|
parser.initWithBoundary(boundary);
|
||||||
|
parser.on('data', ({ name }) => {
|
||||||
|
++calls[name];
|
||||||
|
if (name === 'end')
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiparty': {
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
|
const { Form } = require('multiparty');
|
||||||
|
|
||||||
|
const form = new Form({
|
||||||
|
maxFieldsSize: Infinity,
|
||||||
|
maxFields: Infinity,
|
||||||
|
maxFilesSize: Infinity,
|
||||||
|
autoFields: false,
|
||||||
|
autoFiles: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = new Readable({ read: () => {} });
|
||||||
|
req.headers = {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function hijack(name, fn) {
|
||||||
|
const oldFn = form[name];
|
||||||
|
form[name] = function() {
|
||||||
|
fn();
|
||||||
|
return oldFn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hijack('onParseHeaderField', () => {
|
||||||
|
++calls.headerField;
|
||||||
|
});
|
||||||
|
hijack('onParseHeaderValue', () => {
|
||||||
|
++calls.headerValue;
|
||||||
|
});
|
||||||
|
hijack('onParsePartBegin', () => {
|
||||||
|
++calls.partBegin;
|
||||||
|
});
|
||||||
|
hijack('onParsePartData', () => {
|
||||||
|
++calls.partData;
|
||||||
|
});
|
||||||
|
hijack('onParsePartEnd', () => {
|
||||||
|
++calls.partEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
}).on('part', (p) => p.resume());
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
form.parse(req);
|
||||||
|
for (const buf of buffers)
|
||||||
|
req.push(buf);
|
||||||
|
req.push(null);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
101
backend/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const buffers = [
|
||||||
|
Buffer.from(
|
||||||
|
(new Array(100)).fill('').map((_, i) => `key${i}=value${i}`).join('&')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const calls = {
|
||||||
|
field: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = 3e3;
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
(function next() {
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('field', (name, val, info) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
if (--n === 0)
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
else
|
||||||
|
process.nextTick(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
})();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const QuerystringParser =
|
||||||
|
require('formidable/src/parsers/Querystring.js');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
(function next() {
|
||||||
|
const parser = new QuerystringParser();
|
||||||
|
parser.on('data', (obj) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.end;
|
||||||
|
if (--n === 0)
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
else
|
||||||
|
process.nextTick(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
})();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable-streaming': {
|
||||||
|
const QuerystringParser =
|
||||||
|
require('formidable/src/parsers/StreamingQuerystring.js');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
(function next() {
|
||||||
|
const parser = new QuerystringParser();
|
||||||
|
parser.on('data', (obj) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.end;
|
||||||
|
if (--n === 0)
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
else
|
||||||
|
process.nextTick(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
})();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
84
backend/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const buffers = [
|
||||||
|
Buffer.from(
|
||||||
|
(new Array(900)).fill('').map((_, i) => `key${i}=value${i}`).join('&')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const calls = {
|
||||||
|
field: 0,
|
||||||
|
end: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moduleName = process.argv[2];
|
||||||
|
switch (moduleName) {
|
||||||
|
case 'busboy': {
|
||||||
|
const busboy = require('busboy');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
const parser = busboy({
|
||||||
|
limits: {
|
||||||
|
fieldSizeLimit: Infinity,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
parser.on('field', (name, val, info) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('close', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable': {
|
||||||
|
const QuerystringParser =
|
||||||
|
require('formidable/src/parsers/Querystring.js');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
const parser = new QuerystringParser();
|
||||||
|
parser.on('data', (obj) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'formidable-streaming': {
|
||||||
|
const QuerystringParser =
|
||||||
|
require('formidable/src/parsers/StreamingQuerystring.js');
|
||||||
|
|
||||||
|
console.time(moduleName);
|
||||||
|
const parser = new QuerystringParser();
|
||||||
|
parser.on('data', (obj) => {
|
||||||
|
++calls.field;
|
||||||
|
}).on('end', () => {
|
||||||
|
++calls.end;
|
||||||
|
console.timeEnd(moduleName);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const buf of buffers)
|
||||||
|
parser.write(buf);
|
||||||
|
parser.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (moduleName === undefined)
|
||||||
|
console.error('Missing parser module name');
|
||||||
|
else
|
||||||
|
console.error(`Invalid parser module name: ${moduleName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { parseContentType } = require('./utils.js');
|
||||||
|
|
||||||
|
function getInstance(cfg) {
|
||||||
|
const headers = cfg.headers;
|
||||||
|
const conType = parseContentType(headers['content-type']);
|
||||||
|
if (!conType)
|
||||||
|
throw new Error('Malformed content type');
|
||||||
|
|
||||||
|
for (const type of TYPES) {
|
||||||
|
const matched = type.detect(conType);
|
||||||
|
if (!matched)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const instanceCfg = {
|
||||||
|
limits: cfg.limits,
|
||||||
|
headers,
|
||||||
|
conType,
|
||||||
|
highWaterMark: undefined,
|
||||||
|
fileHwm: undefined,
|
||||||
|
defCharset: undefined,
|
||||||
|
defParamCharset: undefined,
|
||||||
|
preservePath: false,
|
||||||
|
};
|
||||||
|
if (cfg.highWaterMark)
|
||||||
|
instanceCfg.highWaterMark = cfg.highWaterMark;
|
||||||
|
if (cfg.fileHwm)
|
||||||
|
instanceCfg.fileHwm = cfg.fileHwm;
|
||||||
|
instanceCfg.defCharset = cfg.defCharset;
|
||||||
|
instanceCfg.defParamCharset = cfg.defParamCharset;
|
||||||
|
instanceCfg.preservePath = cfg.preservePath;
|
||||||
|
return new type(instanceCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unsupported content type: ${headers['content-type']}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: types are explicitly listed here for easier bundling
|
||||||
|
// See: https://github.com/mscdex/busboy/issues/121
|
||||||
|
const TYPES = [
|
||||||
|
require('./types/multipart'),
|
||||||
|
require('./types/urlencoded'),
|
||||||
|
].filter(function(typemod) { return typeof typemod.detect === 'function'; });
|
||||||
|
|
||||||
|
module.exports = (cfg) => {
|
||||||
|
if (typeof cfg !== 'object' || cfg === null)
|
||||||
|
cfg = {};
|
||||||
|
|
||||||
|
if (typeof cfg.headers !== 'object'
|
||||||
|
|| cfg.headers === null
|
||||||
|
|| typeof cfg.headers['content-type'] !== 'string') {
|
||||||
|
throw new Error('Missing Content-Type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInstance(cfg);
|
||||||
|
};
|
|
@ -0,0 +1,653 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Readable, Writable } = require('stream');
|
||||||
|
|
||||||
|
const StreamSearch = require('streamsearch');
|
||||||
|
|
||||||
|
const {
|
||||||
|
basename,
|
||||||
|
convertToUTF8,
|
||||||
|
getDecoder,
|
||||||
|
parseContentType,
|
||||||
|
parseDisposition,
|
||||||
|
} = require('../utils.js');
|
||||||
|
|
||||||
|
const BUF_CRLF = Buffer.from('\r\n');
|
||||||
|
const BUF_CR = Buffer.from('\r');
|
||||||
|
const BUF_DASH = Buffer.from('-');
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
const MAX_HEADER_PAIRS = 2000; // From node
|
||||||
|
const MAX_HEADER_SIZE = 16 * 1024; // From node (its default value)
|
||||||
|
|
||||||
|
const HPARSER_NAME = 0;
|
||||||
|
const HPARSER_PRE_OWS = 1;
|
||||||
|
const HPARSER_VALUE = 2;
|
||||||
|
class HeaderParser {
|
||||||
|
constructor(cb) {
|
||||||
|
this.header = Object.create(null);
|
||||||
|
this.pairCount = 0;
|
||||||
|
this.byteCount = 0;
|
||||||
|
this.state = HPARSER_NAME;
|
||||||
|
this.name = '';
|
||||||
|
this.value = '';
|
||||||
|
this.crlf = 0;
|
||||||
|
this.cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.header = Object.create(null);
|
||||||
|
this.pairCount = 0;
|
||||||
|
this.byteCount = 0;
|
||||||
|
this.state = HPARSER_NAME;
|
||||||
|
this.name = '';
|
||||||
|
this.value = '';
|
||||||
|
this.crlf = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(chunk, pos, end) {
|
||||||
|
let start = pos;
|
||||||
|
while (pos < end) {
|
||||||
|
switch (this.state) {
|
||||||
|
case HPARSER_NAME: {
|
||||||
|
let done = false;
|
||||||
|
for (; pos < end; ++pos) {
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
const code = chunk[pos];
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
if (code !== 58/* ':' */)
|
||||||
|
return -1;
|
||||||
|
this.name += chunk.latin1Slice(start, pos);
|
||||||
|
if (this.name.length === 0)
|
||||||
|
return -1;
|
||||||
|
++pos;
|
||||||
|
done = true;
|
||||||
|
this.state = HPARSER_PRE_OWS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) {
|
||||||
|
this.name += chunk.latin1Slice(start, pos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// FALLTHROUGH
|
||||||
|
}
|
||||||
|
case HPARSER_PRE_OWS: {
|
||||||
|
// Skip optional whitespace
|
||||||
|
let done = false;
|
||||||
|
for (; pos < end; ++pos) {
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
const code = chunk[pos];
|
||||||
|
if (code !== 32/* ' ' */ && code !== 9/* '\t' */) {
|
||||||
|
start = pos;
|
||||||
|
done = true;
|
||||||
|
this.state = HPARSER_VALUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done)
|
||||||
|
break;
|
||||||
|
// FALLTHROUGH
|
||||||
|
}
|
||||||
|
case HPARSER_VALUE:
|
||||||
|
switch (this.crlf) {
|
||||||
|
case 0: // Nothing yet
|
||||||
|
for (; pos < end; ++pos) {
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
const code = chunk[pos];
|
||||||
|
if (FIELD_VCHAR[code] !== 1) {
|
||||||
|
if (code !== 13/* '\r' */)
|
||||||
|
return -1;
|
||||||
|
++this.crlf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.value += chunk.latin1Slice(start, pos++);
|
||||||
|
break;
|
||||||
|
case 1: // Received CR
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
if (chunk[pos++] !== 10/* '\n' */)
|
||||||
|
return -1;
|
||||||
|
++this.crlf;
|
||||||
|
break;
|
||||||
|
case 2: { // Received CR LF
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
const code = chunk[pos];
|
||||||
|
if (code === 32/* ' ' */ || code === 9/* '\t' */) {
|
||||||
|
// Folded value
|
||||||
|
start = pos;
|
||||||
|
this.crlf = 0;
|
||||||
|
} else {
|
||||||
|
if (++this.pairCount < MAX_HEADER_PAIRS) {
|
||||||
|
this.name = this.name.toLowerCase();
|
||||||
|
if (this.header[this.name] === undefined)
|
||||||
|
this.header[this.name] = [this.value];
|
||||||
|
else
|
||||||
|
this.header[this.name].push(this.value);
|
||||||
|
}
|
||||||
|
if (code === 13/* '\r' */) {
|
||||||
|
++this.crlf;
|
||||||
|
++pos;
|
||||||
|
} else {
|
||||||
|
// Assume start of next header field name
|
||||||
|
start = pos;
|
||||||
|
this.crlf = 0;
|
||||||
|
this.state = HPARSER_NAME;
|
||||||
|
this.name = '';
|
||||||
|
this.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: { // Received CR LF CR
|
||||||
|
if (this.byteCount === MAX_HEADER_SIZE)
|
||||||
|
return -1;
|
||||||
|
++this.byteCount;
|
||||||
|
if (chunk[pos++] !== 10/* '\n' */)
|
||||||
|
return -1;
|
||||||
|
// End of header
|
||||||
|
const header = this.header;
|
||||||
|
this.reset();
|
||||||
|
this.cb(header);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileStream extends Readable {
|
||||||
|
constructor(opts, owner) {
|
||||||
|
super(opts);
|
||||||
|
this.truncated = false;
|
||||||
|
this._readcb = null;
|
||||||
|
this.once('end', () => {
|
||||||
|
// We need to make sure that we call any outstanding _writecb() that is
|
||||||
|
// associated with this file so that processing of the rest of the form
|
||||||
|
// can continue. This may not happen if the file stream ends right after
|
||||||
|
// backpressure kicks in, so we force it here.
|
||||||
|
this._read();
|
||||||
|
if (--owner._fileEndsLeft === 0 && owner._finalcb) {
|
||||||
|
const cb = owner._finalcb;
|
||||||
|
owner._finalcb = null;
|
||||||
|
// Make sure other 'end' event handlers get a chance to be executed
|
||||||
|
// before busboy's 'finish' event is emitted
|
||||||
|
process.nextTick(cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_read(n) {
|
||||||
|
const cb = this._readcb;
|
||||||
|
if (cb) {
|
||||||
|
this._readcb = null;
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ignoreData = {
|
||||||
|
push: (chunk, pos) => {},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function callAndUnsetCb(self, err) {
|
||||||
|
const cb = self._writecb;
|
||||||
|
self._writecb = null;
|
||||||
|
if (err)
|
||||||
|
self.destroy(err);
|
||||||
|
else if (cb)
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
function nullDecoder(val, hint) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Multipart extends Writable {
|
||||||
|
constructor(cfg) {
|
||||||
|
const streamOpts = {
|
||||||
|
autoDestroy: true,
|
||||||
|
emitClose: true,
|
||||||
|
highWaterMark: (typeof cfg.highWaterMark === 'number'
|
||||||
|
? cfg.highWaterMark
|
||||||
|
: undefined),
|
||||||
|
};
|
||||||
|
super(streamOpts);
|
||||||
|
|
||||||
|
if (!cfg.conType.params || typeof cfg.conType.params.boundary !== 'string')
|
||||||
|
throw new Error('Multipart: Boundary not found');
|
||||||
|
|
||||||
|
const boundary = cfg.conType.params.boundary;
|
||||||
|
const paramDecoder = (typeof cfg.defParamCharset === 'string'
|
||||||
|
&& cfg.defParamCharset
|
||||||
|
? getDecoder(cfg.defParamCharset)
|
||||||
|
: nullDecoder);
|
||||||
|
const defCharset = (cfg.defCharset || 'utf8');
|
||||||
|
const preservePath = cfg.preservePath;
|
||||||
|
const fileOpts = {
|
||||||
|
autoDestroy: true,
|
||||||
|
emitClose: true,
|
||||||
|
highWaterMark: (typeof cfg.fileHwm === 'number'
|
||||||
|
? cfg.fileHwm
|
||||||
|
: undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
const limits = cfg.limits;
|
||||||
|
const fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
|
||||||
|
? limits.fieldSize
|
||||||
|
: 1 * 1024 * 1024);
|
||||||
|
const fileSizeLimit = (limits && typeof limits.fileSize === 'number'
|
||||||
|
? limits.fileSize
|
||||||
|
: Infinity);
|
||||||
|
const filesLimit = (limits && typeof limits.files === 'number'
|
||||||
|
? limits.files
|
||||||
|
: Infinity);
|
||||||
|
const fieldsLimit = (limits && typeof limits.fields === 'number'
|
||||||
|
? limits.fields
|
||||||
|
: Infinity);
|
||||||
|
const partsLimit = (limits && typeof limits.parts === 'number'
|
||||||
|
? limits.parts
|
||||||
|
: Infinity);
|
||||||
|
|
||||||
|
let parts = -1; // Account for initial boundary
|
||||||
|
let fields = 0;
|
||||||
|
let files = 0;
|
||||||
|
let skipPart = false;
|
||||||
|
|
||||||
|
this._fileEndsLeft = 0;
|
||||||
|
this._fileStream = undefined;
|
||||||
|
this._complete = false;
|
||||||
|
let fileSize = 0;
|
||||||
|
|
||||||
|
let field;
|
||||||
|
let fieldSize = 0;
|
||||||
|
let partCharset;
|
||||||
|
let partEncoding;
|
||||||
|
let partType;
|
||||||
|
let partName;
|
||||||
|
let partTruncated = false;
|
||||||
|
|
||||||
|
let hitFilesLimit = false;
|
||||||
|
let hitFieldsLimit = false;
|
||||||
|
|
||||||
|
this._hparser = null;
|
||||||
|
const hparser = new HeaderParser((header) => {
|
||||||
|
this._hparser = null;
|
||||||
|
skipPart = false;
|
||||||
|
|
||||||
|
partType = 'text/plain';
|
||||||
|
partCharset = defCharset;
|
||||||
|
partEncoding = '7bit';
|
||||||
|
partName = undefined;
|
||||||
|
partTruncated = false;
|
||||||
|
|
||||||
|
let filename;
|
||||||
|
if (!header['content-disposition']) {
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const disp = parseDisposition(header['content-disposition'][0],
|
||||||
|
paramDecoder);
|
||||||
|
if (!disp || disp.type !== 'form-data') {
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disp.params) {
|
||||||
|
if (disp.params.name)
|
||||||
|
partName = disp.params.name;
|
||||||
|
|
||||||
|
if (disp.params['filename*'])
|
||||||
|
filename = disp.params['filename*'];
|
||||||
|
else if (disp.params.filename)
|
||||||
|
filename = disp.params.filename;
|
||||||
|
|
||||||
|
if (filename !== undefined && !preservePath)
|
||||||
|
filename = basename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header['content-type']) {
|
||||||
|
const conType = parseContentType(header['content-type'][0]);
|
||||||
|
if (conType) {
|
||||||
|
partType = `${conType.type}/${conType.subtype}`;
|
||||||
|
if (conType.params && typeof conType.params.charset === 'string')
|
||||||
|
partCharset = conType.params.charset.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header['content-transfer-encoding'])
|
||||||
|
partEncoding = header['content-transfer-encoding'][0].toLowerCase();
|
||||||
|
|
||||||
|
if (partType === 'application/octet-stream' || filename !== undefined) {
|
||||||
|
// File
|
||||||
|
|
||||||
|
if (files === filesLimit) {
|
||||||
|
if (!hitFilesLimit) {
|
||||||
|
hitFilesLimit = true;
|
||||||
|
this.emit('filesLimit');
|
||||||
|
}
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++files;
|
||||||
|
|
||||||
|
if (this.listenerCount('file') === 0) {
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize = 0;
|
||||||
|
this._fileStream = new FileStream(fileOpts, this);
|
||||||
|
++this._fileEndsLeft;
|
||||||
|
this.emit(
|
||||||
|
'file',
|
||||||
|
partName,
|
||||||
|
this._fileStream,
|
||||||
|
{ filename,
|
||||||
|
encoding: partEncoding,
|
||||||
|
mimeType: partType }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Non-file
|
||||||
|
|
||||||
|
if (fields === fieldsLimit) {
|
||||||
|
if (!hitFieldsLimit) {
|
||||||
|
hitFieldsLimit = true;
|
||||||
|
this.emit('fieldsLimit');
|
||||||
|
}
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++fields;
|
||||||
|
|
||||||
|
if (this.listenerCount('field') === 0) {
|
||||||
|
skipPart = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
field = [];
|
||||||
|
fieldSize = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let matchPostBoundary = 0;
|
||||||
|
const ssCb = (isMatch, data, start, end, isDataSafe) => {
|
||||||
|
retrydata:
|
||||||
|
while (data) {
|
||||||
|
if (this._hparser !== null) {
|
||||||
|
const ret = this._hparser.push(data, start, end);
|
||||||
|
if (ret === -1) {
|
||||||
|
this._hparser = null;
|
||||||
|
hparser.reset();
|
||||||
|
this.emit('error', new Error('Malformed part header'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (matchPostBoundary !== 0) {
|
||||||
|
if (matchPostBoundary === 1) {
|
||||||
|
switch (data[start]) {
|
||||||
|
case 45: // '-'
|
||||||
|
// Try matching '--' after boundary
|
||||||
|
matchPostBoundary = 2;
|
||||||
|
++start;
|
||||||
|
break;
|
||||||
|
case 13: // '\r'
|
||||||
|
// Try matching CR LF before header
|
||||||
|
matchPostBoundary = 3;
|
||||||
|
++start;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
matchPostBoundary = 0;
|
||||||
|
}
|
||||||
|
if (start === end)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchPostBoundary === 2) {
|
||||||
|
matchPostBoundary = 0;
|
||||||
|
if (data[start] === 45/* '-' */) {
|
||||||
|
// End of multipart data
|
||||||
|
this._complete = true;
|
||||||
|
this._bparser = ignoreData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We saw something other than '-', so put the dash we consumed
|
||||||
|
// "back"
|
||||||
|
const writecb = this._writecb;
|
||||||
|
this._writecb = noop;
|
||||||
|
ssCb(false, BUF_DASH, 0, 1, false);
|
||||||
|
this._writecb = writecb;
|
||||||
|
} else if (matchPostBoundary === 3) {
|
||||||
|
matchPostBoundary = 0;
|
||||||
|
if (data[start] === 10/* '\n' */) {
|
||||||
|
++start;
|
||||||
|
if (parts >= partsLimit)
|
||||||
|
break;
|
||||||
|
// Prepare the header parser
|
||||||
|
this._hparser = hparser;
|
||||||
|
if (start === end)
|
||||||
|
break;
|
||||||
|
// Process the remaining data as a header
|
||||||
|
continue retrydata;
|
||||||
|
} else {
|
||||||
|
// We saw something other than LF, so put the CR we consumed
|
||||||
|
// "back"
|
||||||
|
const writecb = this._writecb;
|
||||||
|
this._writecb = noop;
|
||||||
|
ssCb(false, BUF_CR, 0, 1, false);
|
||||||
|
this._writecb = writecb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipPart) {
|
||||||
|
if (this._fileStream) {
|
||||||
|
let chunk;
|
||||||
|
const actualLen = Math.min(end - start, fileSizeLimit - fileSize);
|
||||||
|
if (!isDataSafe) {
|
||||||
|
chunk = Buffer.allocUnsafe(actualLen);
|
||||||
|
data.copy(chunk, 0, start, start + actualLen);
|
||||||
|
} else {
|
||||||
|
chunk = data.slice(start, start + actualLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize += chunk.length;
|
||||||
|
if (fileSize === fileSizeLimit) {
|
||||||
|
if (chunk.length > 0)
|
||||||
|
this._fileStream.push(chunk);
|
||||||
|
this._fileStream.emit('limit');
|
||||||
|
this._fileStream.truncated = true;
|
||||||
|
skipPart = true;
|
||||||
|
} else if (!this._fileStream.push(chunk)) {
|
||||||
|
if (this._writecb)
|
||||||
|
this._fileStream._readcb = this._writecb;
|
||||||
|
this._writecb = null;
|
||||||
|
}
|
||||||
|
} else if (field !== undefined) {
|
||||||
|
let chunk;
|
||||||
|
const actualLen = Math.min(
|
||||||
|
end - start,
|
||||||
|
fieldSizeLimit - fieldSize
|
||||||
|
);
|
||||||
|
if (!isDataSafe) {
|
||||||
|
chunk = Buffer.allocUnsafe(actualLen);
|
||||||
|
data.copy(chunk, 0, start, start + actualLen);
|
||||||
|
} else {
|
||||||
|
chunk = data.slice(start, start + actualLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSize += actualLen;
|
||||||
|
field.push(chunk);
|
||||||
|
if (fieldSize === fieldSizeLimit) {
|
||||||
|
skipPart = true;
|
||||||
|
partTruncated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
matchPostBoundary = 1;
|
||||||
|
|
||||||
|
if (this._fileStream) {
|
||||||
|
// End the active file stream if the previous part was a file
|
||||||
|
this._fileStream.push(null);
|
||||||
|
this._fileStream = null;
|
||||||
|
} else if (field !== undefined) {
|
||||||
|
let data;
|
||||||
|
switch (field.length) {
|
||||||
|
case 0:
|
||||||
|
data = '';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
data = convertToUTF8(field[0], partCharset, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data = convertToUTF8(
|
||||||
|
Buffer.concat(field, fieldSize),
|
||||||
|
partCharset,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
field = undefined;
|
||||||
|
fieldSize = 0;
|
||||||
|
this.emit(
|
||||||
|
'field',
|
||||||
|
partName,
|
||||||
|
data,
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: partTruncated,
|
||||||
|
encoding: partEncoding,
|
||||||
|
mimeType: partType }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++parts === partsLimit)
|
||||||
|
this.emit('partsLimit');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._bparser = new StreamSearch(`\r\n--${boundary}`, ssCb);
|
||||||
|
|
||||||
|
this._writecb = null;
|
||||||
|
this._finalcb = null;
|
||||||
|
|
||||||
|
// Just in case there is no preamble
|
||||||
|
this.write(BUF_CRLF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static detect(conType) {
|
||||||
|
return (conType.type === 'multipart' && conType.subtype === 'form-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
_write(chunk, enc, cb) {
|
||||||
|
this._writecb = cb;
|
||||||
|
this._bparser.push(chunk, 0);
|
||||||
|
if (this._writecb)
|
||||||
|
callAndUnsetCb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroy(err, cb) {
|
||||||
|
this._hparser = null;
|
||||||
|
this._bparser = ignoreData;
|
||||||
|
if (!err)
|
||||||
|
err = checkEndState(this);
|
||||||
|
const fileStream = this._fileStream;
|
||||||
|
if (fileStream) {
|
||||||
|
this._fileStream = null;
|
||||||
|
fileStream.destroy(err);
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
_final(cb) {
|
||||||
|
this._bparser.destroy();
|
||||||
|
if (!this._complete)
|
||||||
|
return cb(new Error('Unexpected end of form'));
|
||||||
|
if (this._fileEndsLeft)
|
||||||
|
this._finalcb = finalcb.bind(null, this, cb);
|
||||||
|
else
|
||||||
|
finalcb(this, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalcb(self, cb, err) {
|
||||||
|
if (err)
|
||||||
|
return cb(err);
|
||||||
|
err = checkEndState(self);
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkEndState(self) {
|
||||||
|
if (self._hparser)
|
||||||
|
return new Error('Malformed part header');
|
||||||
|
const fileStream = self._fileStream;
|
||||||
|
if (fileStream) {
|
||||||
|
self._fileStream = null;
|
||||||
|
fileStream.destroy(new Error('Unexpected end of file'));
|
||||||
|
}
|
||||||
|
if (!self._complete)
|
||||||
|
return new Error('Unexpected end of form');
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOKEN = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
const FIELD_VCHAR = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = Multipart;
|
|
@ -0,0 +1,350 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Writable } = require('stream');
|
||||||
|
|
||||||
|
const { getDecoder } = require('../utils.js');
|
||||||
|
|
||||||
|
class URLEncoded extends Writable {
|
||||||
|
constructor(cfg) {
|
||||||
|
const streamOpts = {
|
||||||
|
autoDestroy: true,
|
||||||
|
emitClose: true,
|
||||||
|
highWaterMark: (typeof cfg.highWaterMark === 'number'
|
||||||
|
? cfg.highWaterMark
|
||||||
|
: undefined),
|
||||||
|
};
|
||||||
|
super(streamOpts);
|
||||||
|
|
||||||
|
let charset = (cfg.defCharset || 'utf8');
|
||||||
|
if (cfg.conType.params && typeof cfg.conType.params.charset === 'string')
|
||||||
|
charset = cfg.conType.params.charset;
|
||||||
|
|
||||||
|
this.charset = charset;
|
||||||
|
|
||||||
|
const limits = cfg.limits;
|
||||||
|
this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
|
||||||
|
? limits.fieldSize
|
||||||
|
: 1 * 1024 * 1024);
|
||||||
|
this.fieldsLimit = (limits && typeof limits.fields === 'number'
|
||||||
|
? limits.fields
|
||||||
|
: Infinity);
|
||||||
|
this.fieldNameSizeLimit = (
|
||||||
|
limits && typeof limits.fieldNameSize === 'number'
|
||||||
|
? limits.fieldNameSize
|
||||||
|
: 100
|
||||||
|
);
|
||||||
|
|
||||||
|
this._inKey = true;
|
||||||
|
this._keyTrunc = false;
|
||||||
|
this._valTrunc = false;
|
||||||
|
this._bytesKey = 0;
|
||||||
|
this._bytesVal = 0;
|
||||||
|
this._fields = 0;
|
||||||
|
this._key = '';
|
||||||
|
this._val = '';
|
||||||
|
this._byte = -2;
|
||||||
|
this._lastPos = 0;
|
||||||
|
this._encode = 0;
|
||||||
|
this._decoder = getDecoder(charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static detect(conType) {
|
||||||
|
return (conType.type === 'application'
|
||||||
|
&& conType.subtype === 'x-www-form-urlencoded');
|
||||||
|
}
|
||||||
|
|
||||||
|
_write(chunk, enc, cb) {
|
||||||
|
if (this._fields >= this.fieldsLimit)
|
||||||
|
return cb();
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const len = chunk.length;
|
||||||
|
this._lastPos = 0;
|
||||||
|
|
||||||
|
// Check if we last ended mid-percent-encoded byte
|
||||||
|
if (this._byte !== -2) {
|
||||||
|
i = readPctEnc(this, chunk, i, len);
|
||||||
|
if (i === -1)
|
||||||
|
return cb(new Error('Malformed urlencoded form'));
|
||||||
|
if (i >= len)
|
||||||
|
return cb();
|
||||||
|
if (this._inKey)
|
||||||
|
++this._bytesKey;
|
||||||
|
else
|
||||||
|
++this._bytesVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
main:
|
||||||
|
while (i < len) {
|
||||||
|
if (this._inKey) {
|
||||||
|
// Parsing key
|
||||||
|
|
||||||
|
i = skipKeyBytes(this, chunk, i, len);
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
switch (chunk[i]) {
|
||||||
|
case 61: // '='
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._key += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._lastPos = ++i;
|
||||||
|
this._key = this._decoder(this._key, this._encode);
|
||||||
|
this._encode = 0;
|
||||||
|
this._inKey = false;
|
||||||
|
continue main;
|
||||||
|
case 38: // '&'
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._key += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._lastPos = ++i;
|
||||||
|
this._key = this._decoder(this._key, this._encode);
|
||||||
|
this._encode = 0;
|
||||||
|
if (this._bytesKey > 0) {
|
||||||
|
this.emit(
|
||||||
|
'field',
|
||||||
|
this._key,
|
||||||
|
'',
|
||||||
|
{ nameTruncated: this._keyTrunc,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: this.charset,
|
||||||
|
mimeType: 'text/plain' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._key = '';
|
||||||
|
this._val = '';
|
||||||
|
this._keyTrunc = false;
|
||||||
|
this._valTrunc = false;
|
||||||
|
this._bytesKey = 0;
|
||||||
|
this._bytesVal = 0;
|
||||||
|
if (++this._fields >= this.fieldsLimit) {
|
||||||
|
this.emit('fieldsLimit');
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case 43: // '+'
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._key += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._key += ' ';
|
||||||
|
this._lastPos = i + 1;
|
||||||
|
break;
|
||||||
|
case 37: // '%'
|
||||||
|
if (this._encode === 0)
|
||||||
|
this._encode = 1;
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._key += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._lastPos = i + 1;
|
||||||
|
this._byte = -1;
|
||||||
|
i = readPctEnc(this, chunk, i + 1, len);
|
||||||
|
if (i === -1)
|
||||||
|
return cb(new Error('Malformed urlencoded form'));
|
||||||
|
if (i >= len)
|
||||||
|
return cb();
|
||||||
|
++this._bytesKey;
|
||||||
|
i = skipKeyBytes(this, chunk, i, len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
++this._bytesKey;
|
||||||
|
i = skipKeyBytes(this, chunk, i, len);
|
||||||
|
}
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._key += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
} else {
|
||||||
|
// Parsing value
|
||||||
|
|
||||||
|
i = skipValBytes(this, chunk, i, len);
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
switch (chunk[i]) {
|
||||||
|
case 38: // '&'
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._val += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._lastPos = ++i;
|
||||||
|
this._inKey = true;
|
||||||
|
this._val = this._decoder(this._val, this._encode);
|
||||||
|
this._encode = 0;
|
||||||
|
if (this._bytesKey > 0 || this._bytesVal > 0) {
|
||||||
|
this.emit(
|
||||||
|
'field',
|
||||||
|
this._key,
|
||||||
|
this._val,
|
||||||
|
{ nameTruncated: this._keyTrunc,
|
||||||
|
valueTruncated: this._valTrunc,
|
||||||
|
encoding: this.charset,
|
||||||
|
mimeType: 'text/plain' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._key = '';
|
||||||
|
this._val = '';
|
||||||
|
this._keyTrunc = false;
|
||||||
|
this._valTrunc = false;
|
||||||
|
this._bytesKey = 0;
|
||||||
|
this._bytesVal = 0;
|
||||||
|
if (++this._fields >= this.fieldsLimit) {
|
||||||
|
this.emit('fieldsLimit');
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
continue main;
|
||||||
|
case 43: // '+'
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._val += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._val += ' ';
|
||||||
|
this._lastPos = i + 1;
|
||||||
|
break;
|
||||||
|
case 37: // '%'
|
||||||
|
if (this._encode === 0)
|
||||||
|
this._encode = 1;
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._val += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
this._lastPos = i + 1;
|
||||||
|
this._byte = -1;
|
||||||
|
i = readPctEnc(this, chunk, i + 1, len);
|
||||||
|
if (i === -1)
|
||||||
|
return cb(new Error('Malformed urlencoded form'));
|
||||||
|
if (i >= len)
|
||||||
|
return cb();
|
||||||
|
++this._bytesVal;
|
||||||
|
i = skipValBytes(this, chunk, i, len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
++this._bytesVal;
|
||||||
|
i = skipValBytes(this, chunk, i, len);
|
||||||
|
}
|
||||||
|
if (this._lastPos < i)
|
||||||
|
this._val += chunk.latin1Slice(this._lastPos, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
_final(cb) {
|
||||||
|
if (this._byte !== -2)
|
||||||
|
return cb(new Error('Malformed urlencoded form'));
|
||||||
|
if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) {
|
||||||
|
if (this._inKey)
|
||||||
|
this._key = this._decoder(this._key, this._encode);
|
||||||
|
else
|
||||||
|
this._val = this._decoder(this._val, this._encode);
|
||||||
|
this.emit(
|
||||||
|
'field',
|
||||||
|
this._key,
|
||||||
|
this._val,
|
||||||
|
{ nameTruncated: this._keyTrunc,
|
||||||
|
valueTruncated: this._valTrunc,
|
||||||
|
encoding: this.charset,
|
||||||
|
mimeType: 'text/plain' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPctEnc(self, chunk, pos, len) {
|
||||||
|
if (pos >= len)
|
||||||
|
return len;
|
||||||
|
|
||||||
|
if (self._byte === -1) {
|
||||||
|
// We saw a '%' but no hex characters yet
|
||||||
|
const hexUpper = HEX_VALUES[chunk[pos++]];
|
||||||
|
if (hexUpper === -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (hexUpper >= 8)
|
||||||
|
self._encode = 2; // Indicate high bits detected
|
||||||
|
|
||||||
|
if (pos < len) {
|
||||||
|
// Both hex characters are in this chunk
|
||||||
|
const hexLower = HEX_VALUES[chunk[pos++]];
|
||||||
|
if (hexLower === -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (self._inKey)
|
||||||
|
self._key += String.fromCharCode((hexUpper << 4) + hexLower);
|
||||||
|
else
|
||||||
|
self._val += String.fromCharCode((hexUpper << 4) + hexLower);
|
||||||
|
|
||||||
|
self._byte = -2;
|
||||||
|
self._lastPos = pos;
|
||||||
|
} else {
|
||||||
|
// Only one hex character was available in this chunk
|
||||||
|
self._byte = hexUpper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We saw only one hex character so far
|
||||||
|
const hexLower = HEX_VALUES[chunk[pos++]];
|
||||||
|
if (hexLower === -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (self._inKey)
|
||||||
|
self._key += String.fromCharCode((self._byte << 4) + hexLower);
|
||||||
|
else
|
||||||
|
self._val += String.fromCharCode((self._byte << 4) + hexLower);
|
||||||
|
|
||||||
|
self._byte = -2;
|
||||||
|
self._lastPos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipKeyBytes(self, chunk, pos, len) {
|
||||||
|
// Skip bytes if we've truncated
|
||||||
|
if (self._bytesKey > self.fieldNameSizeLimit) {
|
||||||
|
if (!self._keyTrunc) {
|
||||||
|
if (self._lastPos < pos)
|
||||||
|
self._key += chunk.latin1Slice(self._lastPos, pos - 1);
|
||||||
|
}
|
||||||
|
self._keyTrunc = true;
|
||||||
|
for (; pos < len; ++pos) {
|
||||||
|
const code = chunk[pos];
|
||||||
|
if (code === 61/* '=' */ || code === 38/* '&' */)
|
||||||
|
break;
|
||||||
|
++self._bytesKey;
|
||||||
|
}
|
||||||
|
self._lastPos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipValBytes(self, chunk, pos, len) {
|
||||||
|
// Skip bytes if we've truncated
|
||||||
|
if (self._bytesVal > self.fieldSizeLimit) {
|
||||||
|
if (!self._valTrunc) {
|
||||||
|
if (self._lastPos < pos)
|
||||||
|
self._val += chunk.latin1Slice(self._lastPos, pos - 1);
|
||||||
|
}
|
||||||
|
self._valTrunc = true;
|
||||||
|
for (; pos < len; ++pos) {
|
||||||
|
if (chunk[pos] === 38/* '&' */)
|
||||||
|
break;
|
||||||
|
++self._bytesVal;
|
||||||
|
}
|
||||||
|
self._lastPos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable no-multi-spaces */
|
||||||
|
const HEX_VALUES = [
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
];
|
||||||
|
/* eslint-enable no-multi-spaces */
|
||||||
|
|
||||||
|
module.exports = URLEncoded;
|
|
@ -0,0 +1,596 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function parseContentType(str) {
|
||||||
|
if (str.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const params = Object.create(null);
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
// Parse type
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
if (code !== 47/* '/' */ || i === 0)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for type without subtype
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const type = str.slice(0, i).toLowerCase();
|
||||||
|
|
||||||
|
// Parse subtype
|
||||||
|
const subtypeStart = ++i;
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
// Make sure we have a subtype
|
||||||
|
if (i === subtypeStart)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parseContentTypeParams(str, i, params) === undefined)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure we have a subtype
|
||||||
|
if (i === subtypeStart)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const subtype = str.slice(subtypeStart, i).toLowerCase();
|
||||||
|
|
||||||
|
return { type, subtype, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseContentTypeParams(str, i, params) {
|
||||||
|
while (i < str.length) {
|
||||||
|
// Consume whitespace
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ended on whitespace
|
||||||
|
if (i === str.length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Check for malformed parameter
|
||||||
|
if (str.charCodeAt(i++) !== 59/* ';' */)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Consume whitespace
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ended on whitespace (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let name;
|
||||||
|
const nameStart = i;
|
||||||
|
// Parse parameter name
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
if (code !== 61/* '=' */)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
name = str.slice(nameStart, i);
|
||||||
|
++i; // Skip over '='
|
||||||
|
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let value = '';
|
||||||
|
let valueStart;
|
||||||
|
if (str.charCodeAt(i) === 34/* '"' */) {
|
||||||
|
valueStart = ++i;
|
||||||
|
let escaping = false;
|
||||||
|
// Parse quoted value
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code === 92/* '\\' */) {
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i;
|
||||||
|
escaping = false;
|
||||||
|
} else {
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
escaping = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code === 34/* '"' */) {
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i;
|
||||||
|
escaping = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i - 1;
|
||||||
|
escaping = false;
|
||||||
|
}
|
||||||
|
// Invalid unescaped quoted character (malformed)
|
||||||
|
if (QDTEXT[code] !== 1)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No end quote (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++i; // Skip over double quote
|
||||||
|
} else {
|
||||||
|
valueStart = i;
|
||||||
|
// Parse unquoted value
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === valueStart)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = str.slice(valueStart, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.toLowerCase();
|
||||||
|
if (params[name] === undefined)
|
||||||
|
params[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDisposition(str, defDecoder) {
|
||||||
|
if (str.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const params = Object.create(null);
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
if (parseDispositionParams(str, i, params, defDecoder) === undefined)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = str.slice(0, i).toLowerCase();
|
||||||
|
|
||||||
|
return { type, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDispositionParams(str, i, params, defDecoder) {
|
||||||
|
while (i < str.length) {
|
||||||
|
// Consume whitespace
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ended on whitespace
|
||||||
|
if (i === str.length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Check for malformed parameter
|
||||||
|
if (str.charCodeAt(i++) !== 59/* ';' */)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Consume whitespace
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ended on whitespace (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let name;
|
||||||
|
const nameStart = i;
|
||||||
|
// Parse parameter name
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
if (code === 61/* '=' */)
|
||||||
|
break;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let value = '';
|
||||||
|
let valueStart;
|
||||||
|
let charset;
|
||||||
|
//~ let lang;
|
||||||
|
name = str.slice(nameStart, i);
|
||||||
|
if (name.charCodeAt(name.length - 1) === 42/* '*' */) {
|
||||||
|
// Extended value
|
||||||
|
|
||||||
|
const charsetStart = ++i;
|
||||||
|
// Parse charset name
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (CHARSET[code] !== 1) {
|
||||||
|
if (code !== 39/* '\'' */)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete charset (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
charset = str.slice(charsetStart, i);
|
||||||
|
++i; // Skip over the '\''
|
||||||
|
|
||||||
|
//~ const langStart = ++i;
|
||||||
|
// Parse language name
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code === 39/* '\'' */)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete language (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//~ lang = str.slice(langStart, i);
|
||||||
|
++i; // Skip over the '\''
|
||||||
|
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
valueStart = i;
|
||||||
|
|
||||||
|
let encode = 0;
|
||||||
|
// Parse value
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (EXTENDED_VALUE[code] !== 1) {
|
||||||
|
if (code === 37/* '%' */) {
|
||||||
|
let hexUpper;
|
||||||
|
let hexLower;
|
||||||
|
if (i + 2 < str.length
|
||||||
|
&& (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1
|
||||||
|
&& (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) {
|
||||||
|
const byteVal = (hexUpper << 4) + hexLower;
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
value += String.fromCharCode(byteVal);
|
||||||
|
i += 2;
|
||||||
|
valueStart = i + 1;
|
||||||
|
if (byteVal >= 128)
|
||||||
|
encode = 2;
|
||||||
|
else if (encode === 0)
|
||||||
|
encode = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// '%' disallowed in non-percent encoded contexts (malformed)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
value = convertToUTF8(value, charset, encode);
|
||||||
|
if (value === undefined)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Non-extended value
|
||||||
|
|
||||||
|
++i; // Skip over '='
|
||||||
|
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (str.charCodeAt(i) === 34/* '"' */) {
|
||||||
|
valueStart = ++i;
|
||||||
|
let escaping = false;
|
||||||
|
// Parse quoted value
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (code === 92/* '\\' */) {
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i;
|
||||||
|
escaping = false;
|
||||||
|
} else {
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
escaping = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code === 34/* '"' */) {
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i;
|
||||||
|
escaping = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
value += str.slice(valueStart, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (escaping) {
|
||||||
|
valueStart = i - 1;
|
||||||
|
escaping = false;
|
||||||
|
}
|
||||||
|
// Invalid unescaped quoted character (malformed)
|
||||||
|
if (QDTEXT[code] !== 1)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No end quote (malformed)
|
||||||
|
if (i === str.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++i; // Skip over double quote
|
||||||
|
} else {
|
||||||
|
valueStart = i;
|
||||||
|
// Parse unquoted value
|
||||||
|
for (; i < str.length; ++i) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
if (TOKEN[code] !== 1) {
|
||||||
|
// No value (malformed)
|
||||||
|
if (i === valueStart)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = str.slice(valueStart, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = defDecoder(value, 2);
|
||||||
|
if (value === undefined)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.toLowerCase();
|
||||||
|
if (params[name] === undefined)
|
||||||
|
params[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDecoder(charset) {
|
||||||
|
let lc;
|
||||||
|
while (true) {
|
||||||
|
switch (charset) {
|
||||||
|
case 'utf-8':
|
||||||
|
case 'utf8':
|
||||||
|
return decoders.utf8;
|
||||||
|
case 'latin1':
|
||||||
|
case 'ascii': // TODO: Make these a separate, strict decoder?
|
||||||
|
case 'us-ascii':
|
||||||
|
case 'iso-8859-1':
|
||||||
|
case 'iso8859-1':
|
||||||
|
case 'iso88591':
|
||||||
|
case 'iso_8859-1':
|
||||||
|
case 'windows-1252':
|
||||||
|
case 'iso_8859-1:1987':
|
||||||
|
case 'cp1252':
|
||||||
|
case 'x-cp1252':
|
||||||
|
return decoders.latin1;
|
||||||
|
case 'utf16le':
|
||||||
|
case 'utf-16le':
|
||||||
|
case 'ucs2':
|
||||||
|
case 'ucs-2':
|
||||||
|
return decoders.utf16le;
|
||||||
|
case 'base64':
|
||||||
|
return decoders.base64;
|
||||||
|
default:
|
||||||
|
if (lc === undefined) {
|
||||||
|
lc = true;
|
||||||
|
charset = charset.toLowerCase();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return decoders.other.bind(charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoders = {
|
||||||
|
utf8: (data, hint) => {
|
||||||
|
if (data.length === 0)
|
||||||
|
return '';
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
// If `data` never had any percent-encoded bytes or never had any that
|
||||||
|
// were outside of the ASCII range, then we can safely just return the
|
||||||
|
// input since UTF-8 is ASCII compatible
|
||||||
|
if (hint < 2)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
data = Buffer.from(data, 'latin1');
|
||||||
|
}
|
||||||
|
return data.utf8Slice(0, data.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
latin1: (data, hint) => {
|
||||||
|
if (data.length === 0)
|
||||||
|
return '';
|
||||||
|
if (typeof data === 'string')
|
||||||
|
return data;
|
||||||
|
return data.latin1Slice(0, data.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
utf16le: (data, hint) => {
|
||||||
|
if (data.length === 0)
|
||||||
|
return '';
|
||||||
|
if (typeof data === 'string')
|
||||||
|
data = Buffer.from(data, 'latin1');
|
||||||
|
return data.ucs2Slice(0, data.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
base64: (data, hint) => {
|
||||||
|
if (data.length === 0)
|
||||||
|
return '';
|
||||||
|
if (typeof data === 'string')
|
||||||
|
data = Buffer.from(data, 'latin1');
|
||||||
|
return data.base64Slice(0, data.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
other: (data, hint) => {
|
||||||
|
if (data.length === 0)
|
||||||
|
return '';
|
||||||
|
if (typeof data === 'string')
|
||||||
|
data = Buffer.from(data, 'latin1');
|
||||||
|
try {
|
||||||
|
const decoder = new TextDecoder(this);
|
||||||
|
return decoder.decode(data);
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertToUTF8(data, charset, hint) {
|
||||||
|
const decode = getDecoder(charset);
|
||||||
|
if (decode)
|
||||||
|
return decode(data, hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
function basename(path) {
|
||||||
|
if (typeof path !== 'string')
|
||||||
|
return '';
|
||||||
|
for (let i = path.length - 1; i >= 0; --i) {
|
||||||
|
switch (path.charCodeAt(i)) {
|
||||||
|
case 0x2F: // '/'
|
||||||
|
case 0x5C: // '\'
|
||||||
|
path = path.slice(i + 1);
|
||||||
|
return (path === '..' || path === '.' ? '' : path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (path === '..' || path === '.' ? '' : path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOKEN = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
const QDTEXT = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CHARSET = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
const EXTENDED_VALUE = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
/* eslint-disable no-multi-spaces */
|
||||||
|
const HEX_VALUES = [
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
];
|
||||||
|
/* eslint-enable no-multi-spaces */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
basename,
|
||||||
|
convertToUTF8,
|
||||||
|
getDecoder,
|
||||||
|
parseContentType,
|
||||||
|
parseDisposition,
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
{ "name": "busboy",
|
||||||
|
"version": "1.6.0",
|
||||||
|
"author": "Brian White <mscdex@mscdex.net>",
|
||||||
|
"description": "A streaming parser for HTML form data for node.js",
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"streamsearch": "^1.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mscdex/eslint-config": "^1.1.0",
|
||||||
|
"eslint": "^7.32.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node test/test.js",
|
||||||
|
"lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test bench",
|
||||||
|
"lint:fix": "npm run lint -- --fix"
|
||||||
|
},
|
||||||
|
"engines": { "node": ">=10.16.0" },
|
||||||
|
"keywords": [ "uploads", "forms", "multipart", "form-data" ],
|
||||||
|
"licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/busboy/raw/master/LICENSE" } ],
|
||||||
|
"repository": { "type": "git", "url": "http://github.com/mscdex/busboy.git" }
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { inspect } = require('util');
|
||||||
|
|
||||||
|
const mustCallChecks = [];
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
function runCallChecks(exitCode) {
|
||||||
|
if (exitCode !== 0) return;
|
||||||
|
|
||||||
|
const failed = mustCallChecks.filter((context) => {
|
||||||
|
if ('minimum' in context) {
|
||||||
|
context.messageSegment = `at least ${context.minimum}`;
|
||||||
|
return context.actual < context.minimum;
|
||||||
|
}
|
||||||
|
context.messageSegment = `exactly ${context.exact}`;
|
||||||
|
return context.actual !== context.exact;
|
||||||
|
});
|
||||||
|
|
||||||
|
failed.forEach((context) => {
|
||||||
|
console.error('Mismatched %s function calls. Expected %s, actual %d.',
|
||||||
|
context.name,
|
||||||
|
context.messageSegment,
|
||||||
|
context.actual);
|
||||||
|
console.error(context.stack.split('\n').slice(2).join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (failed.length)
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustCall(fn, exact) {
|
||||||
|
return _mustCallInner(fn, exact, 'exact');
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustCallAtLeast(fn, minimum) {
|
||||||
|
return _mustCallInner(fn, minimum, 'minimum');
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mustCallInner(fn, criteria = 1, field) {
|
||||||
|
if (process._exiting)
|
||||||
|
throw new Error('Cannot use common.mustCall*() in process exit handler');
|
||||||
|
|
||||||
|
if (typeof fn === 'number') {
|
||||||
|
criteria = fn;
|
||||||
|
fn = noop;
|
||||||
|
} else if (fn === undefined) {
|
||||||
|
fn = noop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof criteria !== 'number')
|
||||||
|
throw new TypeError(`Invalid ${field} value: ${criteria}`);
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
[field]: criteria,
|
||||||
|
actual: 0,
|
||||||
|
stack: inspect(new Error()),
|
||||||
|
name: fn.name || '<anonymous>'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the exit listener only once to avoid listener leak warnings
|
||||||
|
if (mustCallChecks.length === 0)
|
||||||
|
process.on('exit', runCallChecks);
|
||||||
|
|
||||||
|
mustCallChecks.push(context);
|
||||||
|
|
||||||
|
function wrapped(...args) {
|
||||||
|
++context.actual;
|
||||||
|
return fn.call(this, ...args);
|
||||||
|
}
|
||||||
|
// TODO: remove origFn?
|
||||||
|
wrapped.origFn = fn;
|
||||||
|
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallSite(top) {
|
||||||
|
const originalStackFormatter = Error.prepareStackTrace;
|
||||||
|
Error.prepareStackTrace = (err, stack) =>
|
||||||
|
`${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
|
||||||
|
const err = new Error();
|
||||||
|
Error.captureStackTrace(err, top);
|
||||||
|
// With the V8 Error API, the stack is not formatted until it is accessed
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
err.stack;
|
||||||
|
Error.prepareStackTrace = originalStackFormatter;
|
||||||
|
return err.stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustNotCall(msg) {
|
||||||
|
const callSite = getCallSite(mustNotCall);
|
||||||
|
return function mustNotCall(...args) {
|
||||||
|
args = args.map(inspect).join(', ');
|
||||||
|
const argsInfo = (args.length > 0
|
||||||
|
? `\ncalled with arguments: ${args}`
|
||||||
|
: '');
|
||||||
|
assert.fail(
|
||||||
|
`${msg || 'function should not have been called'} at ${callSite}`
|
||||||
|
+ argsInfo);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mustCall,
|
||||||
|
mustCallAtLeast,
|
||||||
|
mustNotCall,
|
||||||
|
};
|
|
@ -0,0 +1,94 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { inspect } = require('util');
|
||||||
|
|
||||||
|
const { mustCall } = require(`${__dirname}/common.js`);
|
||||||
|
|
||||||
|
const busboy = require('..');
|
||||||
|
|
||||||
|
const input = Buffer.from([
|
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
|
||||||
|
'Content-Disposition: form-data; '
|
||||||
|
+ 'name="upload_file_0"; filename="テスト.dat"',
|
||||||
|
'Content-Type: application/octet-stream',
|
||||||
|
'',
|
||||||
|
'A'.repeat(1023),
|
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
|
||||||
|
].join('\r\n'));
|
||||||
|
const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k';
|
||||||
|
const expected = [
|
||||||
|
{ type: 'file',
|
||||||
|
name: 'upload_file_0',
|
||||||
|
data: Buffer.from('A'.repeat(1023)),
|
||||||
|
info: {
|
||||||
|
filename: 'テスト.dat',
|
||||||
|
encoding: '7bit',
|
||||||
|
mimeType: 'application/octet-stream',
|
||||||
|
},
|
||||||
|
limited: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const bb = busboy({
|
||||||
|
defParamCharset: 'utf8',
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
bb.on('field', (name, val, info) => {
|
||||||
|
results.push({ type: 'field', name, val, info });
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('file', (name, stream, info) => {
|
||||||
|
const data = [];
|
||||||
|
let nb = 0;
|
||||||
|
const file = {
|
||||||
|
type: 'file',
|
||||||
|
name,
|
||||||
|
data: null,
|
||||||
|
info,
|
||||||
|
limited: false,
|
||||||
|
};
|
||||||
|
results.push(file);
|
||||||
|
stream.on('data', (d) => {
|
||||||
|
data.push(d);
|
||||||
|
nb += d.length;
|
||||||
|
}).on('limit', () => {
|
||||||
|
file.limited = true;
|
||||||
|
}).on('close', () => {
|
||||||
|
file.data = Buffer.concat(data, nb);
|
||||||
|
assert.strictEqual(stream.truncated, file.limited);
|
||||||
|
}).once('error', (err) => {
|
||||||
|
file.err = err.message;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('error', (err) => {
|
||||||
|
results.push({ error: err.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('partsLimit', () => {
|
||||||
|
results.push('partsLimit');
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('filesLimit', () => {
|
||||||
|
results.push('filesLimit');
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('fieldsLimit', () => {
|
||||||
|
results.push('fieldsLimit');
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('close', mustCall(() => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
results,
|
||||||
|
expected,
|
||||||
|
'Results mismatch.\n'
|
||||||
|
+ `Parsed: ${inspect(results)}\n`
|
||||||
|
+ `Expected: ${inspect(expected)}`
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
bb.end(input);
|
102
backend/node_modules/busboy/test/test-types-multipart-stream-pause.js
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { randomFillSync } = require('crypto');
|
||||||
|
const { inspect } = require('util');
|
||||||
|
|
||||||
|
const busboy = require('..');
|
||||||
|
|
||||||
|
const { mustCall } = require('./common.js');
|
||||||
|
|
||||||
|
const BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh';
|
||||||
|
|
||||||
|
function formDataSection(key, value) {
|
||||||
|
return Buffer.from(
|
||||||
|
`\r\n--${BOUNDARY}`
|
||||||
|
+ `\r\nContent-Disposition: form-data; name="${key}"`
|
||||||
|
+ `\r\n\r\n${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formDataFile(key, filename, contentType) {
|
||||||
|
const buf = Buffer.allocUnsafe(100000);
|
||||||
|
return Buffer.concat([
|
||||||
|
Buffer.from(`\r\n--${BOUNDARY}\r\n`),
|
||||||
|
Buffer.from(`Content-Disposition: form-data; name="${key}"`
|
||||||
|
+ `; filename="${filename}"\r\n`),
|
||||||
|
Buffer.from(`Content-Type: ${contentType}\r\n\r\n`),
|
||||||
|
randomFillSync(buf)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reqChunks = [
|
||||||
|
Buffer.concat([
|
||||||
|
formDataFile('file', 'file.bin', 'application/octet-stream'),
|
||||||
|
formDataSection('foo', 'foo value'),
|
||||||
|
]),
|
||||||
|
formDataSection('bar', 'bar value'),
|
||||||
|
Buffer.from(`\r\n--${BOUNDARY}--\r\n`)
|
||||||
|
];
|
||||||
|
const bb = busboy({
|
||||||
|
headers: {
|
||||||
|
'content-type': `multipart/form-data; boundary=${BOUNDARY}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const expected = [
|
||||||
|
{ type: 'file',
|
||||||
|
name: 'file',
|
||||||
|
info: {
|
||||||
|
filename: 'file.bin',
|
||||||
|
encoding: '7bit',
|
||||||
|
mimeType: 'application/octet-stream',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: 'field',
|
||||||
|
name: 'foo',
|
||||||
|
val: 'foo value',
|
||||||
|
info: {
|
||||||
|
nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: '7bit',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: 'field',
|
||||||
|
name: 'bar',
|
||||||
|
val: 'bar value',
|
||||||
|
info: {
|
||||||
|
nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: '7bit',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
bb.on('field', (name, val, info) => {
|
||||||
|
results.push({ type: 'field', name, val, info });
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('file', (name, stream, info) => {
|
||||||
|
results.push({ type: 'file', name, info });
|
||||||
|
// Simulate a pipe where the destination is pausing (perhaps due to waiting
|
||||||
|
// for file system write to finish)
|
||||||
|
setTimeout(() => {
|
||||||
|
stream.resume();
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('close', mustCall(() => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
results,
|
||||||
|
expected,
|
||||||
|
'Results mismatch.\n'
|
||||||
|
+ `Parsed: ${inspect(results)}\n`
|
||||||
|
+ `Expected: ${inspect(expected)}`
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const chunk of reqChunks)
|
||||||
|
bb.write(chunk);
|
||||||
|
bb.end();
|
|
@ -0,0 +1,488 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { transcode } = require('buffer');
|
||||||
|
const { inspect } = require('util');
|
||||||
|
|
||||||
|
const busboy = require('..');
|
||||||
|
|
||||||
|
const active = new Map();
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{ source: ['foo'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Unassigned value'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned value'
|
||||||
|
},
|
||||||
|
{ source: ['foo&bar=baz'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['bar',
|
||||||
|
'baz',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Unassigned and assigned value'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['baz',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned and unassigned value'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['baz',
|
||||||
|
'bla',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Two assigned values'
|
||||||
|
},
|
||||||
|
{ source: ['foo&bar'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['bar',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Two unassigned values'
|
||||||
|
},
|
||||||
|
{ source: ['foo&bar&'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['bar',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Two unassigned values and ampersand'
|
||||||
|
},
|
||||||
|
{ source: ['foo+1=bar+baz%2Bquux'],
|
||||||
|
expected: [
|
||||||
|
['foo 1',
|
||||||
|
'bar baz+quux',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned key and value with (plus) space'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar%20baz%21'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar baz!',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned value with encoded bytes'
|
||||||
|
},
|
||||||
|
{ source: ['foo%20bar=baz%20bla%21'],
|
||||||
|
expected: [
|
||||||
|
['foo bar',
|
||||||
|
'baz bla!',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned value with encoded bytes #2'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar%20baz%21&num=1000'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar baz!',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['num',
|
||||||
|
'1000',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Two assigned values, one with encoded bytes'
|
||||||
|
},
|
||||||
|
{ source: [
|
||||||
|
Array.from(transcode(Buffer.from('foo'), 'utf8', 'utf16le')).map(
|
||||||
|
(n) => `%${n.toString(16).padStart(2, '0')}`
|
||||||
|
).join(''),
|
||||||
|
'=',
|
||||||
|
Array.from(transcode(Buffer.from('😀!'), 'utf8', 'utf16le')).map(
|
||||||
|
(n) => `%${n.toString(16).padStart(2, '0')}`
|
||||||
|
).join(''),
|
||||||
|
],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'😀!',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'UTF-16LE',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
charset: 'UTF-16LE',
|
||||||
|
what: 'Encoded value with multi-byte charset'
|
||||||
|
},
|
||||||
|
{ source: [
|
||||||
|
'foo=<',
|
||||||
|
Array.from(transcode(Buffer.from('©:^þ'), 'utf8', 'latin1')).map(
|
||||||
|
(n) => `%${n.toString(16).padStart(2, '0')}`
|
||||||
|
).join(''),
|
||||||
|
],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'<©:^þ',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'ISO-8859-1',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
charset: 'ISO-8859-1',
|
||||||
|
what: 'Encoded value with single-byte, ASCII-compatible, non-UTF8 charset'
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [],
|
||||||
|
what: 'Limits: zero fields',
|
||||||
|
limits: { fields: 0 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: one field',
|
||||||
|
limits: { fields: 1 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['baz',
|
||||||
|
'bla',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: field part lengths match limits',
|
||||||
|
limits: { fieldNameSize: 3, fieldSize: 3 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['fo',
|
||||||
|
'bar',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['ba',
|
||||||
|
'bla',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: truncated field name',
|
||||||
|
limits: { fieldNameSize: 2 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['foo',
|
||||||
|
'ba',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['baz',
|
||||||
|
'bl',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: truncated field value',
|
||||||
|
limits: { fieldSize: 2 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['fo',
|
||||||
|
'ba',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['ba',
|
||||||
|
'bl',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: truncated field name and value',
|
||||||
|
limits: { fieldNameSize: 2, fieldSize: 2 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['fo',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['ba',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: truncated field name and zero value limit',
|
||||||
|
limits: { fieldNameSize: 2, fieldSize: 0 }
|
||||||
|
},
|
||||||
|
{ source: ['foo=bar&baz=bla'],
|
||||||
|
expected: [
|
||||||
|
['',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
['',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: true,
|
||||||
|
valueTruncated: true,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Limits: truncated zero field name and zero value limit',
|
||||||
|
limits: { fieldNameSize: 0, fieldSize: 0 }
|
||||||
|
},
|
||||||
|
{ source: ['&'],
|
||||||
|
expected: [],
|
||||||
|
what: 'Ampersand'
|
||||||
|
},
|
||||||
|
{ source: ['&&&&&'],
|
||||||
|
expected: [],
|
||||||
|
what: 'Many ampersands'
|
||||||
|
},
|
||||||
|
{ source: ['='],
|
||||||
|
expected: [
|
||||||
|
['',
|
||||||
|
'',
|
||||||
|
{ nameTruncated: false,
|
||||||
|
valueTruncated: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimeType: 'text/plain' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
what: 'Assigned value, empty name and value'
|
||||||
|
},
|
||||||
|
{ source: [''],
|
||||||
|
expected: [],
|
||||||
|
what: 'Nothing'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
active.set(test, 1);
|
||||||
|
|
||||||
|
const { what } = test;
|
||||||
|
const charset = test.charset || 'utf-8';
|
||||||
|
const bb = busboy({
|
||||||
|
limits: test.limits,
|
||||||
|
headers: {
|
||||||
|
'content-type': `application/x-www-form-urlencoded; charset=${charset}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
bb.on('field', (key, val, info) => {
|
||||||
|
results.push([key, val, info]);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('file', () => {
|
||||||
|
throw new Error(`[${what}] Unexpected file`);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('close', () => {
|
||||||
|
active.delete(test);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
results,
|
||||||
|
test.expected,
|
||||||
|
`[${what}] Results mismatch.\n`
|
||||||
|
+ `Parsed: ${inspect(results)}\n`
|
||||||
|
+ `Expected: ${inspect(test.expected)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const src of test.source) {
|
||||||
|
const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
|
||||||
|
bb.write(buf);
|
||||||
|
}
|
||||||
|
bb.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte-by-byte versions
|
||||||
|
for (let test of tests) {
|
||||||
|
test = { ...test };
|
||||||
|
test.what += ' (byte-by-byte)';
|
||||||
|
active.set(test, 1);
|
||||||
|
|
||||||
|
const { what } = test;
|
||||||
|
const charset = test.charset || 'utf-8';
|
||||||
|
const bb = busboy({
|
||||||
|
limits: test.limits,
|
||||||
|
headers: {
|
||||||
|
'content-type': `application/x-www-form-urlencoded; charset="${charset}"`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
bb.on('field', (key, val, info) => {
|
||||||
|
results.push([key, val, info]);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('file', () => {
|
||||||
|
throw new Error(`[${what}] Unexpected file`);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('close', () => {
|
||||||
|
active.delete(test);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
results,
|
||||||
|
test.expected,
|
||||||
|
`[${what}] Results mismatch.\n`
|
||||||
|
+ `Parsed: ${inspect(results)}\n`
|
||||||
|
+ `Expected: ${inspect(test.expected)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const src of test.source) {
|
||||||
|
const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
|
||||||
|
for (let i = 0; i < buf.length; ++i)
|
||||||
|
bb.write(buf.slice(i, i + 1));
|
||||||
|
}
|
||||||
|
bb.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let exception = false;
|
||||||
|
process.once('uncaughtException', (ex) => {
|
||||||
|
exception = true;
|
||||||
|
throw ex;
|
||||||
|
});
|
||||||
|
process.on('exit', () => {
|
||||||
|
if (exception || active.size === 0)
|
||||||
|
return;
|
||||||
|
process.exitCode = 1;
|
||||||
|
console.error('==========================');
|
||||||
|
console.error(`${active.size} test(s) did not finish:`);
|
||||||
|
console.error('==========================');
|
||||||
|
console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const { readdirSync } = require('fs');
|
||||||
|
const { join } = require('path');
|
||||||
|
|
||||||
|
const files = readdirSync(__dirname).sort();
|
||||||
|
for (const filename of files) {
|
||||||
|
if (filename.startsWith('test-')) {
|
||||||
|
const path = join(__dirname, filename);
|
||||||
|
console.log(`> Running ${filename} ...`);
|
||||||
|
const result = spawnSync(`${process.argv0} ${path}`, {
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
windowsHide: true
|
||||||
|
});
|
||||||
|
if (result.status !== 0)
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2013 Max Ogden
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge,
|
||||||
|
to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify,
|
||||||
|
merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom
|
||||||
|
the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||||
|
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,144 @@
|
||||||
|
var Writable = require('readable-stream').Writable
|
||||||
|
var inherits = require('inherits')
|
||||||
|
var bufferFrom = require('buffer-from')
|
||||||
|
|
||||||
|
if (typeof Uint8Array === 'undefined') {
|
||||||
|
var U8 = require('typedarray').Uint8Array
|
||||||
|
} else {
|
||||||
|
var U8 = Uint8Array
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConcatStream(opts, cb) {
|
||||||
|
if (!(this instanceof ConcatStream)) return new ConcatStream(opts, cb)
|
||||||
|
|
||||||
|
if (typeof opts === 'function') {
|
||||||
|
cb = opts
|
||||||
|
opts = {}
|
||||||
|
}
|
||||||
|
if (!opts) opts = {}
|
||||||
|
|
||||||
|
var encoding = opts.encoding
|
||||||
|
var shouldInferEncoding = false
|
||||||
|
|
||||||
|
if (!encoding) {
|
||||||
|
shouldInferEncoding = true
|
||||||
|
} else {
|
||||||
|
encoding = String(encoding).toLowerCase()
|
||||||
|
if (encoding === 'u8' || encoding === 'uint8') {
|
||||||
|
encoding = 'uint8array'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Writable.call(this, { objectMode: true })
|
||||||
|
|
||||||
|
this.encoding = encoding
|
||||||
|
this.shouldInferEncoding = shouldInferEncoding
|
||||||
|
|
||||||
|
if (cb) this.on('finish', function () { cb(this.getBody()) })
|
||||||
|
this.body = []
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConcatStream
|
||||||
|
inherits(ConcatStream, Writable)
|
||||||
|
|
||||||
|
ConcatStream.prototype._write = function(chunk, enc, next) {
|
||||||
|
this.body.push(chunk)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcatStream.prototype.inferEncoding = function (buff) {
|
||||||
|
var firstBuffer = buff === undefined ? this.body[0] : buff;
|
||||||
|
if (Buffer.isBuffer(firstBuffer)) return 'buffer'
|
||||||
|
if (typeof Uint8Array !== 'undefined' && firstBuffer instanceof Uint8Array) return 'uint8array'
|
||||||
|
if (Array.isArray(firstBuffer)) return 'array'
|
||||||
|
if (typeof firstBuffer === 'string') return 'string'
|
||||||
|
if (Object.prototype.toString.call(firstBuffer) === "[object Object]") return 'object'
|
||||||
|
return 'buffer'
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcatStream.prototype.getBody = function () {
|
||||||
|
if (!this.encoding && this.body.length === 0) return []
|
||||||
|
if (this.shouldInferEncoding) this.encoding = this.inferEncoding()
|
||||||
|
if (this.encoding === 'array') return arrayConcat(this.body)
|
||||||
|
if (this.encoding === 'string') return stringConcat(this.body)
|
||||||
|
if (this.encoding === 'buffer') return bufferConcat(this.body)
|
||||||
|
if (this.encoding === 'uint8array') return u8Concat(this.body)
|
||||||
|
return this.body
|
||||||
|
}
|
||||||
|
|
||||||
|
var isArray = Array.isArray || function (arr) {
|
||||||
|
return Object.prototype.toString.call(arr) == '[object Array]'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArrayish (arr) {
|
||||||
|
return /Array\]$/.test(Object.prototype.toString.call(arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBufferish (p) {
|
||||||
|
return typeof p === 'string' || isArrayish(p) || (p && typeof p.subarray === 'function')
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringConcat (parts) {
|
||||||
|
var strings = []
|
||||||
|
var needsToString = false
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var p = parts[i]
|
||||||
|
if (typeof p === 'string') {
|
||||||
|
strings.push(p)
|
||||||
|
} else if (Buffer.isBuffer(p)) {
|
||||||
|
strings.push(p)
|
||||||
|
} else if (isBufferish(p)) {
|
||||||
|
strings.push(bufferFrom(p))
|
||||||
|
} else {
|
||||||
|
strings.push(bufferFrom(String(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Buffer.isBuffer(parts[0])) {
|
||||||
|
strings = Buffer.concat(strings)
|
||||||
|
strings = strings.toString('utf8')
|
||||||
|
} else {
|
||||||
|
strings = strings.join('')
|
||||||
|
}
|
||||||
|
return strings
|
||||||
|
}
|
||||||
|
|
||||||
|
function bufferConcat (parts) {
|
||||||
|
var bufs = []
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var p = parts[i]
|
||||||
|
if (Buffer.isBuffer(p)) {
|
||||||
|
bufs.push(p)
|
||||||
|
} else if (isBufferish(p)) {
|
||||||
|
bufs.push(bufferFrom(p))
|
||||||
|
} else {
|
||||||
|
bufs.push(bufferFrom(String(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.concat(bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayConcat (parts) {
|
||||||
|
var res = []
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
res.push.apply(res, parts[i])
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function u8Concat (parts) {
|
||||||
|
var len = 0
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
if (typeof parts[i] === 'string') {
|
||||||
|
parts[i] = bufferFrom(parts[i])
|
||||||
|
}
|
||||||
|
len += parts[i].length
|
||||||
|
}
|
||||||
|
var u8 = new U8(len)
|
||||||
|
for (var i = 0, offset = 0; i < parts.length; i++) {
|
||||||
|
var part = parts[i]
|
||||||
|
for (var j = 0; j < part.length; j++) {
|
||||||
|
u8[offset++] = part[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u8
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"name": "concat-stream",
|
||||||
|
"version": "1.6.2",
|
||||||
|
"description": "writable stream that concatenates strings or binary data and calls a callback with the result",
|
||||||
|
"tags": [
|
||||||
|
"stream",
|
||||||
|
"simple",
|
||||||
|
"util",
|
||||||
|
"utility"
|
||||||
|
],
|
||||||
|
"author": "Max Ogden <max@maxogden.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "http://github.com/maxogden/concat-stream.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "http://github.com/maxogden/concat-stream/issues"
|
||||||
|
},
|
||||||
|
"engines": [
|
||||||
|
"node >= 0.8"
|
||||||
|
],
|
||||||
|
"main": "index.js",
|
||||||
|
"files": [
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "tape test/*.js test/server/*.js"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^2.2.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"tape": "^4.6.3"
|
||||||
|
},
|
||||||
|
"testling": {
|
||||||
|
"files": "test/*.js",
|
||||||
|
"browsers": [
|
||||||
|
"ie/8..latest",
|
||||||
|
"firefox/17..latest",
|
||||||
|
"firefox/nightly",
|
||||||
|
"chrome/22..latest",
|
||||||
|
"chrome/canary",
|
||||||
|
"opera/12..latest",
|
||||||
|
"opera/next",
|
||||||
|
"safari/5.1..latest",
|
||||||
|
"ipad/6.0..latest",
|
||||||
|
"iphone/6.0..latest",
|
||||||
|
"android-browser/4.2..latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
# concat-stream
|
||||||
|
|
||||||
|
Writable stream that concatenates all the data from a stream and calls a callback with the result. Use this when you want to collect all the data from a stream into a single buffer.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/maxogden/concat-stream)
|
||||||
|
|
||||||
|
[](https://nodei.co/npm/concat-stream/)
|
||||||
|
|
||||||
|
### description
|
||||||
|
|
||||||
|
Streams emit many buffers. If you want to collect all of the buffers, and when the stream ends concatenate all of the buffers together and receive a single buffer then this is the module for you.
|
||||||
|
|
||||||
|
Only use this if you know you can fit all of the output of your stream into a single Buffer (e.g. in RAM).
|
||||||
|
|
||||||
|
There are also `objectMode` streams that emit things other than Buffers, and you can concatenate these too. See below for details.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
`concat-stream` is part of the [mississippi stream utility collection](https://github.com/maxogden/mississippi) which includes more useful stream modules similar to this one.
|
||||||
|
|
||||||
|
### examples
|
||||||
|
|
||||||
|
#### Buffers
|
||||||
|
|
||||||
|
```js
|
||||||
|
var fs = require('fs')
|
||||||
|
var concat = require('concat-stream')
|
||||||
|
|
||||||
|
var readStream = fs.createReadStream('cat.png')
|
||||||
|
var concatStream = concat(gotPicture)
|
||||||
|
|
||||||
|
readStream.on('error', handleError)
|
||||||
|
readStream.pipe(concatStream)
|
||||||
|
|
||||||
|
function gotPicture(imageBuffer) {
|
||||||
|
// imageBuffer is all of `cat.png` as a node.js Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(err) {
|
||||||
|
// handle your error appropriately here, e.g.:
|
||||||
|
console.error(err) // print the error to STDERR
|
||||||
|
process.exit(1) // exit program with non-zero exit code
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arrays
|
||||||
|
|
||||||
|
```js
|
||||||
|
var write = concat(function(data) {})
|
||||||
|
write.write([1,2,3])
|
||||||
|
write.write([4,5,6])
|
||||||
|
write.end()
|
||||||
|
// data will be [1,2,3,4,5,6] in the above callback
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Uint8Arrays
|
||||||
|
|
||||||
|
```js
|
||||||
|
var write = concat(function(data) {})
|
||||||
|
var a = new Uint8Array(3)
|
||||||
|
a[0] = 97; a[1] = 98; a[2] = 99
|
||||||
|
write.write(a)
|
||||||
|
write.write('!')
|
||||||
|
write.end(Buffer.from('!!1'))
|
||||||
|
```
|
||||||
|
|
||||||
|
See `test/` for more examples
|
||||||
|
|
||||||
|
# methods
|
||||||
|
|
||||||
|
```js
|
||||||
|
var concat = require('concat-stream')
|
||||||
|
```
|
||||||
|
|
||||||
|
## var writable = concat(opts={}, cb)
|
||||||
|
|
||||||
|
Return a `writable` stream that will fire `cb(data)` with all of the data that
|
||||||
|
was written to the stream. Data can be written to `writable` as strings,
|
||||||
|
Buffers, arrays of byte integers, and Uint8Arrays.
|
||||||
|
|
||||||
|
By default `concat-stream` will give you back the same data type as the type of the first buffer written to the stream. Use `opts.encoding` to set what format `data` should be returned as, e.g. if you if you don't want to rely on the built-in type checking or for some other reason.
|
||||||
|
|
||||||
|
* `string` - get a string
|
||||||
|
* `buffer` - get back a Buffer
|
||||||
|
* `array` - get an array of byte integers
|
||||||
|
* `uint8array`, `u8`, `uint8` - get back a Uint8Array
|
||||||
|
* `object`, get back an array of Objects
|
||||||
|
|
||||||
|
If you don't specify an encoding, and the types can't be inferred (e.g. you write things that aren't in the list above), it will try to convert concat them into a `Buffer`.
|
||||||
|
|
||||||
|
If nothing is written to `writable` then `data` will be an empty array `[]`.
|
||||||
|
|
||||||
|
# error handling
|
||||||
|
|
||||||
|
`concat-stream` does not handle errors for you, so you must handle errors on whatever streams you pipe into `concat-stream`. This is a general rule when programming with node.js streams: always handle errors on each and every stream. Since `concat-stream` is not itself a stream it does not emit errors.
|
||||||
|
|
||||||
|
We recommend using [`end-of-stream`](https://npmjs.org/end-of-stream) or [`pump`](https://npmjs.org/pump) for writing error tolerant stream code.
|
||||||
|
|
||||||
|
# license
|
||||||
|
|
||||||
|
MIT LICENSE
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright Node.js contributors. All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
||||||
|
# core-util-is
|
||||||
|
|
||||||
|
The `util.is*` functions introduced in Node v0.12.
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: These type checking functions intentionally don't use `instanceof`
|
||||||
|
// because it is fragile and can be easily faked with `Object.create()`.
|
||||||
|
|
||||||
|
function isArray(arg) {
|
||||||
|
if (Array.isArray) {
|
||||||
|
return Array.isArray(arg);
|
||||||
|
}
|
||||||
|
return objectToString(arg) === '[object Array]';
|
||||||
|
}
|
||||||
|
exports.isArray = isArray;
|
||||||
|
|
||||||
|
function isBoolean(arg) {
|
||||||
|
return typeof arg === 'boolean';
|
||||||
|
}
|
||||||
|
exports.isBoolean = isBoolean;
|
||||||
|
|
||||||
|
function isNull(arg) {
|
||||||
|
return arg === null;
|
||||||
|
}
|
||||||
|
exports.isNull = isNull;
|
||||||
|
|
||||||
|
function isNullOrUndefined(arg) {
|
||||||
|
return arg == null;
|
||||||
|
}
|
||||||
|
exports.isNullOrUndefined = isNullOrUndefined;
|
||||||
|
|
||||||
|
function isNumber(arg) {
|
||||||
|
return typeof arg === 'number';
|
||||||
|
}
|
||||||
|
exports.isNumber = isNumber;
|
||||||
|
|
||||||
|
function isString(arg) {
|
||||||
|
return typeof arg === 'string';
|
||||||
|
}
|
||||||
|
exports.isString = isString;
|
||||||
|
|
||||||
|
function isSymbol(arg) {
|
||||||
|
return typeof arg === 'symbol';
|
||||||
|
}
|
||||||
|
exports.isSymbol = isSymbol;
|
||||||
|
|
||||||
|
function isUndefined(arg) {
|
||||||
|
return arg === void 0;
|
||||||
|
}
|
||||||
|
exports.isUndefined = isUndefined;
|
||||||
|
|
||||||
|
function isRegExp(re) {
|
||||||
|
return objectToString(re) === '[object RegExp]';
|
||||||
|
}
|
||||||
|
exports.isRegExp = isRegExp;
|
||||||
|
|
||||||
|
function isObject(arg) {
|
||||||
|
return typeof arg === 'object' && arg !== null;
|
||||||
|
}
|
||||||
|
exports.isObject = isObject;
|
||||||
|
|
||||||
|
function isDate(d) {
|
||||||
|
return objectToString(d) === '[object Date]';
|
||||||
|
}
|
||||||
|
exports.isDate = isDate;
|
||||||
|
|
||||||
|
function isError(e) {
|
||||||
|
return (objectToString(e) === '[object Error]' || e instanceof Error);
|
||||||
|
}
|
||||||
|
exports.isError = isError;
|
||||||
|
|
||||||
|
function isFunction(arg) {
|
||||||
|
return typeof arg === 'function';
|
||||||
|
}
|
||||||
|
exports.isFunction = isFunction;
|
||||||
|
|
||||||
|
function isPrimitive(arg) {
|
||||||
|
return arg === null ||
|
||||||
|
typeof arg === 'boolean' ||
|
||||||
|
typeof arg === 'number' ||
|
||||||
|
typeof arg === 'string' ||
|
||||||
|
typeof arg === 'symbol' || // ES6 symbol
|
||||||
|
typeof arg === 'undefined';
|
||||||
|
}
|
||||||
|
exports.isPrimitive = isPrimitive;
|
||||||
|
|
||||||
|
exports.isBuffer = require('buffer').Buffer.isBuffer;
|
||||||
|
|
||||||
|
function objectToString(o) {
|
||||||
|
return Object.prototype.toString.call(o);
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "core-util-is",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"description": "The `util.is*` functions introduced in Node v0.12.",
|
||||||
|
"main": "lib/util.js",
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/isaacs/core-util-is"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"util",
|
||||||
|
"isBuffer",
|
||||||
|
"isArray",
|
||||||
|
"isNumber",
|
||||||
|
"isString",
|
||||||
|
"isRegExp",
|
||||||
|
"isThis",
|
||||||
|
"isThat",
|
||||||
|
"polyfill"
|
||||||
|
],
|
||||||
|
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/isaacs/core-util-is/issues"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "tap test.js",
|
||||||
|
"preversion": "npm test",
|
||||||
|
"postversion": "npm publish",
|
||||||
|
"prepublishOnly": "git push origin --follow-tags"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"tap": "^15.0.9"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
|
@ -0,0 +1,4 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "0.8"
|
||||||
|
- "0.10"
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
test:
|
||||||
|
@node_modules/.bin/tape test.js
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
# isarray
|
||||||
|
|
||||||
|
`Array#isArray` for older browsers.
|
||||||
|
|
||||||
|
[](http://travis-ci.org/juliangruber/isarray)
|
||||||
|
[](https://www.npmjs.org/package/isarray)
|
||||||
|
|
||||||
|
[
|
||||||
|
](https://ci.testling.com/juliangruber/isarray)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
var isArray = require('isarray');
|
||||||
|
|
||||||
|
console.log(isArray([])); // => true
|
||||||
|
console.log(isArray({})); // => false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
With [npm](http://npmjs.org) do
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install isarray
|
||||||
|
```
|
||||||
|
|
||||||
|
Then bundle for the browser with
|
||||||
|
[browserify](https://github.com/substack/browserify).
|
||||||
|
|
||||||
|
With [component](http://component.io) do
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ component install juliangruber/isarray
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
(MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name" : "isarray",
|
||||||
|
"description" : "Array#isArray for older browsers",
|
||||||
|
"version" : "0.0.1",
|
||||||
|
"repository" : "juliangruber/isarray",
|
||||||
|
"homepage": "https://github.com/juliangruber/isarray",
|
||||||
|
"main" : "index.js",
|
||||||
|
"scripts" : [
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"dependencies" : {},
|
||||||
|
"keywords": ["browser","isarray","array"],
|
||||||
|
"author": {
|
||||||
|
"name": "Julian Gruber",
|
||||||
|
"email": "mail@juliangruber.com",
|
||||||
|
"url": "http://juliangruber.com"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
var toString = {}.toString;
|
||||||
|
|
||||||
|
module.exports = Array.isArray || function (arr) {
|
||||||
|
return toString.call(arr) == '[object Array]';
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "isarray",
|
||||||
|
"description": "Array#isArray for older browsers",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/juliangruber/isarray.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/juliangruber/isarray",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"tape": "~2.13.4"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"browser",
|
||||||
|
"isarray",
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Julian Gruber",
|
||||||
|
"email": "mail@juliangruber.com",
|
||||||
|
"url": "http://juliangruber.com"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"testling": {
|
||||||
|
"files": "test.js",
|
||||||
|
"browsers": [
|
||||||
|
"ie/8..latest",
|
||||||
|
"firefox/17..latest",
|
||||||
|
"firefox/nightly",
|
||||||
|
"chrome/22..latest",
|
||||||
|
"chrome/canary",
|
||||||
|
"opera/12..latest",
|
||||||
|
"opera/next",
|
||||||
|
"safari/5.1..latest",
|
||||||
|
"ipad/6.0..latest",
|
||||||
|
"iphone/6.0..latest",
|
||||||
|
"android-browser/4.2..latest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "tape test.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
var isArray = require('./');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('is array', function(t){
|
||||||
|
t.ok(isArray([]));
|
||||||
|
t.notOk(isArray({}));
|
||||||
|
t.notOk(isArray(null));
|
||||||
|
t.notOk(isArray(false));
|
||||||
|
|
||||||
|
var obj = {};
|
||||||
|
obj[0] = true;
|
||||||
|
t.notOk(isArray(obj));
|
||||||
|
|
||||||
|
var arr = [];
|
||||||
|
arr.foo = 'bar';
|
||||||
|
t.ok(isArray(arr));
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
|
||||||
|
"extends": "@ljharb/eslint-config/node/0.4",
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
"array-element-newline": 0,
|
||||||
|
"complexity": 0,
|
||||||
|
"func-style": [2, "declaration"],
|
||||||
|
"max-lines-per-function": 0,
|
||||||
|
"max-nested-callbacks": 1,
|
||||||
|
"max-statements-per-line": 1,
|
||||||
|
"max-statements": 0,
|
||||||
|
"multiline-comment-style": 0,
|
||||||
|
"no-continue": 1,
|
||||||
|
"no-param-reassign": 1,
|
||||||
|
"no-restricted-syntax": 1,
|
||||||
|
"object-curly-newline": 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "test/**",
|
||||||
|
"rules": {
|
||||||
|
"camelcase": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [ljharb]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: npm/minimist
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"all": true,
|
||||||
|
"check-coverage": false,
|
||||||
|
"reporter": ["text-summary", "text", "html", "json"],
|
||||||
|
"lines": 86,
|
||||||
|
"statements": 85.93,
|
||||||
|
"functions": 82.43,
|
||||||
|
"branches": 76.06,
|
||||||
|
"exclude": [
|
||||||
|
"coverage",
|
||||||
|
"example",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.2.8](https://github.com/minimistjs/minimist/compare/v1.2.7...v1.2.8) - 2023-02-09
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17)
|
||||||
|
- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12)
|
||||||
|
- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15)
|
||||||
|
- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8)
|
||||||
|
- [Fix] Fix long option followed by single dash [`#15`](https://github.com/minimistjs/minimist/issues/15)
|
||||||
|
- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9)
|
||||||
|
- [Fix] Fix handling of short option with non-trivial equals [`#5`](https://github.com/minimistjs/minimist/issues/5)
|
||||||
|
- [Tests] Remove duplicate test [`#8`](https://github.com/minimistjs/minimist/issues/8)
|
||||||
|
- [Fix] opt.string works with multiple aliases [`#9`](https://github.com/minimistjs/minimist/issues/9)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Merge tag 'v0.2.3' [`a026794`](https://github.com/minimistjs/minimist/commit/a0267947c7870fc5847cf2d437fbe33f392767da)
|
||||||
|
- [eslint] fix indentation and whitespace [`5368ca4`](https://github.com/minimistjs/minimist/commit/5368ca4147e974138a54cc0dc4cea8f756546b70)
|
||||||
|
- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7)
|
||||||
|
- [eslint] more cleanup [`62fde7d`](https://github.com/minimistjs/minimist/commit/62fde7d935f83417fb046741531a9e2346a36976)
|
||||||
|
- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1)
|
||||||
|
- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c)
|
||||||
|
- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91)
|
||||||
|
- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4)
|
||||||
|
- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982)
|
||||||
|
- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0)
|
||||||
|
- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07)
|
||||||
|
- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b)
|
||||||
|
- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa)
|
||||||
|
- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e)
|
||||||
|
- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf)
|
||||||
|
- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`3124ed3`](https://github.com/minimistjs/minimist/commit/3124ed3e46306301ebb3c834874ce0241555c2c4)
|
||||||
|
- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6)
|
||||||
|
- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c)
|
||||||
|
- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a)
|
||||||
|
- [actions] Avoid 0.6 tests due to build failures [`ba92fe6`](https://github.com/minimistjs/minimist/commit/ba92fe6ebbdc0431cca9a2ea8f27beb492f5e4ec)
|
||||||
|
- [Dev Deps] update `tape` [`950eaa7`](https://github.com/minimistjs/minimist/commit/950eaa74f112e04d23e9c606c67472c46739b473)
|
||||||
|
- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b)
|
||||||
|
- Merge tag 'v0.2.2' [`980d7ac`](https://github.com/minimistjs/minimist/commit/980d7ac61a0b4bd552711251ac107d506b23e41f)
|
||||||
|
|
||||||
|
## [v1.2.7](https://github.com/minimistjs/minimist/compare/v1.2.6...v1.2.7) - 2022-10-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [meta] add `auto-changelog` [`0ebf4eb`](https://github.com/minimistjs/minimist/commit/0ebf4ebcd5f7787a5524d31a849ef41316b83c3c)
|
||||||
|
- [actions] add reusable workflows [`e115b63`](https://github.com/minimistjs/minimist/commit/e115b63fa9d3909f33b00a2db647ff79068388de)
|
||||||
|
- [eslint] add eslint; rules to enable later are warnings [`f58745b`](https://github.com/minimistjs/minimist/commit/f58745b9bb84348e1be72af7dbba5840c7c13013)
|
||||||
|
- [Dev Deps] switch from `covert` to `nyc` [`ab03356`](https://github.com/minimistjs/minimist/commit/ab033567b9c8b31117cb026dc7f1e592ce455c65)
|
||||||
|
- [readme] rename and add badges [`236f4a0`](https://github.com/minimistjs/minimist/commit/236f4a07e4ebe5ee44f1496ec6974991ab293ffd)
|
||||||
|
- [meta] create FUNDING.yml; add `funding` in package.json [`783a49b`](https://github.com/minimistjs/minimist/commit/783a49bfd47e8335d3098a8cac75662cf71eb32a)
|
||||||
|
- [meta] use `npmignore` to autogenerate an npmignore file [`f81ece6`](https://github.com/minimistjs/minimist/commit/f81ece6aaec2fa14e69ff4f1e0407a8c4e2635a2)
|
||||||
|
- Only apps should have lockfiles [`56cad44`](https://github.com/minimistjs/minimist/commit/56cad44c7f879b9bb5ec18fcc349308024a89bfc)
|
||||||
|
- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`49c5f9f`](https://github.com/minimistjs/minimist/commit/49c5f9fb7e6a92db9eb340cc679de92fb3aacded)
|
||||||
|
- [Tests] add `aud` in `posttest` [`228ae93`](https://github.com/minimistjs/minimist/commit/228ae938f3cd9db9dfd8bd7458b076a7b2aef280)
|
||||||
|
- [meta] add `safe-publish-latest` [`01fc23f`](https://github.com/minimistjs/minimist/commit/01fc23f5104f85c75059972e01dd33796ab529ff)
|
||||||
|
- [meta] update repo URLs [`6b164c7`](https://github.com/minimistjs/minimist/commit/6b164c7d68e0b6bf32f894699effdfb7c63041dd)
|
||||||
|
|
||||||
|
## [v1.2.6](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.6) - 2022-03-21
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- test from prototype pollution PR [`bc8ecee`](https://github.com/minimistjs/minimist/commit/bc8ecee43875261f4f17eb20b1243d3ed15e70eb)
|
||||||
|
- isConstructorOrProto adapted from PR [`c2b9819`](https://github.com/minimistjs/minimist/commit/c2b981977fa834b223b408cfb860f933c9811e4d)
|
||||||
|
- security notice for additional prototype pollution issue [`ef88b93`](https://github.com/minimistjs/minimist/commit/ef88b9325f77b5ee643ccfc97e2ebda577e4c4e2)
|
||||||
|
|
||||||
|
## [v1.2.5](https://github.com/minimistjs/minimist/compare/v1.2.4...v1.2.5) - 2020-03-12
|
||||||
|
|
||||||
|
## [v1.2.4](https://github.com/minimistjs/minimist/compare/v1.2.3...v1.2.4) - 2020-03-11
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- security notice [`4cf1354`](https://github.com/minimistjs/minimist/commit/4cf1354839cb972e38496d35e12f806eea92c11f)
|
||||||
|
- additional test for constructor prototype pollution [`1043d21`](https://github.com/minimistjs/minimist/commit/1043d212c3caaf871966e710f52cfdf02f9eea4b)
|
||||||
|
|
||||||
|
## [v1.2.3](https://github.com/minimistjs/minimist/compare/v1.2.2...v1.2.3) - 2020-03-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- more failing proto pollution tests [`13c01a5`](https://github.com/minimistjs/minimist/commit/13c01a5327736903704984b7f65616b8476850cc)
|
||||||
|
- even more aggressive checks for protocol pollution [`38a4d1c`](https://github.com/minimistjs/minimist/commit/38a4d1caead72ef99e824bb420a2528eec03d9ab)
|
||||||
|
|
||||||
|
## [v1.2.2](https://github.com/minimistjs/minimist/compare/v1.2.1...v1.2.2) - 2020-03-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- failing test for protocol pollution [`0efed03`](https://github.com/minimistjs/minimist/commit/0efed0340ec8433638758f7ca0c77cb20a0bfbab)
|
||||||
|
- cleanup [`67d3722`](https://github.com/minimistjs/minimist/commit/67d3722413448d00a62963d2d30c34656a92d7e2)
|
||||||
|
- console.dir -> console.log [`47acf72`](https://github.com/minimistjs/minimist/commit/47acf72c715a630bf9ea013867f47f1dd69dfc54)
|
||||||
|
- don't assign onto __proto__ [`63e7ed0`](https://github.com/minimistjs/minimist/commit/63e7ed05aa4b1889ec2f3b196426db4500cbda94)
|
||||||
|
|
||||||
|
## [v1.2.1](https://github.com/minimistjs/minimist/compare/v1.2.0...v1.2.1) - 2020-03-10
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- move the `opts['--']` example back where it belongs [`#63`](https://github.com/minimistjs/minimist/pull/63)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- add test [`6be5dae`](https://github.com/minimistjs/minimist/commit/6be5dae35a32a987bcf4137fcd6c19c5200ee909)
|
||||||
|
- fix bad boolean regexp [`ac3fc79`](https://github.com/minimistjs/minimist/commit/ac3fc796e63b95128fdbdf67ea7fad71bd59aa76)
|
||||||
|
|
||||||
|
## [v1.2.0](https://github.com/minimistjs/minimist/compare/v1.1.3...v1.2.0) - 2015-08-24
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- failing -k=v short test [`63416b8`](https://github.com/minimistjs/minimist/commit/63416b8cd1d0d70e4714564cce465a36e4dd26d7)
|
||||||
|
- kv short fix [`6bbe145`](https://github.com/minimistjs/minimist/commit/6bbe14529166245e86424f220a2321442fe88dc3)
|
||||||
|
- failing kv short test [`f72ab7f`](https://github.com/minimistjs/minimist/commit/f72ab7f4572adc52902c9b6873cc969192f01b10)
|
||||||
|
- fixed kv test [`f5a48c3`](https://github.com/minimistjs/minimist/commit/f5a48c3e50e40ca54f00c8e84de4b4d6e9897fa8)
|
||||||
|
- enforce space between arg key and value [`86b321a`](https://github.com/minimistjs/minimist/commit/86b321affe648a8e016c095a4f0efa9d9074f502)
|
||||||
|
|
||||||
|
## [v1.1.3](https://github.com/minimistjs/minimist/compare/v1.1.2...v1.1.3) - 2015-08-06
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- add failing test - boolean alias array [`0fa3c5b`](https://github.com/minimistjs/minimist/commit/0fa3c5b3dd98551ddecf5392831b4c21211743fc)
|
||||||
|
- fix boolean values with multiple aliases [`9c0a6e7`](https://github.com/minimistjs/minimist/commit/9c0a6e7de25a273b11bbf9a7464f0bd833779795)
|
||||||
|
|
||||||
|
## [v1.1.2](https://github.com/minimistjs/minimist/compare/v1.1.1...v1.1.2) - 2015-07-22
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Convert boolean arguments to boolean values [`8f3dc27`](https://github.com/minimistjs/minimist/commit/8f3dc27cf833f1d54671b6d0bcb55c2fe19672a9)
|
||||||
|
- use non-ancient npm, node 0.12 and iojs [`61ed1d0`](https://github.com/minimistjs/minimist/commit/61ed1d034b9ec7282764ce76f3992b1a0b4906ae)
|
||||||
|
- an older npm for 0.8 [`25cf778`](https://github.com/minimistjs/minimist/commit/25cf778b1220e7838a526832ad6972f75244054f)
|
||||||
|
|
||||||
|
## [v1.1.1](https://github.com/minimistjs/minimist/compare/v1.1.0...v1.1.1) - 2015-03-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- check that they type of a value is a boolean, not just that it is currently set to a boolean [`6863198`](https://github.com/minimistjs/minimist/commit/6863198e36139830ff1f20ffdceaddd93f2c1db9)
|
||||||
|
- upgrade tape, fix type issues from old tape version [`806712d`](https://github.com/minimistjs/minimist/commit/806712df91604ed02b8e39aa372b84aea659ee34)
|
||||||
|
- test for setting a boolean to a null default [`8c444fe`](https://github.com/minimistjs/minimist/commit/8c444fe89384ded7d441c120915ea60620b01dd3)
|
||||||
|
- if the previous value was a boolean, without an default (or with an alias) don't make an array either [`e5f419a`](https://github.com/minimistjs/minimist/commit/e5f419a3b5b3bc3f9e5ac71b7040621af70ed2dd)
|
||||||
|
|
||||||
|
## [v1.1.0](https://github.com/minimistjs/minimist/compare/v1.0.0...v1.1.0) - 2014-08-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- add support for handling "unknown" options not registered with the parser. [`6f3cc5d`](https://github.com/minimistjs/minimist/commit/6f3cc5d4e84524932a6ef2ce3592acc67cdd4383)
|
||||||
|
- reformat package.json [`02ed371`](https://github.com/minimistjs/minimist/commit/02ed37115194d3697ff358e8e25e5e66bab1d9f8)
|
||||||
|
- coverage script [`e5531ba`](https://github.com/minimistjs/minimist/commit/e5531ba0479da3b8138d3d8cac545d84ccb1c8df)
|
||||||
|
- extra fn to get 100% coverage again [`a6972da`](https://github.com/minimistjs/minimist/commit/a6972da89e56bf77642f8ec05a13b6558db93498)
|
||||||
|
|
||||||
|
## [v1.0.0](https://github.com/minimistjs/minimist/compare/v0.2.3...v1.0.0) - 2014-08-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- added stopEarly option [`471c7e4`](https://github.com/minimistjs/minimist/commit/471c7e4a7e910fc7ad8f9df850a186daf32c64e9)
|
||||||
|
- fix list [`fef6ae7`](https://github.com/minimistjs/minimist/commit/fef6ae79c38b9dc1c49569abb7cd04eb965eac5e)
|
||||||
|
|
||||||
|
## [v0.2.3](https://github.com/minimistjs/minimist/compare/v0.2.2...v0.2.3) - 2023-02-09
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17)
|
||||||
|
- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12)
|
||||||
|
- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15)
|
||||||
|
- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8)
|
||||||
|
- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7)
|
||||||
|
- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1)
|
||||||
|
- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982)
|
||||||
|
- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79)
|
||||||
|
- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b)
|
||||||
|
|
||||||
|
## [v0.2.2](https://github.com/minimistjs/minimist/compare/v0.2.1...v0.2.2) - 2022-10-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c)
|
||||||
|
- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91)
|
||||||
|
- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4)
|
||||||
|
- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0)
|
||||||
|
- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07)
|
||||||
|
- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b)
|
||||||
|
- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa)
|
||||||
|
- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e)
|
||||||
|
- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf)
|
||||||
|
- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6)
|
||||||
|
- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c)
|
||||||
|
- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a)
|
||||||
|
|
||||||
|
## [v0.2.1](https://github.com/minimistjs/minimist/compare/v0.2.0...v0.2.1) - 2020-03-12
|
||||||
|
|
||||||
|
## [v0.2.0](https://github.com/minimistjs/minimist/compare/v0.1.0...v0.2.0) - 2014-06-19
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- support all-boolean mode [`450a97f`](https://github.com/minimistjs/minimist/commit/450a97f6e2bc85c7a4a13185c19a818d9a5ebe69)
|
||||||
|
|
||||||
|
## [v0.1.0](https://github.com/minimistjs/minimist/compare/v0.0.10...v0.1.0) - 2014-05-12
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Provide a mechanism to segregate -- arguments [`ce4a1e6`](https://github.com/minimistjs/minimist/commit/ce4a1e63a7e8d5ab88d2a3768adefa6af98a445a)
|
||||||
|
- documented argv['--'] [`14db0e6`](https://github.com/minimistjs/minimist/commit/14db0e6dbc6d2b9e472adaa54dad7004b364634f)
|
||||||
|
- Adding a test-case for notFlags segregation [`715c1e3`](https://github.com/minimistjs/minimist/commit/715c1e3714be223f998f6c537af6b505f0236c16)
|
||||||
|
|
||||||
|
## [v0.0.10](https://github.com/minimistjs/minimist/compare/v0.0.9...v0.0.10) - 2014-05-11
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- dedicated boolean test [`46e448f`](https://github.com/minimistjs/minimist/commit/46e448f9f513cfeb2bcc8b688b9b47ba1e515c2b)
|
||||||
|
- dedicated num test [`9bf2d36`](https://github.com/minimistjs/minimist/commit/9bf2d36f1d3b8795be90b8f7de0a937f098aa394)
|
||||||
|
- aliased values treated as strings [`1ab743b`](https://github.com/minimistjs/minimist/commit/1ab743bad4484d69f1259bed42f9531de01119de)
|
||||||
|
- cover the case of already numbers, at 100% coverage [`b2bb044`](https://github.com/minimistjs/minimist/commit/b2bb04436599d77a2ce029e8e555e25b3aa55d13)
|
||||||
|
- another test for higher coverage [`3662624`](https://github.com/minimistjs/minimist/commit/3662624be976d5489d486a856849c048d13be903)
|
||||||
|
|
||||||
|
## [v0.0.9](https://github.com/minimistjs/minimist/compare/v0.0.8...v0.0.9) - 2014-05-08
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Eliminate `longest` fn. [`824f642`](https://github.com/minimistjs/minimist/commit/824f642038d1b02ede68b6261d1d65163390929a)
|
||||||
|
|
||||||
|
## [v0.0.8](https://github.com/minimistjs/minimist/compare/v0.0.7...v0.0.8) - 2014-02-20
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- return '' if flag is string and empty [`fa63ed4`](https://github.com/minimistjs/minimist/commit/fa63ed4651a4ef4eefddce34188e0d98d745a263)
|
||||||
|
- handle joined single letters [`66c248f`](https://github.com/minimistjs/minimist/commit/66c248f0241d4d421d193b022e9e365f11178534)
|
||||||
|
|
||||||
|
## [v0.0.7](https://github.com/minimistjs/minimist/compare/v0.0.6...v0.0.7) - 2014-02-08
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- another swap of .test for .match [`d1da408`](https://github.com/minimistjs/minimist/commit/d1da40819acbe846d89a5c02721211e3c1260dde)
|
||||||
|
|
||||||
|
## [v0.0.6](https://github.com/minimistjs/minimist/compare/v0.0.5...v0.0.6) - 2014-02-08
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- use .test() instead of .match() to not crash on non-string values in the arguments array [`7e0d1ad`](https://github.com/minimistjs/minimist/commit/7e0d1add8c9e5b9b20a4d3d0f9a94d824c578da1)
|
||||||
|
|
||||||
|
## [v0.0.5](https://github.com/minimistjs/minimist/compare/v0.0.4...v0.0.5) - 2013-09-18
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Improve '--' handling. [`b11822c`](https://github.com/minimistjs/minimist/commit/b11822c09cc9d2460f30384d12afc0b953c037a4)
|
||||||
|
|
||||||
|
## [v0.0.4](https://github.com/minimistjs/minimist/compare/v0.0.3...v0.0.4) - 2013-09-17
|
||||||
|
|
||||||
|
## [v0.0.3](https://github.com/minimistjs/minimist/compare/v0.0.2...v0.0.3) - 2013-09-12
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- failing test for single dash preceeding a double dash [`b465514`](https://github.com/minimistjs/minimist/commit/b465514b82c9ae28972d714facd951deb2ad762b)
|
||||||
|
- fix for the dot test [`6a095f1`](https://github.com/minimistjs/minimist/commit/6a095f1d364c8fab2d6753d2291a0649315d297a)
|
||||||
|
|
||||||
|
## [v0.0.2](https://github.com/minimistjs/minimist/compare/v0.0.1...v0.0.2) - 2013-08-28
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- allow dotted aliases & defaults [`321c33e`](https://github.com/minimistjs/minimist/commit/321c33e755485faaeb44eeb1c05d33b2e0a5a7c4)
|
||||||
|
- use a better version of ff [`e40f611`](https://github.com/minimistjs/minimist/commit/e40f61114cf7be6f7947f7b3eed345853a67dbbb)
|
||||||
|
|
||||||
|
## [v0.0.1](https://github.com/minimistjs/minimist/compare/v0.0.0...v0.0.1) - 2013-06-25
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- remove trailing commas [`6ff0fa0`](https://github.com/minimistjs/minimist/commit/6ff0fa055064f15dbe06d50b89d5173a6796e1db)
|
||||||
|
|
||||||
|
## v0.0.0 - 2013-06-25
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- half of the parse test ported [`3079326`](https://github.com/minimistjs/minimist/commit/307932601325087de6cf94188eb798ffc4f3088a)
|
||||||
|
- stripped down code and a passing test from optimist [`7cced88`](https://github.com/minimistjs/minimist/commit/7cced88d82e399d1a03ed23eb667f04d3f320d10)
|
||||||
|
- ported parse tests completely over [`9448754`](https://github.com/minimistjs/minimist/commit/944875452e0820df6830b1408c26a0f7d3e1db04)
|
||||||
|
- docs, package.json [`a5bf46a`](https://github.com/minimistjs/minimist/commit/a5bf46ac9bb3bd114a9c340276c62c1091e538d5)
|
||||||
|
- move more short tests into short.js [`503edb5`](https://github.com/minimistjs/minimist/commit/503edb5c41d89c0d40831ee517154fc13b0f18b9)
|
||||||
|
- default bool test was wrong, not the code [`1b9f5db`](https://github.com/minimistjs/minimist/commit/1b9f5db4741b49962846081b68518de824992097)
|
||||||
|
- passing long tests ripped out of parse.js [`7972c4a`](https://github.com/minimistjs/minimist/commit/7972c4aff1f4803079e1668006658e2a761a0428)
|
||||||
|
- badges [`84c0370`](https://github.com/minimistjs/minimist/commit/84c037063664d42878aace715fe6572ce01b6f3b)
|
||||||
|
- all the tests now ported, some failures [`64239ed`](https://github.com/minimistjs/minimist/commit/64239edfe92c711c4eb0da254fcdfad2a5fdb605)
|
||||||
|
- failing short test [`f8a5341`](https://github.com/minimistjs/minimist/commit/f8a534112dd1138d2fad722def56a848480c446f)
|
||||||
|
- fixed the numeric test [`6b034f3`](https://github.com/minimistjs/minimist/commit/6b034f37c79342c60083ed97fd222e16928aac51)
|
|
@ -0,0 +1,18 @@
|
||||||
|
This software is released under the MIT license:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,121 @@
|
||||||
|
# minimist <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||||
|
|
||||||
|
[![github actions][actions-image]][actions-url]
|
||||||
|
[![coverage][codecov-image]][codecov-url]
|
||||||
|
[![License][license-image]][license-url]
|
||||||
|
[![Downloads][downloads-image]][downloads-url]
|
||||||
|
|
||||||
|
[![npm badge][npm-badge-png]][package-url]
|
||||||
|
|
||||||
|
parse argument options
|
||||||
|
|
||||||
|
This module is the guts of optimist's argument parser without all the
|
||||||
|
fanciful decoration.
|
||||||
|
|
||||||
|
# example
|
||||||
|
|
||||||
|
``` js
|
||||||
|
var argv = require('minimist')(process.argv.slice(2));
|
||||||
|
console.log(argv);
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ node example/parse.js -a beep -b boop
|
||||||
|
{ _: [], a: 'beep', b: 'boop' }
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
|
||||||
|
{
|
||||||
|
_: ['foo', 'bar', 'baz'],
|
||||||
|
x: 3,
|
||||||
|
y: 4,
|
||||||
|
n: 5,
|
||||||
|
a: true,
|
||||||
|
b: true,
|
||||||
|
c: true,
|
||||||
|
beep: 'boop'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# security
|
||||||
|
|
||||||
|
Previous versions had a prototype pollution bug that could cause privilege
|
||||||
|
escalation in some circumstances when handling untrusted user input.
|
||||||
|
|
||||||
|
Please use version 1.2.6 or later:
|
||||||
|
|
||||||
|
* https://security.snyk.io/vuln/SNYK-JS-MINIMIST-2429795 (version <=1.2.5)
|
||||||
|
* https://snyk.io/vuln/SNYK-JS-MINIMIST-559764 (version <=1.2.3)
|
||||||
|
|
||||||
|
# methods
|
||||||
|
|
||||||
|
``` js
|
||||||
|
var parseArgs = require('minimist')
|
||||||
|
```
|
||||||
|
|
||||||
|
## var argv = parseArgs(args, opts={})
|
||||||
|
|
||||||
|
Return an argument object `argv` populated with the array arguments from `args`.
|
||||||
|
|
||||||
|
`argv._` contains all the arguments that didn't have an option associated with
|
||||||
|
them.
|
||||||
|
|
||||||
|
Numeric-looking arguments will be returned as numbers unless `opts.string` or
|
||||||
|
`opts.boolean` is set for that argument name.
|
||||||
|
|
||||||
|
Any arguments after `'--'` will not be parsed and will end up in `argv._`.
|
||||||
|
|
||||||
|
options can be:
|
||||||
|
|
||||||
|
* `opts.string` - a string or array of strings argument names to always treat as
|
||||||
|
strings
|
||||||
|
* `opts.boolean` - a boolean, string or array of strings to always treat as
|
||||||
|
booleans. if `true` will treat all double hyphenated arguments without equal signs
|
||||||
|
as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
|
||||||
|
* `opts.alias` - an object mapping string names to strings or arrays of string
|
||||||
|
argument names to use as aliases
|
||||||
|
* `opts.default` - an object mapping string argument names to default values
|
||||||
|
* `opts.stopEarly` - when true, populate `argv._` with everything after the
|
||||||
|
first non-option
|
||||||
|
* `opts['--']` - when true, populate `argv._` with everything before the `--`
|
||||||
|
and `argv['--']` with everything after the `--`. Here's an example:
|
||||||
|
|
||||||
|
```
|
||||||
|
> require('./')('one two three -- four five --six'.split(' '), { '--': true })
|
||||||
|
{
|
||||||
|
_: ['one', 'two', 'three'],
|
||||||
|
'--': ['four', 'five', '--six']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that with `opts['--']` set, parsing for arguments still stops after the
|
||||||
|
`--`.
|
||||||
|
|
||||||
|
* `opts.unknown` - a function which is invoked with a command line parameter not
|
||||||
|
defined in the `opts` configuration object. If the function returns `false`, the
|
||||||
|
unknown option is not added to `argv`.
|
||||||
|
|
||||||
|
# install
|
||||||
|
|
||||||
|
With [npm](https://npmjs.org) do:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install minimist
|
||||||
|
```
|
||||||
|
|
||||||
|
# license
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[package-url]: https://npmjs.org/package/minimist
|
||||||
|
[npm-version-svg]: https://versionbadg.es/minimistjs/minimist.svg
|
||||||
|
[npm-badge-png]: https://nodei.co/npm/minimist.png?downloads=true&stars=true
|
||||||
|
[license-image]: https://img.shields.io/npm/l/minimist.svg
|
||||||
|
[license-url]: LICENSE
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/minimist.svg
|
||||||
|
[downloads-url]: https://npm-stat.com/charts.html?package=minimist
|
||||||
|
[codecov-image]: https://codecov.io/gh/minimistjs/minimist/branch/main/graphs/badge.svg
|
||||||
|
[codecov-url]: https://app.codecov.io/gh/minimistjs/minimist/
|
||||||
|
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/minimistjs/minimist
|
||||||
|
[actions-url]: https://github.com/minimistjs/minimist/actions
|
|
@ -0,0 +1,4 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var argv = require('../')(process.argv.slice(2));
|
||||||
|
console.log(argv);
|
|
@ -0,0 +1,263 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function hasKey(obj, keys) {
|
||||||
|
var o = obj;
|
||||||
|
keys.slice(0, -1).forEach(function (key) {
|
||||||
|
o = o[key] || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
var key = keys[keys.length - 1];
|
||||||
|
return key in o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNumber(x) {
|
||||||
|
if (typeof x === 'number') { return true; }
|
||||||
|
if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
|
||||||
|
return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConstructorOrProto(obj, key) {
|
||||||
|
return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (args, opts) {
|
||||||
|
if (!opts) { opts = {}; }
|
||||||
|
|
||||||
|
var flags = {
|
||||||
|
bools: {},
|
||||||
|
strings: {},
|
||||||
|
unknownFn: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof opts.unknown === 'function') {
|
||||||
|
flags.unknownFn = opts.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof opts.boolean === 'boolean' && opts.boolean) {
|
||||||
|
flags.allBools = true;
|
||||||
|
} else {
|
||||||
|
[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
|
||||||
|
flags.bools[key] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliases = {};
|
||||||
|
|
||||||
|
function aliasIsBoolean(key) {
|
||||||
|
return aliases[key].some(function (x) {
|
||||||
|
return flags.bools[x];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(opts.alias || {}).forEach(function (key) {
|
||||||
|
aliases[key] = [].concat(opts.alias[key]);
|
||||||
|
aliases[key].forEach(function (x) {
|
||||||
|
aliases[x] = [key].concat(aliases[key].filter(function (y) {
|
||||||
|
return x !== y;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[].concat(opts.string).filter(Boolean).forEach(function (key) {
|
||||||
|
flags.strings[key] = true;
|
||||||
|
if (aliases[key]) {
|
||||||
|
[].concat(aliases[key]).forEach(function (k) {
|
||||||
|
flags.strings[k] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var defaults = opts.default || {};
|
||||||
|
|
||||||
|
var argv = { _: [] };
|
||||||
|
|
||||||
|
function argDefined(key, arg) {
|
||||||
|
return (flags.allBools && (/^--[^=]+$/).test(arg))
|
||||||
|
|| flags.strings[key]
|
||||||
|
|| flags.bools[key]
|
||||||
|
|| aliases[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKey(obj, keys, value) {
|
||||||
|
var o = obj;
|
||||||
|
for (var i = 0; i < keys.length - 1; i++) {
|
||||||
|
var key = keys[i];
|
||||||
|
if (isConstructorOrProto(o, key)) { return; }
|
||||||
|
if (o[key] === undefined) { o[key] = {}; }
|
||||||
|
if (
|
||||||
|
o[key] === Object.prototype
|
||||||
|
|| o[key] === Number.prototype
|
||||||
|
|| o[key] === String.prototype
|
||||||
|
) {
|
||||||
|
o[key] = {};
|
||||||
|
}
|
||||||
|
if (o[key] === Array.prototype) { o[key] = []; }
|
||||||
|
o = o[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastKey = keys[keys.length - 1];
|
||||||
|
if (isConstructorOrProto(o, lastKey)) { return; }
|
||||||
|
if (
|
||||||
|
o === Object.prototype
|
||||||
|
|| o === Number.prototype
|
||||||
|
|| o === String.prototype
|
||||||
|
) {
|
||||||
|
o = {};
|
||||||
|
}
|
||||||
|
if (o === Array.prototype) { o = []; }
|
||||||
|
if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
|
||||||
|
o[lastKey] = value;
|
||||||
|
} else if (Array.isArray(o[lastKey])) {
|
||||||
|
o[lastKey].push(value);
|
||||||
|
} else {
|
||||||
|
o[lastKey] = [o[lastKey], value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setArg(key, val, arg) {
|
||||||
|
if (arg && flags.unknownFn && !argDefined(key, arg)) {
|
||||||
|
if (flags.unknownFn(arg) === false) { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = !flags.strings[key] && isNumber(val)
|
||||||
|
? Number(val)
|
||||||
|
: val;
|
||||||
|
setKey(argv, key.split('.'), value);
|
||||||
|
|
||||||
|
(aliases[key] || []).forEach(function (x) {
|
||||||
|
setKey(argv, x.split('.'), value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(flags.bools).forEach(function (key) {
|
||||||
|
setArg(key, defaults[key] === undefined ? false : defaults[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var notFlags = [];
|
||||||
|
|
||||||
|
if (args.indexOf('--') !== -1) {
|
||||||
|
notFlags = args.slice(args.indexOf('--') + 1);
|
||||||
|
args = args.slice(0, args.indexOf('--'));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
var arg = args[i];
|
||||||
|
var key;
|
||||||
|
var next;
|
||||||
|
|
||||||
|
if ((/^--.+=/).test(arg)) {
|
||||||
|
// Using [\s\S] instead of . because js doesn't support the
|
||||||
|
// 'dotall' regex modifier. See:
|
||||||
|
// http://stackoverflow.com/a/1068308/13216
|
||||||
|
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
|
||||||
|
key = m[1];
|
||||||
|
var value = m[2];
|
||||||
|
if (flags.bools[key]) {
|
||||||
|
value = value !== 'false';
|
||||||
|
}
|
||||||
|
setArg(key, value, arg);
|
||||||
|
} else if ((/^--no-.+/).test(arg)) {
|
||||||
|
key = arg.match(/^--no-(.+)/)[1];
|
||||||
|
setArg(key, false, arg);
|
||||||
|
} else if ((/^--.+/).test(arg)) {
|
||||||
|
key = arg.match(/^--(.+)/)[1];
|
||||||
|
next = args[i + 1];
|
||||||
|
if (
|
||||||
|
next !== undefined
|
||||||
|
&& !(/^(-|--)[^-]/).test(next)
|
||||||
|
&& !flags.bools[key]
|
||||||
|
&& !flags.allBools
|
||||||
|
&& (aliases[key] ? !aliasIsBoolean(key) : true)
|
||||||
|
) {
|
||||||
|
setArg(key, next, arg);
|
||||||
|
i += 1;
|
||||||
|
} else if ((/^(true|false)$/).test(next)) {
|
||||||
|
setArg(key, next === 'true', arg);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
setArg(key, flags.strings[key] ? '' : true, arg);
|
||||||
|
}
|
||||||
|
} else if ((/^-[^-]+/).test(arg)) {
|
||||||
|
var letters = arg.slice(1, -1).split('');
|
||||||
|
|
||||||
|
var broken = false;
|
||||||
|
for (var j = 0; j < letters.length; j++) {
|
||||||
|
next = arg.slice(j + 2);
|
||||||
|
|
||||||
|
if (next === '-') {
|
||||||
|
setArg(letters[j], next, arg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
|
||||||
|
setArg(letters[j], next.slice(1), arg);
|
||||||
|
broken = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(/[A-Za-z]/).test(letters[j])
|
||||||
|
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
|
||||||
|
) {
|
||||||
|
setArg(letters[j], next, arg);
|
||||||
|
broken = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
|
||||||
|
setArg(letters[j], arg.slice(j + 2), arg);
|
||||||
|
broken = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key = arg.slice(-1)[0];
|
||||||
|
if (!broken && key !== '-') {
|
||||||
|
if (
|
||||||
|
args[i + 1]
|
||||||
|
&& !(/^(-|--)[^-]/).test(args[i + 1])
|
||||||
|
&& !flags.bools[key]
|
||||||
|
&& (aliases[key] ? !aliasIsBoolean(key) : true)
|
||||||
|
) {
|
||||||
|
setArg(key, args[i + 1], arg);
|
||||||
|
i += 1;
|
||||||
|
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
|
||||||
|
setArg(key, args[i + 1] === 'true', arg);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
setArg(key, flags.strings[key] ? '' : true, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
|
||||||
|
argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
|
||||||
|
}
|
||||||
|
if (opts.stopEarly) {
|
||||||
|
argv._.push.apply(argv._, args.slice(i + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(defaults).forEach(function (k) {
|
||||||
|
if (!hasKey(argv, k.split('.'))) {
|
||||||
|
setKey(argv, k.split('.'), defaults[k]);
|
||||||
|
|
||||||
|
(aliases[k] || []).forEach(function (x) {
|
||||||
|
setKey(argv, x.split('.'), defaults[k]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts['--']) {
|
||||||
|
argv['--'] = notFlags.slice();
|
||||||
|
} else {
|
||||||
|
notFlags.forEach(function (k) {
|
||||||
|
argv._.push(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return argv;
|
||||||
|
};
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"name": "minimist",
|
||||||
|
"version": "1.2.8",
|
||||||
|
"description": "parse argument options",
|
||||||
|
"main": "index.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"@ljharb/eslint-config": "^21.0.1",
|
||||||
|
"aud": "^2.0.2",
|
||||||
|
"auto-changelog": "^2.4.0",
|
||||||
|
"eslint": "=8.8.0",
|
||||||
|
"in-publish": "^2.0.1",
|
||||||
|
"npmignore": "^0.3.0",
|
||||||
|
"nyc": "^10.3.2",
|
||||||
|
"safe-publish-latest": "^2.0.0",
|
||||||
|
"tape": "^5.6.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "npmignore --auto --commentLines=auto",
|
||||||
|
"prepublishOnly": "safe-publish-latest",
|
||||||
|
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||||
|
"lint": "eslint --ext=js,mjs .",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"tests-only": "nyc tape 'test/**/*.js'",
|
||||||
|
"test": "npm run tests-only",
|
||||||
|
"posttest": "aud --production",
|
||||||
|
"version": "auto-changelog && git add CHANGELOG.md",
|
||||||
|
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||||
|
},
|
||||||
|
"testling": {
|
||||||
|
"files": "test/*.js",
|
||||||
|
"browsers": [
|
||||||
|
"ie/6..latest",
|
||||||
|
"ff/5",
|
||||||
|
"firefox/latest",
|
||||||
|
"chrome/10",
|
||||||
|
"chrome/latest",
|
||||||
|
"safari/5.1",
|
||||||
|
"safari/latest",
|
||||||
|
"opera/12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/minimistjs/minimist.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/minimistjs/minimist",
|
||||||
|
"keywords": [
|
||||||
|
"argv",
|
||||||
|
"getopt",
|
||||||
|
"parser",
|
||||||
|
"optimist"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "James Halliday",
|
||||||
|
"email": "mail@substack.net",
|
||||||
|
"url": "http://substack.net"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"auto-changelog": {
|
||||||
|
"output": "CHANGELOG.md",
|
||||||
|
"template": "keepachangelog",
|
||||||
|
"unreleased": false,
|
||||||
|
"commitLimit": false,
|
||||||
|
"backfillLimit": false,
|
||||||
|
"hideCredit": true
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"ignore": [
|
||||||
|
".github/workflows"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('flag boolean true (default all --args to boolean)', function (t) {
|
||||||
|
var argv = parse(['moo', '--honk', 'cow'], {
|
||||||
|
boolean: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
honk: true,
|
||||||
|
_: ['moo', 'cow'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(typeof argv.honk, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flag boolean true only affects double hyphen arguments without equals signs', function (t) {
|
||||||
|
var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], {
|
||||||
|
boolean: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
honk: true,
|
||||||
|
tacos: 'good',
|
||||||
|
p: 55,
|
||||||
|
_: ['moo', 'cow'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(typeof argv.honk, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,177 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('flag boolean default false', function (t) {
|
||||||
|
var argv = parse(['moo'], {
|
||||||
|
boolean: ['t', 'verbose'],
|
||||||
|
default: { verbose: false, t: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
verbose: false,
|
||||||
|
t: false,
|
||||||
|
_: ['moo'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(typeof argv.verbose, 'boolean');
|
||||||
|
t.deepEqual(typeof argv.t, 'boolean');
|
||||||
|
t.end();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean groups', function (t) {
|
||||||
|
var argv = parse(['-x', '-z', 'one', 'two', 'three'], {
|
||||||
|
boolean: ['x', 'y', 'z'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
x: true,
|
||||||
|
y: false,
|
||||||
|
z: true,
|
||||||
|
_: ['one', 'two', 'three'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(typeof argv.x, 'boolean');
|
||||||
|
t.deepEqual(typeof argv.y, 'boolean');
|
||||||
|
t.deepEqual(typeof argv.z, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('boolean and alias with chainable api', function (t) {
|
||||||
|
var aliased = ['-h', 'derp'];
|
||||||
|
var regular = ['--herp', 'derp'];
|
||||||
|
var aliasedArgv = parse(aliased, {
|
||||||
|
boolean: 'herp',
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
});
|
||||||
|
var propertyArgv = parse(regular, {
|
||||||
|
boolean: 'herp',
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
});
|
||||||
|
var expected = {
|
||||||
|
herp: true,
|
||||||
|
h: true,
|
||||||
|
_: ['derp'],
|
||||||
|
};
|
||||||
|
|
||||||
|
t.same(aliasedArgv, expected);
|
||||||
|
t.same(propertyArgv, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean and alias with options hash', function (t) {
|
||||||
|
var aliased = ['-h', 'derp'];
|
||||||
|
var regular = ['--herp', 'derp'];
|
||||||
|
var opts = {
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
boolean: 'herp',
|
||||||
|
};
|
||||||
|
var aliasedArgv = parse(aliased, opts);
|
||||||
|
var propertyArgv = parse(regular, opts);
|
||||||
|
var expected = {
|
||||||
|
herp: true,
|
||||||
|
h: true,
|
||||||
|
_: ['derp'],
|
||||||
|
};
|
||||||
|
t.same(aliasedArgv, expected);
|
||||||
|
t.same(propertyArgv, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean and alias array with options hash', function (t) {
|
||||||
|
var aliased = ['-h', 'derp'];
|
||||||
|
var regular = ['--herp', 'derp'];
|
||||||
|
var alt = ['--harp', 'derp'];
|
||||||
|
var opts = {
|
||||||
|
alias: { h: ['herp', 'harp'] },
|
||||||
|
boolean: 'h',
|
||||||
|
};
|
||||||
|
var aliasedArgv = parse(aliased, opts);
|
||||||
|
var propertyArgv = parse(regular, opts);
|
||||||
|
var altPropertyArgv = parse(alt, opts);
|
||||||
|
var expected = {
|
||||||
|
harp: true,
|
||||||
|
herp: true,
|
||||||
|
h: true,
|
||||||
|
_: ['derp'],
|
||||||
|
};
|
||||||
|
t.same(aliasedArgv, expected);
|
||||||
|
t.same(propertyArgv, expected);
|
||||||
|
t.same(altPropertyArgv, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean and alias using explicit true', function (t) {
|
||||||
|
var aliased = ['-h', 'true'];
|
||||||
|
var regular = ['--herp', 'true'];
|
||||||
|
var opts = {
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
boolean: 'h',
|
||||||
|
};
|
||||||
|
var aliasedArgv = parse(aliased, opts);
|
||||||
|
var propertyArgv = parse(regular, opts);
|
||||||
|
var expected = {
|
||||||
|
herp: true,
|
||||||
|
h: true,
|
||||||
|
_: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
t.same(aliasedArgv, expected);
|
||||||
|
t.same(propertyArgv, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// regression, see https://github.com/substack/node-optimist/issues/71
|
||||||
|
test('boolean and --x=true', function (t) {
|
||||||
|
var parsed = parse(['--boool', '--other=true'], {
|
||||||
|
boolean: 'boool',
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(parsed.boool, true);
|
||||||
|
t.same(parsed.other, 'true');
|
||||||
|
|
||||||
|
parsed = parse(['--boool', '--other=false'], {
|
||||||
|
boolean: 'boool',
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(parsed.boool, true);
|
||||||
|
t.same(parsed.other, 'false');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean --boool=true', function (t) {
|
||||||
|
var parsed = parse(['--boool=true'], {
|
||||||
|
default: {
|
||||||
|
boool: false,
|
||||||
|
},
|
||||||
|
boolean: ['boool'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(parsed.boool, true);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean --boool=false', function (t) {
|
||||||
|
var parsed = parse(['--boool=false'], {
|
||||||
|
default: {
|
||||||
|
boool: true,
|
||||||
|
},
|
||||||
|
boolean: ['boool'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(parsed.boool, false);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean using something similar to true', function (t) {
|
||||||
|
var opts = { boolean: 'h' };
|
||||||
|
var result = parse(['-h', 'true.txt'], opts);
|
||||||
|
var expected = {
|
||||||
|
h: true,
|
||||||
|
_: ['true.txt'],
|
||||||
|
};
|
||||||
|
|
||||||
|
t.same(result, expected);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('-', function (t) {
|
||||||
|
t.plan(6);
|
||||||
|
t.deepEqual(parse(['-n', '-']), { n: '-', _: [] });
|
||||||
|
t.deepEqual(parse(['--nnn', '-']), { nnn: '-', _: [] });
|
||||||
|
t.deepEqual(parse(['-']), { _: ['-'] });
|
||||||
|
t.deepEqual(parse(['-f-']), { f: '-', _: [] });
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-b', '-'], { boolean: 'b' }),
|
||||||
|
{ b: true, _: ['-'] }
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-s', '-'], { string: 's' }),
|
||||||
|
{ s: '-', _: [] }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('-a -- b', function (t) {
|
||||||
|
t.plan(2);
|
||||||
|
t.deepEqual(parse(['-a', '--', 'b']), { a: true, _: ['b'] });
|
||||||
|
t.deepEqual(parse(['--a', '--', 'b']), { a: true, _: ['b'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('move arguments after the -- into their own `--` array', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--name', 'John', 'before', '--', 'after'], { '--': true }),
|
||||||
|
{ name: 'John', _: ['before'], '--': ['after'] }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('--- option value', function (t) {
|
||||||
|
// A multi-dash value is largely an edge case, but check the behaviour is as expected,
|
||||||
|
// and in particular the same for short option and long option (as made consistent in Jan 2023).
|
||||||
|
t.plan(2);
|
||||||
|
t.deepEqual(parse(['-n', '---']), { n: '---', _: [] });
|
||||||
|
t.deepEqual(parse(['--nnn', '---']), { nnn: '---', _: [] });
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var test = require('tape');
|
||||||
|
var parse = require('../');
|
||||||
|
|
||||||
|
test('boolean default true', function (t) {
|
||||||
|
var argv = parse([], {
|
||||||
|
boolean: 'sometrue',
|
||||||
|
default: { sometrue: true },
|
||||||
|
});
|
||||||
|
t.equal(argv.sometrue, true);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean default false', function (t) {
|
||||||
|
var argv = parse([], {
|
||||||
|
boolean: 'somefalse',
|
||||||
|
default: { somefalse: false },
|
||||||
|
});
|
||||||
|
t.equal(argv.somefalse, false);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('boolean default to null', function (t) {
|
||||||
|
var argv = parse([], {
|
||||||
|
boolean: 'maybe',
|
||||||
|
default: { maybe: null },
|
||||||
|
});
|
||||||
|
t.equal(argv.maybe, null);
|
||||||
|
|
||||||
|
var argvLong = parse(['--maybe'], {
|
||||||
|
boolean: 'maybe',
|
||||||
|
default: { maybe: null },
|
||||||
|
});
|
||||||
|
t.equal(argvLong.maybe, true);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('dotted alias', function (t) {
|
||||||
|
var argv = parse(['--a.b', '22'], { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } });
|
||||||
|
t.equal(argv.a.b, 22);
|
||||||
|
t.equal(argv.aa.bb, 22);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dotted default', function (t) {
|
||||||
|
var argv = parse('', { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } });
|
||||||
|
t.equal(argv.a.b, 11);
|
||||||
|
t.equal(argv.aa.bb, 11);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dotted default with no alias', function (t) {
|
||||||
|
var argv = parse('', { default: { 'a.b': 11 } });
|
||||||
|
t.equal(argv.a.b, 11);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('short -k=v', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var argv = parse(['-b=123']);
|
||||||
|
t.deepEqual(argv, { b: 123, _: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multi short -k=v', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var argv = parse(['-a=whatever', '-b=robots']);
|
||||||
|
t.deepEqual(argv, { a: 'whatever', b: 'robots', _: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('short with embedded equals -k=a=b', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var argv = parse(['-k=a=b']);
|
||||||
|
t.deepEqual(argv, { k: 'a=b', _: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('short with later equals like -ab=c', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var argv = parse(['-ab=c']);
|
||||||
|
t.deepEqual(argv, { a: true, b: 'c', _: [] });
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var test = require('tape');
|
||||||
|
var parse = require('../');
|
||||||
|
|
||||||
|
test('long opts', function (t) {
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--bool']),
|
||||||
|
{ bool: true, _: [] },
|
||||||
|
'long boolean'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--pow', 'xixxle']),
|
||||||
|
{ pow: 'xixxle', _: [] },
|
||||||
|
'long capture sp'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--pow=xixxle']),
|
||||||
|
{ pow: 'xixxle', _: [] },
|
||||||
|
'long capture eq'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--host', 'localhost', '--port', '555']),
|
||||||
|
{ host: 'localhost', port: 555, _: [] },
|
||||||
|
'long captures sp'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--host=localhost', '--port=555']),
|
||||||
|
{ host: 'localhost', port: 555, _: [] },
|
||||||
|
'long captures eq'
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('nums', function (t) {
|
||||||
|
var argv = parse([
|
||||||
|
'-x', '1234',
|
||||||
|
'-y', '5.67',
|
||||||
|
'-z', '1e7',
|
||||||
|
'-w', '10f',
|
||||||
|
'--hex', '0xdeadbeef',
|
||||||
|
'789',
|
||||||
|
]);
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
x: 1234,
|
||||||
|
y: 5.67,
|
||||||
|
z: 1e7,
|
||||||
|
w: '10f',
|
||||||
|
hex: 0xdeadbeef,
|
||||||
|
_: [789],
|
||||||
|
});
|
||||||
|
t.deepEqual(typeof argv.x, 'number');
|
||||||
|
t.deepEqual(typeof argv.y, 'number');
|
||||||
|
t.deepEqual(typeof argv.z, 'number');
|
||||||
|
t.deepEqual(typeof argv.w, 'string');
|
||||||
|
t.deepEqual(typeof argv.hex, 'number');
|
||||||
|
t.deepEqual(typeof argv._[0], 'number');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('already a number', function (t) {
|
||||||
|
var argv = parse(['-x', 1234, 789]);
|
||||||
|
t.deepEqual(argv, { x: 1234, _: [789] });
|
||||||
|
t.deepEqual(typeof argv.x, 'number');
|
||||||
|
t.deepEqual(typeof argv._[0], 'number');
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,209 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('parse args', function (t) {
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['--no-moo']),
|
||||||
|
{ moo: false, _: [] },
|
||||||
|
'no'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-v', 'a', '-v', 'b', '-v', 'c']),
|
||||||
|
{ v: ['a', 'b', 'c'], _: [] },
|
||||||
|
'multi'
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('comprehensive', function (t) {
|
||||||
|
t.deepEqual(
|
||||||
|
parse([
|
||||||
|
'--name=meowmers', 'bare', '-cats', 'woo',
|
||||||
|
'-h', 'awesome', '--multi=quux',
|
||||||
|
'--key', 'value',
|
||||||
|
'-b', '--bool', '--no-meep', '--multi=baz',
|
||||||
|
'--', '--not-a-flag', 'eek',
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
c: true,
|
||||||
|
a: true,
|
||||||
|
t: true,
|
||||||
|
s: 'woo',
|
||||||
|
h: 'awesome',
|
||||||
|
b: true,
|
||||||
|
bool: true,
|
||||||
|
key: 'value',
|
||||||
|
multi: ['quux', 'baz'],
|
||||||
|
meep: false,
|
||||||
|
name: 'meowmers',
|
||||||
|
_: ['bare', '--not-a-flag', 'eek'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flag boolean', function (t) {
|
||||||
|
var argv = parse(['-t', 'moo'], { boolean: 't' });
|
||||||
|
t.deepEqual(argv, { t: true, _: ['moo'] });
|
||||||
|
t.deepEqual(typeof argv.t, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flag boolean value', function (t) {
|
||||||
|
var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
|
||||||
|
boolean: ['t', 'verbose'],
|
||||||
|
default: { verbose: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
verbose: false,
|
||||||
|
t: true,
|
||||||
|
_: ['moo'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(typeof argv.verbose, 'boolean');
|
||||||
|
t.deepEqual(typeof argv.t, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('newlines in params', function (t) {
|
||||||
|
var args = parse(['-s', 'X\nX']);
|
||||||
|
t.deepEqual(args, { _: [], s: 'X\nX' });
|
||||||
|
|
||||||
|
// reproduce in bash:
|
||||||
|
// VALUE="new
|
||||||
|
// line"
|
||||||
|
// node program.js --s="$VALUE"
|
||||||
|
args = parse(['--s=X\nX']);
|
||||||
|
t.deepEqual(args, { _: [], s: 'X\nX' });
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strings', function (t) {
|
||||||
|
var s = parse(['-s', '0001234'], { string: 's' }).s;
|
||||||
|
t.equal(s, '0001234');
|
||||||
|
t.equal(typeof s, 'string');
|
||||||
|
|
||||||
|
var x = parse(['-x', '56'], { string: 'x' }).x;
|
||||||
|
t.equal(x, '56');
|
||||||
|
t.equal(typeof x, 'string');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stringArgs', function (t) {
|
||||||
|
var s = parse([' ', ' '], { string: '_' })._;
|
||||||
|
t.same(s.length, 2);
|
||||||
|
t.same(typeof s[0], 'string');
|
||||||
|
t.same(s[0], ' ');
|
||||||
|
t.same(typeof s[1], 'string');
|
||||||
|
t.same(s[1], ' ');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('empty strings', function (t) {
|
||||||
|
var s = parse(['-s'], { string: 's' }).s;
|
||||||
|
t.equal(s, '');
|
||||||
|
t.equal(typeof s, 'string');
|
||||||
|
|
||||||
|
var str = parse(['--str'], { string: 'str' }).str;
|
||||||
|
t.equal(str, '');
|
||||||
|
t.equal(typeof str, 'string');
|
||||||
|
|
||||||
|
var letters = parse(['-art'], {
|
||||||
|
string: ['a', 't'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.equal(letters.a, '');
|
||||||
|
t.equal(letters.r, true);
|
||||||
|
t.equal(letters.t, '');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string and alias', function (t) {
|
||||||
|
var x = parse(['--str', '000123'], {
|
||||||
|
string: 's',
|
||||||
|
alias: { s: 'str' },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.equal(x.str, '000123');
|
||||||
|
t.equal(typeof x.str, 'string');
|
||||||
|
t.equal(x.s, '000123');
|
||||||
|
t.equal(typeof x.s, 'string');
|
||||||
|
|
||||||
|
var y = parse(['-s', '000123'], {
|
||||||
|
string: 'str',
|
||||||
|
alias: { str: 's' },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.equal(y.str, '000123');
|
||||||
|
t.equal(typeof y.str, 'string');
|
||||||
|
t.equal(y.s, '000123');
|
||||||
|
t.equal(typeof y.s, 'string');
|
||||||
|
|
||||||
|
var z = parse(['-s123'], {
|
||||||
|
alias: { str: ['s', 'S'] },
|
||||||
|
string: ['str'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(
|
||||||
|
z,
|
||||||
|
{ _: [], s: '123', S: '123', str: '123' },
|
||||||
|
'opt.string works with multiple aliases'
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('slashBreak', function (t) {
|
||||||
|
t.same(
|
||||||
|
parse(['-I/foo/bar/baz']),
|
||||||
|
{ I: '/foo/bar/baz', _: [] }
|
||||||
|
);
|
||||||
|
t.same(
|
||||||
|
parse(['-xyz/foo/bar/baz']),
|
||||||
|
{ x: true, y: true, z: '/foo/bar/baz', _: [] }
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('alias', function (t) {
|
||||||
|
var argv = parse(['-f', '11', '--zoom', '55'], {
|
||||||
|
alias: { z: 'zoom' },
|
||||||
|
});
|
||||||
|
t.equal(argv.zoom, 55);
|
||||||
|
t.equal(argv.z, argv.zoom);
|
||||||
|
t.equal(argv.f, 11);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiAlias', function (t) {
|
||||||
|
var argv = parse(['-f', '11', '--zoom', '55'], {
|
||||||
|
alias: { z: ['zm', 'zoom'] },
|
||||||
|
});
|
||||||
|
t.equal(argv.zoom, 55);
|
||||||
|
t.equal(argv.z, argv.zoom);
|
||||||
|
t.equal(argv.z, argv.zm);
|
||||||
|
t.equal(argv.f, 11);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nested dotted objects', function (t) {
|
||||||
|
var argv = parse([
|
||||||
|
'--foo.bar', '3', '--foo.baz', '4',
|
||||||
|
'--foo.quux.quibble', '5', '--foo.quux.o_O',
|
||||||
|
'--beep.boop',
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.same(argv.foo, {
|
||||||
|
bar: 3,
|
||||||
|
baz: 4,
|
||||||
|
quux: {
|
||||||
|
quibble: 5,
|
||||||
|
o_O: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.same(argv.beep, { boop: true });
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('parse with modifier functions', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var argv = parse(['-b', '123'], { boolean: 'b' });
|
||||||
|
t.deepEqual(argv, { b: true, _: [123] });
|
||||||
|
});
|
|
@ -0,0 +1,64 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* eslint no-proto: 0 */
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('proto pollution', function (t) {
|
||||||
|
var argv = parse(['--__proto__.x', '123']);
|
||||||
|
t.equal({}.x, undefined);
|
||||||
|
t.equal(argv.__proto__.x, undefined);
|
||||||
|
t.equal(argv.x, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proto pollution (array)', function (t) {
|
||||||
|
var argv = parse(['--x', '4', '--x', '5', '--x.__proto__.z', '789']);
|
||||||
|
t.equal({}.z, undefined);
|
||||||
|
t.deepEqual(argv.x, [4, 5]);
|
||||||
|
t.equal(argv.x.z, undefined);
|
||||||
|
t.equal(argv.x.__proto__.z, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proto pollution (number)', function (t) {
|
||||||
|
var argv = parse(['--x', '5', '--x.__proto__.z', '100']);
|
||||||
|
t.equal({}.z, undefined);
|
||||||
|
t.equal((4).z, undefined);
|
||||||
|
t.equal(argv.x, 5);
|
||||||
|
t.equal(argv.x.z, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proto pollution (string)', function (t) {
|
||||||
|
var argv = parse(['--x', 'abc', '--x.__proto__.z', 'def']);
|
||||||
|
t.equal({}.z, undefined);
|
||||||
|
t.equal('...'.z, undefined);
|
||||||
|
t.equal(argv.x, 'abc');
|
||||||
|
t.equal(argv.x.z, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proto pollution (constructor)', function (t) {
|
||||||
|
var argv = parse(['--constructor.prototype.y', '123']);
|
||||||
|
t.equal({}.y, undefined);
|
||||||
|
t.equal(argv.y, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proto pollution (constructor function)', function (t) {
|
||||||
|
var argv = parse(['--_.concat.constructor.prototype.y', '123']);
|
||||||
|
function fnToBeTested() {}
|
||||||
|
t.equal(fnToBeTested.y, undefined);
|
||||||
|
t.equal(argv.y, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// powered by snyk - https://github.com/backstage/backstage/issues/10343
|
||||||
|
test('proto pollution (constructor function) snyk', function (t) {
|
||||||
|
var argv = parse('--_.constructor.constructor.prototype.foo bar'.split(' '));
|
||||||
|
t.equal(function () {}.foo, undefined);
|
||||||
|
t.equal(argv.y, undefined);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,69 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('numeric short args', function (t) {
|
||||||
|
t.plan(2);
|
||||||
|
t.deepEqual(parse(['-n123']), { n: 123, _: [] });
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-123', '456']),
|
||||||
|
{ 1: true, 2: true, 3: 456, _: [] }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('short', function (t) {
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-b']),
|
||||||
|
{ b: true, _: [] },
|
||||||
|
'short boolean'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['foo', 'bar', 'baz']),
|
||||||
|
{ _: ['foo', 'bar', 'baz'] },
|
||||||
|
'bare'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-cats']),
|
||||||
|
{ c: true, a: true, t: true, s: true, _: [] },
|
||||||
|
'group'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-cats', 'meow']),
|
||||||
|
{ c: true, a: true, t: true, s: 'meow', _: [] },
|
||||||
|
'short group next'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-h', 'localhost']),
|
||||||
|
{ h: 'localhost', _: [] },
|
||||||
|
'short capture'
|
||||||
|
);
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-h', 'localhost', '-p', '555']),
|
||||||
|
{ h: 'localhost', p: 555, _: [] },
|
||||||
|
'short captures'
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mixed short bool and capture', function (t) {
|
||||||
|
t.same(
|
||||||
|
parse(['-h', 'localhost', '-fp', '555', 'script.js']),
|
||||||
|
{
|
||||||
|
f: true, p: 555, h: 'localhost',
|
||||||
|
_: ['script.js'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('short and long', function (t) {
|
||||||
|
t.deepEqual(
|
||||||
|
parse(['-h', 'localhost', '-fp', '555', 'script.js']),
|
||||||
|
{
|
||||||
|
f: true, p: 555, h: 'localhost',
|
||||||
|
_: ['script.js'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('stops parsing on the first non-option when stopEarly is set', function (t) {
|
||||||
|
var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], {
|
||||||
|
stopEarly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.deepEqual(argv, {
|
||||||
|
aaa: 'bbb',
|
||||||
|
_: ['ccc', '--ddd'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('boolean and alias is not unknown', function (t) {
|
||||||
|
var unknown = [];
|
||||||
|
function unknownFn(arg) {
|
||||||
|
unknown.push(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var aliased = ['-h', 'true', '--derp', 'true'];
|
||||||
|
var regular = ['--herp', 'true', '-d', 'true'];
|
||||||
|
var opts = {
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
boolean: 'h',
|
||||||
|
unknown: unknownFn,
|
||||||
|
};
|
||||||
|
parse(aliased, opts);
|
||||||
|
parse(regular, opts);
|
||||||
|
|
||||||
|
t.same(unknown, ['--derp', '-d']);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flag boolean true any double hyphen argument is not unknown', function (t) {
|
||||||
|
var unknown = [];
|
||||||
|
function unknownFn(arg) {
|
||||||
|
unknown.push(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], {
|
||||||
|
boolean: true,
|
||||||
|
unknown: unknownFn,
|
||||||
|
});
|
||||||
|
t.same(unknown, ['--tacos=good', 'cow', '-p']);
|
||||||
|
t.same(argv, {
|
||||||
|
honk: true,
|
||||||
|
_: [],
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string and alias is not unknown', function (t) {
|
||||||
|
var unknown = [];
|
||||||
|
function unknownFn(arg) {
|
||||||
|
unknown.push(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var aliased = ['-h', 'hello', '--derp', 'goodbye'];
|
||||||
|
var regular = ['--herp', 'hello', '-d', 'moon'];
|
||||||
|
var opts = {
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
string: 'h',
|
||||||
|
unknown: unknownFn,
|
||||||
|
};
|
||||||
|
parse(aliased, opts);
|
||||||
|
parse(regular, opts);
|
||||||
|
|
||||||
|
t.same(unknown, ['--derp', '-d']);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default and alias is not unknown', function (t) {
|
||||||
|
var unknown = [];
|
||||||
|
function unknownFn(arg) {
|
||||||
|
unknown.push(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var aliased = ['-h', 'hello'];
|
||||||
|
var regular = ['--herp', 'hello'];
|
||||||
|
var opts = {
|
||||||
|
default: { h: 'bar' },
|
||||||
|
alias: { h: 'herp' },
|
||||||
|
unknown: unknownFn,
|
||||||
|
};
|
||||||
|
parse(aliased, opts);
|
||||||
|
parse(regular, opts);
|
||||||
|
|
||||||
|
t.same(unknown, []);
|
||||||
|
t.end();
|
||||||
|
unknownFn(); // exercise fn for 100% coverage
|
||||||
|
});
|
||||||
|
|
||||||
|
test('value following -- is not unknown', function (t) {
|
||||||
|
var unknown = [];
|
||||||
|
function unknownFn(arg) {
|
||||||
|
unknown.push(arg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var aliased = ['--bad', '--', 'good', 'arg'];
|
||||||
|
var opts = {
|
||||||
|
'--': true,
|
||||||
|
unknown: unknownFn,
|
||||||
|
};
|
||||||
|
var argv = parse(aliased, opts);
|
||||||
|
|
||||||
|
t.same(unknown, ['--bad']);
|
||||||
|
t.same(argv, {
|
||||||
|
'--': ['good', 'arg'],
|
||||||
|
_: [],
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var parse = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('whitespace should be whitespace', function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
var x = parse(['-x', '\t']).x;
|
||||||
|
t.equal(x, '\t');
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
Copyright 2010 James Halliday (mail@substack.net)
|
||||||
|
|
||||||
|
This project is free software released under the MIT/X11 license:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var mkdirp = require('../');
|
||||||
|
var minimist = require('minimist');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var argv = minimist(process.argv.slice(2), {
|
||||||
|
alias: { m: 'mode', h: 'help' },
|
||||||
|
string: [ 'mode' ]
|
||||||
|
});
|
||||||
|
if (argv.help) {
|
||||||
|
fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths = argv._.slice();
|
||||||
|
var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;
|
||||||
|
|
||||||
|
(function next () {
|
||||||
|
if (paths.length === 0) return;
|
||||||
|
var p = paths.shift();
|
||||||
|
|
||||||
|
if (mode === undefined) mkdirp(p, cb)
|
||||||
|
else mkdirp(p, mode, cb)
|
||||||
|
|
||||||
|
function cb (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
else next();
|
||||||
|
}
|
||||||
|
})();
|