added models.
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# python-venv excluded
|
||||||
|
include/
|
||||||
|
bin/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
share/
|
||||||
|
lib64
|
||||||
|
pyvenv.cfg
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const multer = require("multer");
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, "./public/images");
|
||||||
|
},
|
||||||
|
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
const uniquePrefix = Date.now();
|
||||||
|
cb(null, uniquePrefix + "-" + file.originalname);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({ storage: storage });
|
||||||
|
|
||||||
|
module.exports = upload;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Import libraries
|
||||||
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
from transformers import ViTImageProcessor, ViTForImageClassification
|
||||||
|
|
||||||
|
# Load the image processor and model
|
||||||
|
model_name = 'vishnun0027/Crop_Disease_model_1'
|
||||||
|
image_processor = ViTImageProcessor.from_pretrained(model_name)
|
||||||
|
model = ViTForImageClassification.from_pretrained(
|
||||||
|
model_name,
|
||||||
|
ignore_mismatched_sizes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load image
|
||||||
|
try:
|
||||||
|
image = Image.open('/home/overnion/Status200/tomato.png')
|
||||||
|
# Convert the image to RGB if it's not already
|
||||||
|
if (image.mode != 'RGB'):
|
||||||
|
image = image.convert('RGB')
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: Image file not found.")
|
||||||
|
exit()
|
||||||
|
except UnidentifiedImageError:
|
||||||
|
print("Error: Unable to open image. Check the file type.")
|
||||||
|
exit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Preparing the image for the model
|
||||||
|
inputs = image_processor(images=image, return_tensors="pt")
|
||||||
|
|
||||||
|
# Make the prediction
|
||||||
|
outputs = model(**inputs)
|
||||||
|
logits = outputs.logits
|
||||||
|
predicted_class_idx = logits.argmax(-1).item()
|
||||||
|
|
||||||
|
# Print the predicted class
|
||||||
|
print("Predicted class:", model.config.id2label[predicted_class_idx])
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# For executing: python3 app.py /path/to/file
|
||||||
|
# Import libraries
|
||||||
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
from transformers import ViTImageProcessor, ViTForImageClassification
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Specify the local directory where the model files are stored
|
||||||
|
local_model_path = '/home/overnion/Status200/models/pretrained'
|
||||||
|
|
||||||
|
# Check if the image path is provided
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Error: No image path provided. Please provide the path to the image as an argument.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
# Load the image processor and model
|
||||||
|
model_name = 'vishnun0027/Crop_Disease_model_1'
|
||||||
|
image_processor = ViTImageProcessor.from_pretrained(model_name)
|
||||||
|
model = ViTForImageClassification.from_pretrained(
|
||||||
|
model_name,
|
||||||
|
ignore_mismatched_sizes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load image
|
||||||
|
image_path = sys.argv[1] # Get the image path from command line arguments
|
||||||
|
try:
|
||||||
|
image = Image.open(image_path)
|
||||||
|
# Convert the image to RGB if it's not already
|
||||||
|
if image.mode != 'RGB':
|
||||||
|
image = image.convert('RGB')
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: Image file not found.")
|
||||||
|
exit()
|
||||||
|
except UnidentifiedImageError:
|
||||||
|
print("Error: Unable to open image. Check the file type.")
|
||||||
|
exit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Preparing the image for the model
|
||||||
|
inputs = image_processor(images=image, return_tensors="pt")
|
||||||
|
|
||||||
|
# Make the prediction
|
||||||
|
outputs = model(**inputs)
|
||||||
|
logits = outputs.logits
|
||||||
|
predicted_class_idx = logits.argmax(-1).item()
|
||||||
|
|
||||||
|
# Print the predicted class
|
||||||
|
print(model.config.id2label[predicted_class_idx])
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
from transformers import ViTImageProcessor, ViTForImageClassification
|
||||||
|
|
||||||
|
# Load the image processor and model
|
||||||
|
model_name = 'wambugu71/crop_leaf_diseases_vit'
|
||||||
|
image_processor = ViTImageProcessor.from_pretrained(model_name)
|
||||||
|
model = ViTForImageClassification.from_pretrained(
|
||||||
|
model_name,
|
||||||
|
ignore_mismatched_sizes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load your image
|
||||||
|
try:
|
||||||
|
image = Image.open('/home/overnion/Status200/potato2.png') # Replace with the actual path to your image
|
||||||
|
# Convert the image to RGB if it's not already
|
||||||
|
if image.mode != 'RGB':
|
||||||
|
image = image.convert('RGB')
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: Image file not found.")
|
||||||
|
exit()
|
||||||
|
except UnidentifiedImageError:
|
||||||
|
print("Error: Unable to open image. Check the file type.")
|
||||||
|
exit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Prepare the image for the model
|
||||||
|
inputs = image_processor(images=image, return_tensors="pt")
|
||||||
|
|
||||||
|
# Make the prediction
|
||||||
|
outputs = model(**inputs)
|
||||||
|
logits = outputs.logits
|
||||||
|
predicted_class_idx = logits.argmax(-1).item()
|
||||||
|
|
||||||
|
# Print the predicted class
|
||||||
|
print("Predicted class:", model.config.id2label[predicted_class_idx])
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
from transformers import CLIPModel, CLIPTokenizer
|
||||||
|
|
||||||
|
# Load the model
|
||||||
|
model_name = "TonyStarkD99/CLIP-Crop_Disease-Large"
|
||||||
|
model = CLIPModel.from_pretrained(model_name)
|
||||||
|
|
||||||
|
# Load your image
|
||||||
|
image_path = "/home/overnion/Status200/rice.png" # Replace with your image path
|
||||||
|
image = Image.open(image_path)
|
||||||
|
|
||||||
|
# Define the class labels (text prompts)
|
||||||
|
class_labels = [
|
||||||
|
"healthy plant",
|
||||||
|
"powdery mildew",
|
||||||
|
"leaf rust",
|
||||||
|
"stem rust",
|
||||||
|
"fusarium head blight",
|
||||||
|
"gray leaf spot",
|
||||||
|
"bacterial blight",
|
||||||
|
"downy mildew",
|
||||||
|
"aphid infestation",
|
||||||
|
"white mold",
|
||||||
|
"black rot",
|
||||||
|
"root rot",
|
||||||
|
"yellow leaf curl",
|
||||||
|
"blight",
|
||||||
|
"necrotic spots",
|
||||||
|
"chlorosis",
|
||||||
|
"wilt",
|
||||||
|
"damping off",
|
||||||
|
"viral infection",
|
||||||
|
"pest damage"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Resize and normalize the image
|
||||||
|
image = image.convert("RGB") # Ensure the image is in RGB format
|
||||||
|
image = image.resize((224, 224)) # Resize to the expected input size
|
||||||
|
|
||||||
|
# Convert the image to a tensor
|
||||||
|
image_tensor = torch.tensor(np.array(image)).permute(2, 0, 1).unsqueeze(0) # Convert to (1, C, H, W)
|
||||||
|
image_tensor = image_tensor.float() / 255.0 # Normalize to [0, 1]
|
||||||
|
|
||||||
|
# Load the tokenizer
|
||||||
|
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch16") # Use a compatible tokenizer
|
||||||
|
|
||||||
|
# Tokenize the text prompts
|
||||||
|
text_inputs = tokenizer(class_labels, padding=True, return_tensors="pt")
|
||||||
|
|
||||||
|
# Make predictions
|
||||||
|
with torch.no_grad():
|
||||||
|
outputs = model(pixel_values=image_tensor, input_ids=text_inputs['input_ids'])
|
||||||
|
|
||||||
|
logits_per_image = outputs.logits_per_image # This gives the similarity scores
|
||||||
|
probs = logits_per_image.softmax(dim=1) # Convert to probabilities
|
||||||
|
|
||||||
|
# Get the predicted class
|
||||||
|
predicted_class_idx = probs.argmax().item()
|
||||||
|
predicted_class = class_labels[predicted_class_idx]
|
||||||
|
|
||||||
|
# Print the predicted class and probabilities
|
||||||
|
print("Predicted class:", predicted_class)
|
||||||
|
# print("Probabilities:", probs.detach().numpy())
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
from transformers import CLIPModel, CLIPTokenizer
|
||||||
|
|
||||||
|
# Load the model
|
||||||
|
model_name = "TonyStarkD99/CLIP-Crop_Disease-Large"
|
||||||
|
model = CLIPModel.from_pretrained(model_name)
|
||||||
|
|
||||||
|
# Load your image
|
||||||
|
image_path = "/home/overnion/Status200/tomato.png" # Replace with your image path
|
||||||
|
image = Image.open(image_path)
|
||||||
|
|
||||||
|
# Define the class labels (text prompts)
|
||||||
|
class_labels = [
|
||||||
|
"healthy plant",
|
||||||
|
"diseased plant",
|
||||||
|
"wilted plant",
|
||||||
|
"pest-infested plant"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Resize and normalize the image
|
||||||
|
image = image.convert("RGB") # Ensure the image is in RGB format
|
||||||
|
image = image.resize((224, 224)) # Resize to the expected input size
|
||||||
|
|
||||||
|
# Convert the image to a tensor
|
||||||
|
image_tensor = torch.tensor(np.array(image)).permute(2, 0, 1).unsqueeze(0) # Convert to (1, C, H, W)
|
||||||
|
image_tensor = image_tensor.float() / 255.0 # Normalize to [0, 1]
|
||||||
|
|
||||||
|
# Load the tokenizer
|
||||||
|
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch16") # Use a compatible tokenizer
|
||||||
|
|
||||||
|
# Tokenize the text prompts
|
||||||
|
text_inputs = tokenizer(class_labels, padding=True, return_tensors="pt")
|
||||||
|
|
||||||
|
# Make predictions
|
||||||
|
with torch.no_grad():
|
||||||
|
outputs = model(pixel_values=image_tensor, input_ids=text_inputs['input_ids'])
|
||||||
|
|
||||||
|
logits_per_image = outputs.logits_per_image # This gives the similarity scores
|
||||||
|
probs = logits_per_image.softmax(dim=1) # Convert to probabilities
|
||||||
|
|
||||||
|
# Get the predicted class
|
||||||
|
predicted_class_idx = probs.argmax().item()
|
||||||
|
predicted_class = class_labels[predicted_class_idx]
|
||||||
|
|
||||||
|
# Print the predicted class and probabilities
|
||||||
|
print("Predicted class:", predicted_class)
|
||||||
|
print("Probabilities:", probs.detach().numpy())
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
+1
@@ -0,0 +1 @@
|
|||||||
|
../mime/cli.js
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../mkdirp/bin/cmd.js
|
||||||
+1008
File diff suppressed because it is too large
Load Diff
+243
@@ -0,0 +1,243 @@
|
|||||||
|
1.3.8 / 2022-02-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.34
|
||||||
|
- deps: mime-db@~1.51.0
|
||||||
|
* deps: negotiator@0.6.3
|
||||||
|
|
||||||
|
1.3.7 / 2019-04-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.6.2
|
||||||
|
- Fix sorting charset, encoding, and language with extra parameters
|
||||||
|
|
||||||
|
1.3.6 / 2019-04-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.24
|
||||||
|
- deps: mime-db@~1.40.0
|
||||||
|
|
||||||
|
1.3.5 / 2018-02-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.18
|
||||||
|
- deps: mime-db@~1.33.0
|
||||||
|
|
||||||
|
1.3.4 / 2017-08-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.16
|
||||||
|
- deps: mime-db@~1.29.0
|
||||||
|
|
||||||
|
1.3.3 / 2016-05-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.11
|
||||||
|
- deps: mime-db@~1.23.0
|
||||||
|
* deps: negotiator@0.6.1
|
||||||
|
- perf: improve `Accept` parsing speed
|
||||||
|
- perf: improve `Accept-Charset` parsing speed
|
||||||
|
- perf: improve `Accept-Encoding` parsing speed
|
||||||
|
- perf: improve `Accept-Language` parsing speed
|
||||||
|
|
||||||
|
1.3.2 / 2016-03-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.10
|
||||||
|
- Fix extension of `application/dash+xml`
|
||||||
|
- Update primary extension for `audio/mp4`
|
||||||
|
- deps: mime-db@~1.22.0
|
||||||
|
|
||||||
|
1.3.1 / 2016-01-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.9
|
||||||
|
- deps: mime-db@~1.21.0
|
||||||
|
|
||||||
|
1.3.0 / 2015-09-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.7
|
||||||
|
- deps: mime-db@~1.19.0
|
||||||
|
* deps: negotiator@0.6.0
|
||||||
|
- Fix including type extensions in parameters in `Accept` parsing
|
||||||
|
- Fix parsing `Accept` parameters with quoted equals
|
||||||
|
- Fix parsing `Accept` parameters with quoted semicolons
|
||||||
|
- Lazy-load modules from main entry point
|
||||||
|
- perf: delay type concatenation until needed
|
||||||
|
- perf: enable strict mode
|
||||||
|
- perf: hoist regular expressions
|
||||||
|
- perf: remove closures getting spec properties
|
||||||
|
- perf: remove a closure from media type parsing
|
||||||
|
- perf: remove property delete from media type parsing
|
||||||
|
|
||||||
|
1.2.13 / 2015-09-06
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.6
|
||||||
|
- deps: mime-db@~1.18.0
|
||||||
|
|
||||||
|
1.2.12 / 2015-07-30
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.4
|
||||||
|
- deps: mime-db@~1.16.0
|
||||||
|
|
||||||
|
1.2.11 / 2015-07-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.3
|
||||||
|
- deps: mime-db@~1.15.0
|
||||||
|
|
||||||
|
1.2.10 / 2015-07-01
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.2
|
||||||
|
- deps: mime-db@~1.14.0
|
||||||
|
|
||||||
|
1.2.9 / 2015-06-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.1
|
||||||
|
- perf: fix deopt during mapping
|
||||||
|
|
||||||
|
1.2.8 / 2015-06-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.0
|
||||||
|
- deps: mime-db@~1.13.0
|
||||||
|
* perf: avoid argument reassignment & argument slice
|
||||||
|
* perf: avoid negotiator recursive construction
|
||||||
|
* perf: enable strict mode
|
||||||
|
* perf: remove unnecessary bitwise operator
|
||||||
|
|
||||||
|
1.2.7 / 2015-05-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.5.3
|
||||||
|
- Fix media type parameter matching to be case-insensitive
|
||||||
|
|
||||||
|
1.2.6 / 2015-05-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.11
|
||||||
|
- deps: mime-db@~1.9.1
|
||||||
|
* deps: negotiator@0.5.2
|
||||||
|
- Fix comparing media types with quoted values
|
||||||
|
- Fix splitting media types with quoted commas
|
||||||
|
|
||||||
|
1.2.5 / 2015-03-13
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.10
|
||||||
|
- deps: mime-db@~1.8.0
|
||||||
|
|
||||||
|
1.2.4 / 2015-02-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Support Node.js 0.6
|
||||||
|
* deps: mime-types@~2.0.9
|
||||||
|
- deps: mime-db@~1.7.0
|
||||||
|
* deps: negotiator@0.5.1
|
||||||
|
- Fix preference sorting to be stable for long acceptable lists
|
||||||
|
|
||||||
|
1.2.3 / 2015-01-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.8
|
||||||
|
- deps: mime-db@~1.6.0
|
||||||
|
|
||||||
|
1.2.2 / 2014-12-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.7
|
||||||
|
- deps: mime-db@~1.5.0
|
||||||
|
|
||||||
|
1.2.1 / 2014-12-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.5
|
||||||
|
- deps: mime-db@~1.3.1
|
||||||
|
|
||||||
|
1.2.0 / 2014-12-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.5.0
|
||||||
|
- Fix list return order when large accepted list
|
||||||
|
- Fix missing identity encoding when q=0 exists
|
||||||
|
- Remove dynamic building of Negotiator class
|
||||||
|
|
||||||
|
1.1.4 / 2014-12-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.4
|
||||||
|
- deps: mime-db@~1.3.0
|
||||||
|
|
||||||
|
1.1.3 / 2014-11-09
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.3
|
||||||
|
- deps: mime-db@~1.2.0
|
||||||
|
|
||||||
|
1.1.2 / 2014-10-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.9
|
||||||
|
- Fix error when media type has invalid parameter
|
||||||
|
|
||||||
|
1.1.1 / 2014-09-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.2
|
||||||
|
- deps: mime-db@~1.1.0
|
||||||
|
* deps: negotiator@0.4.8
|
||||||
|
- Fix all negotiations to be case-insensitive
|
||||||
|
- Stable sort preferences of same quality according to client order
|
||||||
|
|
||||||
|
1.1.0 / 2014-09-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* update `mime-types`
|
||||||
|
|
||||||
|
1.0.7 / 2014-07-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix wrong type returned from `type` when match after unknown extension
|
||||||
|
|
||||||
|
1.0.6 / 2014-06-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.7
|
||||||
|
|
||||||
|
1.0.5 / 2014-06-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix crash when unknown extension given
|
||||||
|
|
||||||
|
1.0.4 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* use `mime-types`
|
||||||
|
|
||||||
|
1.0.3 / 2014-06-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.6
|
||||||
|
- Order by specificity when quality is the same
|
||||||
|
|
||||||
|
1.0.2 / 2014-05-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix interpretation when header not in request
|
||||||
|
* deps: pin negotiator@0.4.5
|
||||||
|
|
||||||
|
1.0.1 / 2014-01-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Identity encoding isn't always acceptable
|
||||||
|
* deps: negotiator@~0.4.0
|
||||||
|
|
||||||
|
1.0.0 / 2013-12-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Genesis
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||||
|
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.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.
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
# accepts
|
||||||
|
|
||||||
|
[![NPM Version][npm-version-image]][npm-url]
|
||||||
|
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||||
|
[![Node.js Version][node-version-image]][node-version-url]
|
||||||
|
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
||||||
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
|
||||||
|
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
|
||||||
|
|
||||||
|
In addition to negotiator, it allows:
|
||||||
|
|
||||||
|
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
|
||||||
|
as well as `('text/html', 'application/json')`.
|
||||||
|
- Allows type shorthands such as `json`.
|
||||||
|
- Returns `false` when no types match
|
||||||
|
- Treats non-existent headers as `*`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||||
|
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||||
|
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install accepts
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```js
|
||||||
|
var accepts = require('accepts')
|
||||||
|
```
|
||||||
|
|
||||||
|
### accepts(req)
|
||||||
|
|
||||||
|
Create a new `Accepts` object for the given `req`.
|
||||||
|
|
||||||
|
#### .charset(charsets)
|
||||||
|
|
||||||
|
Return the first accepted charset. If nothing in `charsets` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .charsets()
|
||||||
|
|
||||||
|
Return the charsets that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .encoding(encodings)
|
||||||
|
|
||||||
|
Return the first accepted encoding. If nothing in `encodings` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .encodings()
|
||||||
|
|
||||||
|
Return the encodings that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .language(languages)
|
||||||
|
|
||||||
|
Return the first accepted language. If nothing in `languages` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .languages()
|
||||||
|
|
||||||
|
Return the languages that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .type(types)
|
||||||
|
|
||||||
|
Return the first accepted type (and it is returned as the same text as what
|
||||||
|
appears in the `types` array). If nothing in `types` is accepted, then `false`
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
The `types` array can contain full MIME types or file extensions. Any value
|
||||||
|
that is not a full MIME types is passed to `require('mime-types').lookup`.
|
||||||
|
|
||||||
|
#### .types()
|
||||||
|
|
||||||
|
Return the types that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple type negotiation
|
||||||
|
|
||||||
|
This simple example shows how to use `accepts` to return a different typed
|
||||||
|
respond body based on what the client wants to accept. The server lists it's
|
||||||
|
preferences in order and will get back the best match between the client and
|
||||||
|
server.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var accepts = require('accepts')
|
||||||
|
var http = require('http')
|
||||||
|
|
||||||
|
function app (req, res) {
|
||||||
|
var accept = accepts(req)
|
||||||
|
|
||||||
|
// the order of this list is significant; should be server preferred order
|
||||||
|
switch (accept.type(['json', 'html'])) {
|
||||||
|
case 'json':
|
||||||
|
res.setHeader('Content-Type', 'application/json')
|
||||||
|
res.write('{"hello":"world!"}')
|
||||||
|
break
|
||||||
|
case 'html':
|
||||||
|
res.setHeader('Content-Type', 'text/html')
|
||||||
|
res.write('<b>hello, world!</b>')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// the fallback is text/plain, so no need to specify it above
|
||||||
|
res.setHeader('Content-Type', 'text/plain')
|
||||||
|
res.write('hello, world!')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
http.createServer(app).listen(3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can test this out with the cURL program:
|
||||||
|
```sh
|
||||||
|
curl -I -H'Accept: text/html' http://localhost:3000/
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
|
||||||
|
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
|
||||||
|
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
|
||||||
|
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
|
||||||
|
[node-version-image]: https://badgen.net/npm/node/accepts
|
||||||
|
[node-version-url]: https://nodejs.org/en/download
|
||||||
|
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
|
||||||
|
[npm-url]: https://npmjs.org/package/accepts
|
||||||
|
[npm-version-image]: https://badgen.net/npm/v/accepts
|
||||||
+238
@@ -0,0 +1,238 @@
|
|||||||
|
/*!
|
||||||
|
* accepts
|
||||||
|
* Copyright(c) 2014 Jonathan Ong
|
||||||
|
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Negotiator = require('negotiator')
|
||||||
|
var mime = require('mime-types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = Accepts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Accepts object for the given req.
|
||||||
|
*
|
||||||
|
* @param {object} req
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Accepts (req) {
|
||||||
|
if (!(this instanceof Accepts)) {
|
||||||
|
return new Accepts(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headers = req.headers
|
||||||
|
this.negotiator = new Negotiator(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given `type(s)` is acceptable, returning
|
||||||
|
* the best match when true, otherwise `undefined`, in which
|
||||||
|
* case you should respond with 406 "Not Acceptable".
|
||||||
|
*
|
||||||
|
* The `type` value may be a single mime type string
|
||||||
|
* such as "application/json", the extension name
|
||||||
|
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
|
||||||
|
* or array is given the _best_ match, if any is returned.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* // Accept: text/html
|
||||||
|
* this.types('html');
|
||||||
|
* // => "html"
|
||||||
|
*
|
||||||
|
* // Accept: text/*, application/json
|
||||||
|
* this.types('html');
|
||||||
|
* // => "html"
|
||||||
|
* this.types('text/html');
|
||||||
|
* // => "text/html"
|
||||||
|
* this.types('json', 'text');
|
||||||
|
* // => "json"
|
||||||
|
* this.types('application/json');
|
||||||
|
* // => "application/json"
|
||||||
|
*
|
||||||
|
* // Accept: text/*, application/json
|
||||||
|
* this.types('image/png');
|
||||||
|
* this.types('png');
|
||||||
|
* // => undefined
|
||||||
|
*
|
||||||
|
* // Accept: text/*;q=.5, application/json
|
||||||
|
* this.types(['html', 'json']);
|
||||||
|
* this.types('html', 'json');
|
||||||
|
* // => "json"
|
||||||
|
*
|
||||||
|
* @param {String|Array} types...
|
||||||
|
* @return {String|Array|Boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.type =
|
||||||
|
Accepts.prototype.types = function (types_) {
|
||||||
|
var types = types_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (types && !Array.isArray(types)) {
|
||||||
|
types = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < types.length; i++) {
|
||||||
|
types[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no types, return all requested types
|
||||||
|
if (!types || types.length === 0) {
|
||||||
|
return this.negotiator.mediaTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// no accept header, return first given type
|
||||||
|
if (!this.headers.accept) {
|
||||||
|
return types[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimes = types.map(extToMime)
|
||||||
|
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
|
||||||
|
var first = accepts[0]
|
||||||
|
|
||||||
|
return first
|
||||||
|
? types[mimes.indexOf(first)]
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted encodings or best fit based on `encodings`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Encoding: gzip, deflate`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['gzip', 'deflate']
|
||||||
|
*
|
||||||
|
* @param {String|Array} encodings...
|
||||||
|
* @return {String|Array}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.encoding =
|
||||||
|
Accepts.prototype.encodings = function (encodings_) {
|
||||||
|
var encodings = encodings_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (encodings && !Array.isArray(encodings)) {
|
||||||
|
encodings = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < encodings.length; i++) {
|
||||||
|
encodings[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no encodings, return all requested encodings
|
||||||
|
if (!encodings || encodings.length === 0) {
|
||||||
|
return this.negotiator.encodings()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.encodings(encodings)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted charsets or best fit based on `charsets`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||||
|
*
|
||||||
|
* @param {String|Array} charsets...
|
||||||
|
* @return {String|Array}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.charset =
|
||||||
|
Accepts.prototype.charsets = function (charsets_) {
|
||||||
|
var charsets = charsets_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (charsets && !Array.isArray(charsets)) {
|
||||||
|
charsets = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < charsets.length; i++) {
|
||||||
|
charsets[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no charsets, return all requested charsets
|
||||||
|
if (!charsets || charsets.length === 0) {
|
||||||
|
return this.negotiator.charsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.charsets(charsets)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted languages or best fit based on `langs`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['es', 'pt', 'en']
|
||||||
|
*
|
||||||
|
* @param {String|Array} langs...
|
||||||
|
* @return {Array|String}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.lang =
|
||||||
|
Accepts.prototype.langs =
|
||||||
|
Accepts.prototype.language =
|
||||||
|
Accepts.prototype.languages = function (languages_) {
|
||||||
|
var languages = languages_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (languages && !Array.isArray(languages)) {
|
||||||
|
languages = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < languages.length; i++) {
|
||||||
|
languages[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no languages, return all requested languages
|
||||||
|
if (!languages || languages.length === 0) {
|
||||||
|
return this.negotiator.languages()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.languages(languages)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert extnames to mime.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function extToMime (type) {
|
||||||
|
return type.indexOf('/') === -1
|
||||||
|
? mime.lookup(type)
|
||||||
|
: type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if mime is valid.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function validMime (type) {
|
||||||
|
return typeof type === 'string'
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "accepts",
|
||||||
|
"description": "Higher-level content negotiation",
|
||||||
|
"version": "1.3.8",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "jshttp/accepts",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"deep-equal": "1.0.1",
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-standard": "14.1.1",
|
||||||
|
"eslint-plugin-import": "2.25.4",
|
||||||
|
"eslint-plugin-markdown": "2.2.1",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-promise": "4.3.1",
|
||||||
|
"eslint-plugin-standard": "4.1.0",
|
||||||
|
"mocha": "9.2.0",
|
||||||
|
"nyc": "15.1.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --reporter spec --check-leaks --bail test/",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"content",
|
||||||
|
"negotiation",
|
||||||
|
"accept",
|
||||||
|
"accepts"
|
||||||
|
]
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
+21
@@ -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.
|
||||||
+44
@@ -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
|
||||||
+12
@@ -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
|
||||||
+19
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.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.
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
# Array Flatten
|
||||||
|
|
||||||
|
[![NPM version][npm-image]][npm-url]
|
||||||
|
[![NPM downloads][downloads-image]][downloads-url]
|
||||||
|
[![Build status][travis-image]][travis-url]
|
||||||
|
[![Test coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
> Flatten an array of nested arrays into a single flat array. Accepts an optional depth.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install array-flatten --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var flatten = require('array-flatten')
|
||||||
|
|
||||||
|
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9])
|
||||||
|
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
|
||||||
|
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9], 2)
|
||||||
|
//=> [1, 2, 3, [4, [5], 6], 7, 8, 9]
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
flatten(arguments) //=> [1, 2, 3]
|
||||||
|
})(1, [2, 3])
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/array-flatten.svg?style=flat
|
||||||
|
[npm-url]: https://npmjs.org/package/array-flatten
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/array-flatten.svg?style=flat
|
||||||
|
[downloads-url]: https://npmjs.org/package/array-flatten
|
||||||
|
[travis-image]: https://img.shields.io/travis/blakeembrey/array-flatten.svg?style=flat
|
||||||
|
[travis-url]: https://travis-ci.org/blakeembrey/array-flatten
|
||||||
|
[coveralls-image]: https://img.shields.io/coveralls/blakeembrey/array-flatten.svg?style=flat
|
||||||
|
[coveralls-url]: https://coveralls.io/r/blakeembrey/array-flatten?branch=master
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `arrayFlatten`.
|
||||||
|
*/
|
||||||
|
module.exports = arrayFlatten
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive flatten function with depth.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Array} result
|
||||||
|
* @param {Number} depth
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function flattenWithDepth (array, result, depth) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var value = array[i]
|
||||||
|
|
||||||
|
if (depth > 0 && Array.isArray(value)) {
|
||||||
|
flattenWithDepth(value, result, depth - 1)
|
||||||
|
} else {
|
||||||
|
result.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive flatten function. Omitting depth is slightly faster.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Array} result
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function flattenForever (array, result) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var value = array[i]
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
flattenForever(value, result)
|
||||||
|
} else {
|
||||||
|
result.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten an array, with the ability to define a depth.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Number} depth
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function arrayFlatten (array, depth) {
|
||||||
|
if (depth == null) {
|
||||||
|
return flattenForever(array, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenWithDepth(array, [], depth)
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "array-flatten",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"description": "Flatten an array of nested arrays into a single flat array",
|
||||||
|
"main": "array-flatten.js",
|
||||||
|
"files": [
|
||||||
|
"array-flatten.js",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "istanbul cover _mocha -- -R spec"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/blakeembrey/array-flatten.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"array",
|
||||||
|
"flatten",
|
||||||
|
"arguments",
|
||||||
|
"depth"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Blake Embrey",
|
||||||
|
"email": "hello@blakeembrey.com",
|
||||||
|
"url": "http://blakeembrey.me"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/blakeembrey/array-flatten/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/blakeembrey/array-flatten",
|
||||||
|
"devDependencies": {
|
||||||
|
"istanbul": "^0.3.13",
|
||||||
|
"mocha": "^2.2.4",
|
||||||
|
"pre-commit": "^1.0.7",
|
||||||
|
"standard": "^3.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
+672
@@ -0,0 +1,672 @@
|
|||||||
|
1.20.3 / 2024-09-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: qs@6.13.0
|
||||||
|
* add `depth` option to customize the depth level in the parser
|
||||||
|
* IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`)
|
||||||
|
|
||||||
|
1.20.2 / 2023-02-21
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix strict json error message on Node.js 19+
|
||||||
|
* deps: content-type@~1.0.5
|
||||||
|
- perf: skip value escaping when unnecessary
|
||||||
|
* deps: raw-body@2.5.2
|
||||||
|
|
||||||
|
1.20.1 / 2022-10-06
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: qs@6.11.0
|
||||||
|
* perf: remove unnecessary object clone
|
||||||
|
|
||||||
|
1.20.0 / 2022-04-02
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix error message for json parse whitespace in `strict`
|
||||||
|
* Fix internal error when inflated body exceeds limit
|
||||||
|
* Prevent loss of async hooks context
|
||||||
|
* Prevent hanging when request already read
|
||||||
|
* deps: depd@2.0.0
|
||||||
|
- Replace internal `eval` usage with `Function` constructor
|
||||||
|
- Use instance methods on `process` to check for listeners
|
||||||
|
* deps: http-errors@2.0.0
|
||||||
|
- deps: depd@2.0.0
|
||||||
|
- deps: statuses@2.0.1
|
||||||
|
* deps: on-finished@2.4.1
|
||||||
|
* deps: qs@6.10.3
|
||||||
|
* deps: raw-body@2.5.1
|
||||||
|
- deps: http-errors@2.0.0
|
||||||
|
|
||||||
|
1.19.2 / 2022-02-15
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@3.1.2
|
||||||
|
* deps: qs@6.9.7
|
||||||
|
* Fix handling of `__proto__` keys
|
||||||
|
* deps: raw-body@2.4.3
|
||||||
|
- deps: bytes@3.1.2
|
||||||
|
|
||||||
|
1.19.1 / 2021-12-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@3.1.1
|
||||||
|
* deps: http-errors@1.8.1
|
||||||
|
- deps: inherits@2.0.4
|
||||||
|
- deps: toidentifier@1.0.1
|
||||||
|
- deps: setprototypeof@1.2.0
|
||||||
|
* deps: qs@6.9.6
|
||||||
|
* deps: raw-body@2.4.2
|
||||||
|
- deps: bytes@3.1.1
|
||||||
|
- deps: http-errors@1.8.1
|
||||||
|
* deps: safe-buffer@5.2.1
|
||||||
|
* deps: type-is@~1.6.18
|
||||||
|
|
||||||
|
1.19.0 / 2019-04-25
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@3.1.0
|
||||||
|
- Add petabyte (`pb`) support
|
||||||
|
* deps: http-errors@1.7.2
|
||||||
|
- Set constructor name when possible
|
||||||
|
- deps: setprototypeof@1.1.1
|
||||||
|
- deps: statuses@'>= 1.5.0 < 2'
|
||||||
|
* deps: iconv-lite@0.4.24
|
||||||
|
- Added encoding MIK
|
||||||
|
* deps: qs@6.7.0
|
||||||
|
- Fix parsing array brackets after index
|
||||||
|
* deps: raw-body@2.4.0
|
||||||
|
- deps: bytes@3.1.0
|
||||||
|
- deps: http-errors@1.7.2
|
||||||
|
- deps: iconv-lite@0.4.24
|
||||||
|
* deps: type-is@~1.6.17
|
||||||
|
- deps: mime-types@~2.1.24
|
||||||
|
- perf: prevent internal `throw` on invalid type
|
||||||
|
|
||||||
|
1.18.3 / 2018-05-14
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix stack trace for strict json parse error
|
||||||
|
* deps: depd@~1.1.2
|
||||||
|
- perf: remove argument reassignment
|
||||||
|
* deps: http-errors@~1.6.3
|
||||||
|
- deps: depd@~1.1.2
|
||||||
|
- deps: setprototypeof@1.1.0
|
||||||
|
- deps: statuses@'>= 1.3.1 < 2'
|
||||||
|
* deps: iconv-lite@0.4.23
|
||||||
|
- Fix loading encoding with year appended
|
||||||
|
- Fix deprecation warnings on Node.js 10+
|
||||||
|
* deps: qs@6.5.2
|
||||||
|
* deps: raw-body@2.3.3
|
||||||
|
- deps: http-errors@1.6.3
|
||||||
|
- deps: iconv-lite@0.4.23
|
||||||
|
* deps: type-is@~1.6.16
|
||||||
|
- deps: mime-types@~2.1.18
|
||||||
|
|
||||||
|
1.18.2 / 2017-09-22
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@2.6.9
|
||||||
|
* perf: remove argument reassignment
|
||||||
|
|
||||||
|
1.18.1 / 2017-09-12
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: content-type@~1.0.4
|
||||||
|
- perf: remove argument reassignment
|
||||||
|
- perf: skip parameter parsing when no parameters
|
||||||
|
* deps: iconv-lite@0.4.19
|
||||||
|
- Fix ISO-8859-1 regression
|
||||||
|
- Update Windows-1255
|
||||||
|
* deps: qs@6.5.1
|
||||||
|
- Fix parsing & compacting very deep objects
|
||||||
|
* deps: raw-body@2.3.2
|
||||||
|
- deps: iconv-lite@0.4.19
|
||||||
|
|
||||||
|
1.18.0 / 2017-09-08
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix JSON strict violation error to match native parse error
|
||||||
|
* Include the `body` property on verify errors
|
||||||
|
* Include the `type` property on all generated errors
|
||||||
|
* Use `http-errors` to set status code on errors
|
||||||
|
* deps: bytes@3.0.0
|
||||||
|
* deps: debug@2.6.8
|
||||||
|
* deps: depd@~1.1.1
|
||||||
|
- Remove unnecessary `Buffer` loading
|
||||||
|
* deps: http-errors@~1.6.2
|
||||||
|
- deps: depd@1.1.1
|
||||||
|
* deps: iconv-lite@0.4.18
|
||||||
|
- Add support for React Native
|
||||||
|
- Add a warning if not loaded as utf-8
|
||||||
|
- Fix CESU-8 decoding in Node.js 8
|
||||||
|
- Improve speed of ISO-8859-1 encoding
|
||||||
|
* deps: qs@6.5.0
|
||||||
|
* deps: raw-body@2.3.1
|
||||||
|
- Use `http-errors` for standard emitted errors
|
||||||
|
- deps: bytes@3.0.0
|
||||||
|
- deps: iconv-lite@0.4.18
|
||||||
|
- perf: skip buffer decoding on overage chunk
|
||||||
|
* perf: prevent internal `throw` when missing charset
|
||||||
|
|
||||||
|
1.17.2 / 2017-05-17
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@2.6.7
|
||||||
|
- Fix `DEBUG_MAX_ARRAY_LENGTH`
|
||||||
|
- deps: ms@2.0.0
|
||||||
|
* deps: type-is@~1.6.15
|
||||||
|
- deps: mime-types@~2.1.15
|
||||||
|
|
||||||
|
1.17.1 / 2017-03-06
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: qs@6.4.0
|
||||||
|
- Fix regression parsing keys starting with `[`
|
||||||
|
|
||||||
|
1.17.0 / 2017-03-01
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: http-errors@~1.6.1
|
||||||
|
- Make `message` property enumerable for `HttpError`s
|
||||||
|
- deps: setprototypeof@1.0.3
|
||||||
|
* deps: qs@6.3.1
|
||||||
|
- Fix compacting nested arrays
|
||||||
|
|
||||||
|
1.16.1 / 2017-02-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@2.6.1
|
||||||
|
- Fix deprecation messages in WebStorm and other editors
|
||||||
|
- Undeprecate `DEBUG_FD` set to `1` or `2`
|
||||||
|
|
||||||
|
1.16.0 / 2017-01-17
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@2.6.0
|
||||||
|
- Allow colors in workers
|
||||||
|
- Deprecated `DEBUG_FD` environment variable
|
||||||
|
- Fix error when running under React Native
|
||||||
|
- Use same color for same namespace
|
||||||
|
- deps: ms@0.7.2
|
||||||
|
* deps: http-errors@~1.5.1
|
||||||
|
- deps: inherits@2.0.3
|
||||||
|
- deps: setprototypeof@1.0.2
|
||||||
|
- deps: statuses@'>= 1.3.1 < 2'
|
||||||
|
* deps: iconv-lite@0.4.15
|
||||||
|
- Added encoding MS-31J
|
||||||
|
- Added encoding MS-932
|
||||||
|
- Added encoding MS-936
|
||||||
|
- Added encoding MS-949
|
||||||
|
- Added encoding MS-950
|
||||||
|
- Fix GBK/GB18030 handling of Euro character
|
||||||
|
* deps: qs@6.2.1
|
||||||
|
- Fix array parsing from skipping empty values
|
||||||
|
* deps: raw-body@~2.2.0
|
||||||
|
- deps: iconv-lite@0.4.15
|
||||||
|
* deps: type-is@~1.6.14
|
||||||
|
- deps: mime-types@~2.1.13
|
||||||
|
|
||||||
|
1.15.2 / 2016-06-19
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@2.4.0
|
||||||
|
* deps: content-type@~1.0.2
|
||||||
|
- perf: enable strict mode
|
||||||
|
* deps: http-errors@~1.5.0
|
||||||
|
- Use `setprototypeof` module to replace `__proto__` setting
|
||||||
|
- deps: statuses@'>= 1.3.0 < 2'
|
||||||
|
- perf: enable strict mode
|
||||||
|
* deps: qs@6.2.0
|
||||||
|
* deps: raw-body@~2.1.7
|
||||||
|
- deps: bytes@2.4.0
|
||||||
|
- perf: remove double-cleanup on happy path
|
||||||
|
* deps: type-is@~1.6.13
|
||||||
|
- deps: mime-types@~2.1.11
|
||||||
|
|
||||||
|
1.15.1 / 2016-05-05
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@2.3.0
|
||||||
|
- Drop partial bytes on all parsed units
|
||||||
|
- Fix parsing byte string that looks like hex
|
||||||
|
* deps: raw-body@~2.1.6
|
||||||
|
- deps: bytes@2.3.0
|
||||||
|
* deps: type-is@~1.6.12
|
||||||
|
- deps: mime-types@~2.1.10
|
||||||
|
|
||||||
|
1.15.0 / 2016-02-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: http-errors@~1.4.0
|
||||||
|
- Add `HttpError` export, for `err instanceof createError.HttpError`
|
||||||
|
- deps: inherits@2.0.1
|
||||||
|
- deps: statuses@'>= 1.2.1 < 2'
|
||||||
|
* deps: qs@6.1.0
|
||||||
|
* deps: type-is@~1.6.11
|
||||||
|
- deps: mime-types@~2.1.9
|
||||||
|
|
||||||
|
1.14.2 / 2015-12-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: bytes@2.2.0
|
||||||
|
* deps: iconv-lite@0.4.13
|
||||||
|
* deps: qs@5.2.0
|
||||||
|
* deps: raw-body@~2.1.5
|
||||||
|
- deps: bytes@2.2.0
|
||||||
|
- deps: iconv-lite@0.4.13
|
||||||
|
* deps: type-is@~1.6.10
|
||||||
|
- deps: mime-types@~2.1.8
|
||||||
|
|
||||||
|
1.14.1 / 2015-09-27
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix issue where invalid charset results in 400 when `verify` used
|
||||||
|
* deps: iconv-lite@0.4.12
|
||||||
|
- Fix CESU-8 decoding in Node.js 4.x
|
||||||
|
* deps: raw-body@~2.1.4
|
||||||
|
- Fix masking critical errors from `iconv-lite`
|
||||||
|
- deps: iconv-lite@0.4.12
|
||||||
|
* deps: type-is@~1.6.9
|
||||||
|
- deps: mime-types@~2.1.7
|
||||||
|
|
||||||
|
1.14.0 / 2015-09-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Fix JSON strict parse error to match syntax errors
|
||||||
|
* Provide static `require` analysis in `urlencoded` parser
|
||||||
|
* deps: depd@~1.1.0
|
||||||
|
- Support web browser loading
|
||||||
|
* deps: qs@5.1.0
|
||||||
|
* deps: raw-body@~2.1.3
|
||||||
|
- Fix sync callback when attaching data listener causes sync read
|
||||||
|
* deps: type-is@~1.6.8
|
||||||
|
- Fix type error when given invalid type to match against
|
||||||
|
- deps: mime-types@~2.1.6
|
||||||
|
|
||||||
|
1.13.3 / 2015-07-31
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: type-is@~1.6.6
|
||||||
|
- deps: mime-types@~2.1.4
|
||||||
|
|
||||||
|
1.13.2 / 2015-07-05
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: iconv-lite@0.4.11
|
||||||
|
* deps: qs@4.0.0
|
||||||
|
- Fix dropping parameters like `hasOwnProperty`
|
||||||
|
- Fix user-visible incompatibilities from 3.1.0
|
||||||
|
- Fix various parsing edge cases
|
||||||
|
* deps: raw-body@~2.1.2
|
||||||
|
- Fix error stack traces to skip `makeError`
|
||||||
|
- deps: iconv-lite@0.4.11
|
||||||
|
* deps: type-is@~1.6.4
|
||||||
|
- deps: mime-types@~2.1.2
|
||||||
|
- perf: enable strict mode
|
||||||
|
- perf: remove argument reassignment
|
||||||
|
|
||||||
|
1.13.1 / 2015-06-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: qs@2.4.2
|
||||||
|
- Downgraded from 3.1.0 because of user-visible incompatibilities
|
||||||
|
|
||||||
|
1.13.0 / 2015-06-14
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Add `statusCode` property on `Error`s, in addition to `status`
|
||||||
|
* Change `type` default to `application/json` for JSON parser
|
||||||
|
* Change `type` default to `application/x-www-form-urlencoded` for urlencoded parser
|
||||||
|
* Provide static `require` analysis
|
||||||
|
* Use the `http-errors` module to generate errors
|
||||||
|
* deps: bytes@2.1.0
|
||||||
|
- Slight optimizations
|
||||||
|
* deps: iconv-lite@0.4.10
|
||||||
|
- The encoding UTF-16 without BOM now defaults to UTF-16LE when detection fails
|
||||||
|
- Leading BOM is now removed when decoding
|
||||||
|
* deps: on-finished@~2.3.0
|
||||||
|
- Add defined behavior for HTTP `CONNECT` requests
|
||||||
|
- Add defined behavior for HTTP `Upgrade` requests
|
||||||
|
- deps: ee-first@1.1.1
|
||||||
|
* deps: qs@3.1.0
|
||||||
|
- Fix dropping parameters like `hasOwnProperty`
|
||||||
|
- Fix various parsing edge cases
|
||||||
|
- Parsed object now has `null` prototype
|
||||||
|
* deps: raw-body@~2.1.1
|
||||||
|
- Use `unpipe` module for unpiping requests
|
||||||
|
- deps: iconv-lite@0.4.10
|
||||||
|
* deps: type-is@~1.6.3
|
||||||
|
- deps: mime-types@~2.1.1
|
||||||
|
- perf: reduce try block size
|
||||||
|
- perf: remove bitwise operations
|
||||||
|
* perf: enable strict mode
|
||||||
|
* perf: remove argument reassignment
|
||||||
|
* perf: remove delete call
|
||||||
|
|
||||||
|
1.12.4 / 2015-05-10
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@~2.2.0
|
||||||
|
* deps: qs@2.4.2
|
||||||
|
- Fix allowing parameters like `constructor`
|
||||||
|
* deps: on-finished@~2.2.1
|
||||||
|
* deps: raw-body@~2.0.1
|
||||||
|
- Fix a false-positive when unpiping in Node.js 0.8
|
||||||
|
- deps: bytes@2.0.1
|
||||||
|
* deps: type-is@~1.6.2
|
||||||
|
- deps: mime-types@~2.0.11
|
||||||
|
|
||||||
|
1.12.3 / 2015-04-15
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Slight efficiency improvement when not debugging
|
||||||
|
* deps: depd@~1.0.1
|
||||||
|
* deps: iconv-lite@0.4.8
|
||||||
|
- Add encoding alias UNICODE-1-1-UTF-7
|
||||||
|
* deps: raw-body@1.3.4
|
||||||
|
- Fix hanging callback if request aborts during read
|
||||||
|
- deps: iconv-lite@0.4.8
|
||||||
|
|
||||||
|
1.12.2 / 2015-03-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: qs@2.4.1
|
||||||
|
- Fix error when parameter `hasOwnProperty` is present
|
||||||
|
|
||||||
|
1.12.1 / 2015-03-15
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: debug@~2.1.3
|
||||||
|
- Fix high intensity foreground color for bold
|
||||||
|
- deps: ms@0.7.0
|
||||||
|
* deps: type-is@~1.6.1
|
||||||
|
- deps: mime-types@~2.0.10
|
||||||
|
|
||||||
|
1.12.0 / 2015-02-13
|
||||||
|
===================
|
||||||
|
|
||||||
|
* add `debug` messages
|
||||||
|
* accept a function for the `type` option
|
||||||
|
* use `content-type` to parse `Content-Type` headers
|
||||||
|
* deps: iconv-lite@0.4.7
|
||||||
|
- Gracefully support enumerables on `Object.prototype`
|
||||||
|
* deps: raw-body@1.3.3
|
||||||
|
- deps: iconv-lite@0.4.7
|
||||||
|
* deps: type-is@~1.6.0
|
||||||
|
- fix argument reassignment
|
||||||
|
- fix false-positives in `hasBody` `Transfer-Encoding` check
|
||||||
|
- support wildcard for both type and subtype (`*/*`)
|
||||||
|
- deps: mime-types@~2.0.9
|
||||||
|
|
||||||
|
1.11.0 / 2015-01-30
|
||||||
|
===================
|
||||||
|
|
||||||
|
* make internal `extended: true` depth limit infinity
|
||||||
|
* deps: type-is@~1.5.6
|
||||||
|
- deps: mime-types@~2.0.8
|
||||||
|
|
||||||
|
1.10.2 / 2015-01-20
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: iconv-lite@0.4.6
|
||||||
|
- Fix rare aliases of single-byte encodings
|
||||||
|
* deps: raw-body@1.3.2
|
||||||
|
- deps: iconv-lite@0.4.6
|
||||||
|
|
||||||
|
1.10.1 / 2015-01-01
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: on-finished@~2.2.0
|
||||||
|
* deps: type-is@~1.5.5
|
||||||
|
- deps: mime-types@~2.0.7
|
||||||
|
|
||||||
|
1.10.0 / 2014-12-02
|
||||||
|
===================
|
||||||
|
|
||||||
|
* make internal `extended: true` array limit dynamic
|
||||||
|
|
||||||
|
1.9.3 / 2014-11-21
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: iconv-lite@0.4.5
|
||||||
|
- Fix Windows-31J and X-SJIS encoding support
|
||||||
|
* deps: qs@2.3.3
|
||||||
|
- Fix `arrayLimit` behavior
|
||||||
|
* deps: raw-body@1.3.1
|
||||||
|
- deps: iconv-lite@0.4.5
|
||||||
|
* deps: type-is@~1.5.3
|
||||||
|
- deps: mime-types@~2.0.3
|
||||||
|
|
||||||
|
1.9.2 / 2014-10-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@2.3.2
|
||||||
|
- Fix parsing of mixed objects and values
|
||||||
|
|
||||||
|
1.9.1 / 2014-10-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: on-finished@~2.1.1
|
||||||
|
- Fix handling of pipelined requests
|
||||||
|
* deps: qs@2.3.0
|
||||||
|
- Fix parsing of mixed implicit and explicit arrays
|
||||||
|
* deps: type-is@~1.5.2
|
||||||
|
- deps: mime-types@~2.0.2
|
||||||
|
|
||||||
|
1.9.0 / 2014-09-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* include the charset in "unsupported charset" error message
|
||||||
|
* include the encoding in "unsupported content encoding" error message
|
||||||
|
* deps: depd@~1.0.0
|
||||||
|
|
||||||
|
1.8.4 / 2014-09-23
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix content encoding to be case-insensitive
|
||||||
|
|
||||||
|
1.8.3 / 2014-09-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@2.2.4
|
||||||
|
- Fix issue with object keys starting with numbers truncated
|
||||||
|
|
||||||
|
1.8.2 / 2014-09-15
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: depd@0.4.5
|
||||||
|
|
||||||
|
1.8.1 / 2014-09-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: media-typer@0.3.0
|
||||||
|
* deps: type-is@~1.5.1
|
||||||
|
|
||||||
|
1.8.0 / 2014-09-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* make empty-body-handling consistent between chunked requests
|
||||||
|
- empty `json` produces `{}`
|
||||||
|
- empty `raw` produces `new Buffer(0)`
|
||||||
|
- empty `text` produces `''`
|
||||||
|
- empty `urlencoded` produces `{}`
|
||||||
|
* deps: qs@2.2.3
|
||||||
|
- Fix issue where first empty value in array is discarded
|
||||||
|
* deps: type-is@~1.5.0
|
||||||
|
- fix `hasbody` to be true for `content-length: 0`
|
||||||
|
|
||||||
|
1.7.0 / 2014-09-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `parameterLimit` option to `urlencoded` parser
|
||||||
|
* change `urlencoded` extended array limit to 100
|
||||||
|
* respond with 413 when over `parameterLimit` in `urlencoded`
|
||||||
|
|
||||||
|
1.6.7 / 2014-08-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@2.2.2
|
||||||
|
- Remove unnecessary cloning
|
||||||
|
|
||||||
|
1.6.6 / 2014-08-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@2.2.0
|
||||||
|
- Array parsing fix
|
||||||
|
- Performance improvements
|
||||||
|
|
||||||
|
1.6.5 / 2014-08-16
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: on-finished@2.1.0
|
||||||
|
|
||||||
|
1.6.4 / 2014-08-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@1.2.2
|
||||||
|
|
||||||
|
1.6.3 / 2014-08-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@1.2.1
|
||||||
|
|
||||||
|
1.6.2 / 2014-08-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@1.2.0
|
||||||
|
- Fix parsing array of objects
|
||||||
|
|
||||||
|
1.6.1 / 2014-08-06
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@1.1.0
|
||||||
|
- Accept urlencoded square brackets
|
||||||
|
- Accept empty values in implicit array notation
|
||||||
|
|
||||||
|
1.6.0 / 2014-08-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: qs@1.0.2
|
||||||
|
- Complete rewrite
|
||||||
|
- Limits array length to 20
|
||||||
|
- Limits object depth to 5
|
||||||
|
- Limits parameters to 1,000
|
||||||
|
|
||||||
|
1.5.2 / 2014-07-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: depd@0.4.4
|
||||||
|
- Work-around v8 generating empty stack traces
|
||||||
|
|
||||||
|
1.5.1 / 2014-07-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: depd@0.4.3
|
||||||
|
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||||
|
|
||||||
|
1.5.0 / 2014-07-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: depd@0.4.2
|
||||||
|
- Add `TRACE_DEPRECATION` environment variable
|
||||||
|
- Remove non-standard grey color from color output
|
||||||
|
- Support `--no-deprecation` argument
|
||||||
|
- Support `--trace-deprecation` argument
|
||||||
|
* deps: iconv-lite@0.4.4
|
||||||
|
- Added encoding UTF-7
|
||||||
|
* deps: raw-body@1.3.0
|
||||||
|
- deps: iconv-lite@0.4.4
|
||||||
|
- Added encoding UTF-7
|
||||||
|
- Fix `Cannot switch to old mode now` error on Node.js 0.10+
|
||||||
|
* deps: type-is@~1.3.2
|
||||||
|
|
||||||
|
1.4.3 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: type-is@1.3.1
|
||||||
|
- fix global variable leak
|
||||||
|
|
||||||
|
1.4.2 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: type-is@1.3.0
|
||||||
|
- improve type parsing
|
||||||
|
|
||||||
|
1.4.1 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix urlencoded extended deprecation message
|
||||||
|
|
||||||
|
1.4.0 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `text` parser
|
||||||
|
* add `raw` parser
|
||||||
|
* check accepted charset in content-type (accepts utf-8)
|
||||||
|
* check accepted encoding in content-encoding (accepts identity)
|
||||||
|
* deprecate `bodyParser()` middleware; use `.json()` and `.urlencoded()` as needed
|
||||||
|
* deprecate `urlencoded()` without provided `extended` option
|
||||||
|
* lazy-load urlencoded parsers
|
||||||
|
* parsers split into files for reduced mem usage
|
||||||
|
* support gzip and deflate bodies
|
||||||
|
- set `inflate: false` to turn off
|
||||||
|
* deps: raw-body@1.2.2
|
||||||
|
- Support all encodings from `iconv-lite`
|
||||||
|
|
||||||
|
1.3.1 / 2014-06-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: type-is@1.2.1
|
||||||
|
- Switch dependency from mime to mime-types@1.0.0
|
||||||
|
|
||||||
|
1.3.0 / 2014-05-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `extended` option to urlencoded parser
|
||||||
|
|
||||||
|
1.2.2 / 2014-05-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: raw-body@1.1.6
|
||||||
|
- assert stream encoding on node.js 0.8
|
||||||
|
- assert stream encoding on node.js < 0.10.6
|
||||||
|
- deps: bytes@1
|
||||||
|
|
||||||
|
1.2.1 / 2014-05-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* invoke `next(err)` after request fully read
|
||||||
|
- prevents hung responses and socket hang ups
|
||||||
|
|
||||||
|
1.2.0 / 2014-05-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `verify` option
|
||||||
|
* deps: type-is@1.2.0
|
||||||
|
- support suffix matching
|
||||||
|
|
||||||
|
1.1.2 / 2014-05-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* improve json parser speed
|
||||||
|
|
||||||
|
1.1.1 / 2014-05-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix repeated limit parsing with every request
|
||||||
|
|
||||||
|
1.1.0 / 2014-05-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `type` option
|
||||||
|
* deps: pin for safety and consistency
|
||||||
|
|
||||||
|
1.0.2 / 2014-04-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* use `type-is` module
|
||||||
|
|
||||||
|
1.0.1 / 2014-03-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* lower default limits to 100kb
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||||
|
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.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.
|
||||||
+476
@@ -0,0 +1,476 @@
|
|||||||
|
# body-parser
|
||||||
|
|
||||||
|
[![NPM Version][npm-version-image]][npm-url]
|
||||||
|
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||||
|
[![Build Status][ci-image]][ci-url]
|
||||||
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||||
|
[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer]
|
||||||
|
|
||||||
|
Node.js body parsing middleware.
|
||||||
|
|
||||||
|
Parse incoming request bodies in a middleware before your handlers, available
|
||||||
|
under the `req.body` property.
|
||||||
|
|
||||||
|
**Note** As `req.body`'s shape is based on user-controlled input, all
|
||||||
|
properties and values in this object are untrusted and should be validated
|
||||||
|
before trusting. For example, `req.body.foo.toString()` may fail in multiple
|
||||||
|
ways, for example the `foo` property may not be there or may not be a string,
|
||||||
|
and `toString` may not be a function and instead a string or other user input.
|
||||||
|
|
||||||
|
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).
|
||||||
|
|
||||||
|
_This does not handle multipart bodies_, due to their complex and typically
|
||||||
|
large nature. For multipart bodies, you may be interested in the following
|
||||||
|
modules:
|
||||||
|
|
||||||
|
* [busboy](https://www.npmjs.org/package/busboy#readme) and
|
||||||
|
[connect-busboy](https://www.npmjs.org/package/connect-busboy#readme)
|
||||||
|
* [multiparty](https://www.npmjs.org/package/multiparty#readme) and
|
||||||
|
[connect-multiparty](https://www.npmjs.org/package/connect-multiparty#readme)
|
||||||
|
* [formidable](https://www.npmjs.org/package/formidable#readme)
|
||||||
|
* [multer](https://www.npmjs.org/package/multer#readme)
|
||||||
|
|
||||||
|
This module provides the following parsers:
|
||||||
|
|
||||||
|
* [JSON body parser](#bodyparserjsonoptions)
|
||||||
|
* [Raw body parser](#bodyparserrawoptions)
|
||||||
|
* [Text body parser](#bodyparsertextoptions)
|
||||||
|
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
|
||||||
|
|
||||||
|
Other body parsers you might be interested in:
|
||||||
|
|
||||||
|
- [body](https://www.npmjs.org/package/body#readme)
|
||||||
|
- [co-body](https://www.npmjs.org/package/co-body#readme)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install body-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```js
|
||||||
|
var bodyParser = require('body-parser')
|
||||||
|
```
|
||||||
|
|
||||||
|
The `bodyParser` object exposes various factories to create middlewares. All
|
||||||
|
middlewares will populate the `req.body` property with the parsed body when
|
||||||
|
the `Content-Type` request header matches the `type` option, or an empty
|
||||||
|
object (`{}`) if there was no body to parse, the `Content-Type` was not matched,
|
||||||
|
or an error occurred.
|
||||||
|
|
||||||
|
The various errors returned by this module are described in the
|
||||||
|
[errors section](#errors).
|
||||||
|
|
||||||
|
### bodyParser.json([options])
|
||||||
|
|
||||||
|
Returns middleware that only parses `json` and only looks at requests where
|
||||||
|
the `Content-Type` header matches the `type` option. This parser accepts any
|
||||||
|
Unicode encoding of the body and supports automatic inflation of `gzip` and
|
||||||
|
`deflate` encodings.
|
||||||
|
|
||||||
|
A new `body` object containing the parsed data is populated on the `request`
|
||||||
|
object after the middleware (i.e. `req.body`).
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
The `json` function takes an optional `options` object that may contain any of
|
||||||
|
the following keys:
|
||||||
|
|
||||||
|
##### inflate
|
||||||
|
|
||||||
|
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||||
|
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||||
|
|
||||||
|
##### limit
|
||||||
|
|
||||||
|
Controls the maximum request body size. If this is a number, then the value
|
||||||
|
specifies the number of bytes; if it is a string, the value is passed to the
|
||||||
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||||
|
to `'100kb'`.
|
||||||
|
|
||||||
|
##### reviver
|
||||||
|
|
||||||
|
The `reviver` option is passed directly to `JSON.parse` as the second
|
||||||
|
argument. You can find more information on this argument
|
||||||
|
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
|
||||||
|
|
||||||
|
##### strict
|
||||||
|
|
||||||
|
When set to `true`, will only accept arrays and objects; when `false` will
|
||||||
|
accept anything `JSON.parse` accepts. Defaults to `true`.
|
||||||
|
|
||||||
|
##### type
|
||||||
|
|
||||||
|
The `type` option is used to determine what media type the middleware will
|
||||||
|
parse. This option can be a string, array of strings, or a function. If not a
|
||||||
|
function, `type` option is passed directly to the
|
||||||
|
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
||||||
|
be an extension name (like `json`), a mime type (like `application/json`), or
|
||||||
|
a mime type with a wildcard (like `*/*` or `*/json`). If a function, the `type`
|
||||||
|
option is called as `fn(req)` and the request is parsed if it returns a truthy
|
||||||
|
value. Defaults to `application/json`.
|
||||||
|
|
||||||
|
##### verify
|
||||||
|
|
||||||
|
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||||
|
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||||
|
encoding of the request. The parsing can be aborted by throwing an error.
|
||||||
|
|
||||||
|
### bodyParser.raw([options])
|
||||||
|
|
||||||
|
Returns middleware that parses all bodies as a `Buffer` and only looks at
|
||||||
|
requests where the `Content-Type` header matches the `type` option. This
|
||||||
|
parser supports automatic inflation of `gzip` and `deflate` encodings.
|
||||||
|
|
||||||
|
A new `body` object containing the parsed data is populated on the `request`
|
||||||
|
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
|
||||||
|
of the body.
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
The `raw` function takes an optional `options` object that may contain any of
|
||||||
|
the following keys:
|
||||||
|
|
||||||
|
##### inflate
|
||||||
|
|
||||||
|
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||||
|
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||||
|
|
||||||
|
##### limit
|
||||||
|
|
||||||
|
Controls the maximum request body size. If this is a number, then the value
|
||||||
|
specifies the number of bytes; if it is a string, the value is passed to the
|
||||||
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||||
|
to `'100kb'`.
|
||||||
|
|
||||||
|
##### type
|
||||||
|
|
||||||
|
The `type` option is used to determine what media type the middleware will
|
||||||
|
parse. This option can be a string, array of strings, or a function.
|
||||||
|
If not a function, `type` option is passed directly to the
|
||||||
|
[type-is](https://www.npmjs.org/package/type-is#readme) library and this
|
||||||
|
can be an extension name (like `bin`), a mime type (like
|
||||||
|
`application/octet-stream`), or a mime type with a wildcard (like `*/*` or
|
||||||
|
`application/*`). If a function, the `type` option is called as `fn(req)`
|
||||||
|
and the request is parsed if it returns a truthy value. Defaults to
|
||||||
|
`application/octet-stream`.
|
||||||
|
|
||||||
|
##### verify
|
||||||
|
|
||||||
|
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||||
|
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||||
|
encoding of the request. The parsing can be aborted by throwing an error.
|
||||||
|
|
||||||
|
### bodyParser.text([options])
|
||||||
|
|
||||||
|
Returns middleware that parses all bodies as a string and only looks at
|
||||||
|
requests where the `Content-Type` header matches the `type` option. This
|
||||||
|
parser supports automatic inflation of `gzip` and `deflate` encodings.
|
||||||
|
|
||||||
|
A new `body` string containing the parsed data is populated on the `request`
|
||||||
|
object after the middleware (i.e. `req.body`). This will be a string of the
|
||||||
|
body.
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
The `text` function takes an optional `options` object that may contain any of
|
||||||
|
the following keys:
|
||||||
|
|
||||||
|
##### defaultCharset
|
||||||
|
|
||||||
|
Specify the default character set for the text content if the charset is not
|
||||||
|
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
|
||||||
|
|
||||||
|
##### inflate
|
||||||
|
|
||||||
|
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||||
|
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||||
|
|
||||||
|
##### limit
|
||||||
|
|
||||||
|
Controls the maximum request body size. If this is a number, then the value
|
||||||
|
specifies the number of bytes; if it is a string, the value is passed to the
|
||||||
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||||
|
to `'100kb'`.
|
||||||
|
|
||||||
|
##### type
|
||||||
|
|
||||||
|
The `type` option is used to determine what media type the middleware will
|
||||||
|
parse. This option can be a string, array of strings, or a function. If not
|
||||||
|
a function, `type` option is passed directly to the
|
||||||
|
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
||||||
|
be an extension name (like `txt`), a mime type (like `text/plain`), or a mime
|
||||||
|
type with a wildcard (like `*/*` or `text/*`). If a function, the `type`
|
||||||
|
option is called as `fn(req)` and the request is parsed if it returns a
|
||||||
|
truthy value. Defaults to `text/plain`.
|
||||||
|
|
||||||
|
##### verify
|
||||||
|
|
||||||
|
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||||
|
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||||
|
encoding of the request. The parsing can be aborted by throwing an error.
|
||||||
|
|
||||||
|
### bodyParser.urlencoded([options])
|
||||||
|
|
||||||
|
Returns middleware that only parses `urlencoded` bodies and only looks at
|
||||||
|
requests where the `Content-Type` header matches the `type` option. This
|
||||||
|
parser accepts only UTF-8 encoding of the body and supports automatic
|
||||||
|
inflation of `gzip` and `deflate` encodings.
|
||||||
|
|
||||||
|
A new `body` object containing the parsed data is populated on the `request`
|
||||||
|
object after the middleware (i.e. `req.body`). This object will contain
|
||||||
|
key-value pairs, where the value can be a string or array (when `extended` is
|
||||||
|
`false`), or any type (when `extended` is `true`).
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
The `urlencoded` function takes an optional `options` object that may contain
|
||||||
|
any of the following keys:
|
||||||
|
|
||||||
|
##### extended
|
||||||
|
|
||||||
|
The `extended` option allows to choose between parsing the URL-encoded data
|
||||||
|
with the `querystring` library (when `false`) or the `qs` library (when
|
||||||
|
`true`). The "extended" syntax allows for rich objects and arrays to be
|
||||||
|
encoded into the URL-encoded format, allowing for a JSON-like experience
|
||||||
|
with URL-encoded. For more information, please
|
||||||
|
[see the qs library](https://www.npmjs.org/package/qs#readme).
|
||||||
|
|
||||||
|
Defaults to `true`, but using the default has been deprecated. Please
|
||||||
|
research into the difference between `qs` and `querystring` and choose the
|
||||||
|
appropriate setting.
|
||||||
|
|
||||||
|
##### inflate
|
||||||
|
|
||||||
|
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
||||||
|
`false`, deflated bodies are rejected. Defaults to `true`.
|
||||||
|
|
||||||
|
##### limit
|
||||||
|
|
||||||
|
Controls the maximum request body size. If this is a number, then the value
|
||||||
|
specifies the number of bytes; if it is a string, the value is passed to the
|
||||||
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
||||||
|
to `'100kb'`.
|
||||||
|
|
||||||
|
##### parameterLimit
|
||||||
|
|
||||||
|
The `parameterLimit` option controls the maximum number of parameters that
|
||||||
|
are allowed in the URL-encoded data. If a request contains more parameters
|
||||||
|
than this value, a 413 will be returned to the client. Defaults to `1000`.
|
||||||
|
|
||||||
|
##### type
|
||||||
|
|
||||||
|
The `type` option is used to determine what media type the middleware will
|
||||||
|
parse. This option can be a string, array of strings, or a function. If not
|
||||||
|
a function, `type` option is passed directly to the
|
||||||
|
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
||||||
|
be an extension name (like `urlencoded`), a mime type (like
|
||||||
|
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
|
||||||
|
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
|
||||||
|
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
|
||||||
|
to `application/x-www-form-urlencoded`.
|
||||||
|
|
||||||
|
##### verify
|
||||||
|
|
||||||
|
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
||||||
|
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
||||||
|
encoding of the request. The parsing can be aborted by throwing an error.
|
||||||
|
|
||||||
|
#### depth
|
||||||
|
|
||||||
|
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
The middlewares provided by this module create errors using the
|
||||||
|
[`http-errors` module](https://www.npmjs.com/package/http-errors). The errors
|
||||||
|
will typically have a `status`/`statusCode` property that contains the suggested
|
||||||
|
HTTP response code, an `expose` property to determine if the `message` property
|
||||||
|
should be displayed to the client, a `type` property to determine the type of
|
||||||
|
error without matching against the `message`, and a `body` property containing
|
||||||
|
the read body, if available.
|
||||||
|
|
||||||
|
The following are the common errors created, though any error can come through
|
||||||
|
for various reasons.
|
||||||
|
|
||||||
|
### content encoding unsupported
|
||||||
|
|
||||||
|
This error will occur when the request had a `Content-Encoding` header that
|
||||||
|
contained an encoding but the "inflation" option was set to `false`. The
|
||||||
|
`status` property is set to `415`, the `type` property is set to
|
||||||
|
`'encoding.unsupported'`, and the `charset` property will be set to the
|
||||||
|
encoding that is unsupported.
|
||||||
|
|
||||||
|
### entity parse failed
|
||||||
|
|
||||||
|
This error will occur when the request contained an entity that could not be
|
||||||
|
parsed by the middleware. The `status` property is set to `400`, the `type`
|
||||||
|
property is set to `'entity.parse.failed'`, and the `body` property is set to
|
||||||
|
the entity value that failed parsing.
|
||||||
|
|
||||||
|
### entity verify failed
|
||||||
|
|
||||||
|
This error will occur when the request contained an entity that could not be
|
||||||
|
failed verification by the defined `verify` option. The `status` property is
|
||||||
|
set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
|
||||||
|
`body` property is set to the entity value that failed verification.
|
||||||
|
|
||||||
|
### request aborted
|
||||||
|
|
||||||
|
This error will occur when the request is aborted by the client before reading
|
||||||
|
the body has finished. The `received` property will be set to the number of
|
||||||
|
bytes received before the request was aborted and the `expected` property is
|
||||||
|
set to the number of expected bytes. The `status` property is set to `400`
|
||||||
|
and `type` property is set to `'request.aborted'`.
|
||||||
|
|
||||||
|
### request entity too large
|
||||||
|
|
||||||
|
This error will occur when the request body's size is larger than the "limit"
|
||||||
|
option. The `limit` property will be set to the byte limit and the `length`
|
||||||
|
property will be set to the request body's length. The `status` property is
|
||||||
|
set to `413` and the `type` property is set to `'entity.too.large'`.
|
||||||
|
|
||||||
|
### request size did not match content length
|
||||||
|
|
||||||
|
This error will occur when the request's length did not match the length from
|
||||||
|
the `Content-Length` header. This typically occurs when the request is malformed,
|
||||||
|
typically when the `Content-Length` header was calculated based on characters
|
||||||
|
instead of bytes. The `status` property is set to `400` and the `type` property
|
||||||
|
is set to `'request.size.invalid'`.
|
||||||
|
|
||||||
|
### stream encoding should not be set
|
||||||
|
|
||||||
|
This error will occur when something called the `req.setEncoding` method prior
|
||||||
|
to this middleware. This module operates directly on bytes only and you cannot
|
||||||
|
call `req.setEncoding` when using this module. The `status` property is set to
|
||||||
|
`500` and the `type` property is set to `'stream.encoding.set'`.
|
||||||
|
|
||||||
|
### stream is not readable
|
||||||
|
|
||||||
|
This error will occur when the request is no longer readable when this middleware
|
||||||
|
attempts to read it. This typically means something other than a middleware from
|
||||||
|
this module read the request body already and the middleware was also configured to
|
||||||
|
read the same request. The `status` property is set to `500` and the `type`
|
||||||
|
property is set to `'stream.not.readable'`.
|
||||||
|
|
||||||
|
### too many parameters
|
||||||
|
|
||||||
|
This error will occur when the content of the request exceeds the configured
|
||||||
|
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
|
||||||
|
`413` and the `type` property is set to `'parameters.too.many'`.
|
||||||
|
|
||||||
|
### unsupported charset "BOGUS"
|
||||||
|
|
||||||
|
This error will occur when the request had a charset parameter in the
|
||||||
|
`Content-Type` header, but the `iconv-lite` module does not support it OR the
|
||||||
|
parser does not support it. The charset is contained in the message as well
|
||||||
|
as in the `charset` property. The `status` property is set to `415`, the
|
||||||
|
`type` property is set to `'charset.unsupported'`, and the `charset` property
|
||||||
|
is set to the charset that is unsupported.
|
||||||
|
|
||||||
|
### unsupported content encoding "bogus"
|
||||||
|
|
||||||
|
This error will occur when the request had a `Content-Encoding` header that
|
||||||
|
contained an unsupported encoding. The encoding is contained in the message
|
||||||
|
as well as in the `encoding` property. The `status` property is set to `415`,
|
||||||
|
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
|
||||||
|
property is set to the encoding that is unsupported.
|
||||||
|
|
||||||
|
### The input exceeded the depth
|
||||||
|
|
||||||
|
This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Express/Connect top-level generic
|
||||||
|
|
||||||
|
This example demonstrates adding a generic JSON and URL-encoded parser as a
|
||||||
|
top-level middleware, which will parse the bodies of all incoming requests.
|
||||||
|
This is the simplest setup.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var express = require('express')
|
||||||
|
var bodyParser = require('body-parser')
|
||||||
|
|
||||||
|
var app = express()
|
||||||
|
|
||||||
|
// parse application/x-www-form-urlencoded
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
|
||||||
|
// parse application/json
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
|
||||||
|
app.use(function (req, res) {
|
||||||
|
res.setHeader('Content-Type', 'text/plain')
|
||||||
|
res.write('you posted:\n')
|
||||||
|
res.end(JSON.stringify(req.body, null, 2))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Express route-specific
|
||||||
|
|
||||||
|
This example demonstrates adding body parsers specifically to the routes that
|
||||||
|
need them. In general, this is the most recommended way to use body-parser with
|
||||||
|
Express.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var express = require('express')
|
||||||
|
var bodyParser = require('body-parser')
|
||||||
|
|
||||||
|
var app = express()
|
||||||
|
|
||||||
|
// create application/json parser
|
||||||
|
var jsonParser = bodyParser.json()
|
||||||
|
|
||||||
|
// create application/x-www-form-urlencoded parser
|
||||||
|
var urlencodedParser = bodyParser.urlencoded({ extended: false })
|
||||||
|
|
||||||
|
// POST /login gets urlencoded bodies
|
||||||
|
app.post('/login', urlencodedParser, function (req, res) {
|
||||||
|
res.send('welcome, ' + req.body.username)
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST /api/users gets JSON bodies
|
||||||
|
app.post('/api/users', jsonParser, function (req, res) {
|
||||||
|
// create user in req.body
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change accepted type for parsers
|
||||||
|
|
||||||
|
All the parsers accept a `type` option which allows you to change the
|
||||||
|
`Content-Type` that the middleware will parse.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var express = require('express')
|
||||||
|
var bodyParser = require('body-parser')
|
||||||
|
|
||||||
|
var app = express()
|
||||||
|
|
||||||
|
// parse various different custom JSON types as JSON
|
||||||
|
app.use(bodyParser.json({ type: 'application/*+json' }))
|
||||||
|
|
||||||
|
// parse some custom thing into a Buffer
|
||||||
|
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }))
|
||||||
|
|
||||||
|
// parse an HTML body into a string
|
||||||
|
app.use(bodyParser.text({ type: 'text/html' }))
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[ci-image]: https://badgen.net/github/checks/expressjs/body-parser/master?label=ci
|
||||||
|
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
|
||||||
|
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/body-parser/master
|
||||||
|
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
|
||||||
|
[node-version-image]: https://badgen.net/npm/node/body-parser
|
||||||
|
[node-version-url]: https://nodejs.org/en/download
|
||||||
|
[npm-downloads-image]: https://badgen.net/npm/dm/body-parser
|
||||||
|
[npm-url]: https://npmjs.org/package/body-parser
|
||||||
|
[npm-version-image]: https://badgen.net/npm/v/body-parser
|
||||||
|
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
|
||||||
|
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Security Policies and Procedures
|
||||||
|
|
||||||
|
## Reporting a Bug
|
||||||
|
|
||||||
|
The Express team and community take all security bugs seriously. Thank you
|
||||||
|
for improving the security of Express. We appreciate your efforts and
|
||||||
|
responsible disclosure and will make every effort to acknowledge your
|
||||||
|
contributions.
|
||||||
|
|
||||||
|
Report security bugs by emailing the current owner(s) of `body-parser`. This
|
||||||
|
information can be found in the npm registry using the command
|
||||||
|
`npm owner ls body-parser`.
|
||||||
|
If unsure or unable to get the information from the above, open an issue
|
||||||
|
in the [project issue tracker](https://github.com/expressjs/body-parser/issues)
|
||||||
|
asking for the current contact information.
|
||||||
|
|
||||||
|
To ensure the timely response to your report, please ensure that the entirety
|
||||||
|
of the report is contained within the email body and not solely behind a web
|
||||||
|
link or an attachment.
|
||||||
|
|
||||||
|
At least one owner will acknowledge your email within 48 hours, and will send a
|
||||||
|
more detailed response within 48 hours indicating the next steps in handling
|
||||||
|
your report. After the initial reply to your report, the owners will
|
||||||
|
endeavor to keep you informed of the progress towards a fix and full
|
||||||
|
announcement, and may ask for additional information or guidance.
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*!
|
||||||
|
* body-parser
|
||||||
|
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var deprecate = require('depd')('body-parser')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache of loaded parsers.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var parsers = Object.create(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef Parsers
|
||||||
|
* @type {function}
|
||||||
|
* @property {function} json
|
||||||
|
* @property {function} raw
|
||||||
|
* @property {function} text
|
||||||
|
* @property {function} urlencoded
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @type {Parsers}
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports = module.exports = deprecate.function(bodyParser,
|
||||||
|
'bodyParser: use individual json/urlencoded middlewares')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON parser.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(exports, 'json', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: createParserGetter('json')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw parser.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(exports, 'raw', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: createParserGetter('raw')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text parser.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(exports, 'text', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: createParserGetter('text')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL-encoded parser.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(exports, 'urlencoded', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: createParserGetter('urlencoded')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a middleware to parse json and urlencoded bodies.
|
||||||
|
*
|
||||||
|
* @param {object} [options]
|
||||||
|
* @return {function}
|
||||||
|
* @deprecated
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function bodyParser (options) {
|
||||||
|
// use default type for parsers
|
||||||
|
var opts = Object.create(options || null, {
|
||||||
|
type: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
value: undefined,
|
||||||
|
writable: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var _urlencoded = exports.urlencoded(opts)
|
||||||
|
var _json = exports.json(opts)
|
||||||
|
|
||||||
|
return function bodyParser (req, res, next) {
|
||||||
|
_json(req, res, function (err) {
|
||||||
|
if (err) return next(err)
|
||||||
|
_urlencoded(req, res, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a getter for loading a parser.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createParserGetter (name) {
|
||||||
|
return function get () {
|
||||||
|
return loadParser(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a parser module.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function loadParser (parserName) {
|
||||||
|
var parser = parsers[parserName]
|
||||||
|
|
||||||
|
if (parser !== undefined) {
|
||||||
|
return parser
|
||||||
|
}
|
||||||
|
|
||||||
|
// this uses a switch for static require analysis
|
||||||
|
switch (parserName) {
|
||||||
|
case 'json':
|
||||||
|
parser = require('./lib/types/json')
|
||||||
|
break
|
||||||
|
case 'raw':
|
||||||
|
parser = require('./lib/types/raw')
|
||||||
|
break
|
||||||
|
case 'text':
|
||||||
|
parser = require('./lib/types/text')
|
||||||
|
break
|
||||||
|
case 'urlencoded':
|
||||||
|
parser = require('./lib/types/urlencoded')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// store to prevent invoking require()
|
||||||
|
return (parsers[parserName] = parser)
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "body-parser",
|
||||||
|
"description": "Node.js body parsing middleware",
|
||||||
|
"version": "1.20.3",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "expressjs/body-parser",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.5",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.13.0",
|
||||||
|
"raw-body": "2.5.2",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "8.34.0",
|
||||||
|
"eslint-config-standard": "14.1.1",
|
||||||
|
"eslint-plugin-import": "2.27.5",
|
||||||
|
"eslint-plugin-markdown": "3.0.0",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-promise": "6.1.1",
|
||||||
|
"eslint-plugin-standard": "4.1.0",
|
||||||
|
"methods": "1.1.2",
|
||||||
|
"mocha": "10.2.0",
|
||||||
|
"nyc": "15.1.0",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"supertest": "6.3.3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/",
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"SECURITY.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -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.
|
||||||
+72
@@ -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
|
||||||
+19
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
+69
@@ -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`
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: '@mscdex/eslint-config',
|
||||||
|
};
|
||||||
+24
@@ -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
|
||||||
+23
@@ -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
|
||||||
+19
@@ -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.
|
||||||
+191
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
+22
@@ -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" }
|
||||||
|
}
|
||||||
+109
@@ -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,
|
||||||
|
};
|
||||||
+94
@@ -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
@@ -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();
|
||||||
+1053
File diff suppressed because it is too large
Load Diff
+488
@@ -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'));
|
||||||
|
});
|
||||||
|
}
|
||||||
+20
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
3.1.2 / 2022-01-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix return value for un-parsable strings
|
||||||
|
|
||||||
|
3.1.1 / 2021-11-15
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix "thousandsSeparator" incorrecting formatting fractional part
|
||||||
|
|
||||||
|
3.1.0 / 2019-01-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add petabyte (`pb`) support
|
||||||
|
|
||||||
|
3.0.0 / 2017-08-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Change "kB" to "KB" in format output
|
||||||
|
* Remove support for Node.js 0.6
|
||||||
|
* Remove support for ComponentJS
|
||||||
|
|
||||||
|
2.5.0 / 2017-03-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add option "unit"
|
||||||
|
|
||||||
|
2.4.0 / 2016-06-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add option "unitSeparator"
|
||||||
|
|
||||||
|
2.3.0 / 2016-02-15
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Drop partial bytes on all parsed units
|
||||||
|
* Fix non-finite numbers to `.format` to return `null`
|
||||||
|
* Fix parsing byte string that looks like hex
|
||||||
|
* perf: hoist regular expressions
|
||||||
|
|
||||||
|
2.2.0 / 2015-11-13
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add option "decimalPlaces"
|
||||||
|
* add option "fixedDecimals"
|
||||||
|
|
||||||
|
2.1.0 / 2015-05-21
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `.format` export
|
||||||
|
* add `.parse` export
|
||||||
|
|
||||||
|
2.0.2 / 2015-05-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* remove map recreation
|
||||||
|
* remove unnecessary object construction
|
||||||
|
|
||||||
|
2.0.1 / 2015-05-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix browserify require
|
||||||
|
* remove node.extend dependency
|
||||||
|
|
||||||
|
2.0.0 / 2015-04-12
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add option "case"
|
||||||
|
* add option "thousandsSeparator"
|
||||||
|
* return "null" on invalid parse input
|
||||||
|
* support proper round-trip: bytes(bytes(num)) === num
|
||||||
|
* units no longer case sensitive when parsing
|
||||||
|
|
||||||
|
1.0.0 / 2014-05-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add negative support. fixes #6
|
||||||
|
|
||||||
|
0.3.0 / 2014-03-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* added terabyte support
|
||||||
|
|
||||||
|
0.2.1 / 2013-04-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add .component
|
||||||
|
|
||||||
|
0.2.0 / 2012-10-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* bytes(200).should.eql('200b')
|
||||||
|
|
||||||
|
0.1.0 / 2012-07-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add bytes to string conversion [yields]
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2012-2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||||
|
Copyright (c) 2015 Jed Watson <jed.watson@me.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.
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# Bytes utility
|
||||||
|
|
||||||
|
[![NPM Version][npm-image]][npm-url]
|
||||||
|
[![NPM Downloads][downloads-image]][downloads-url]
|
||||||
|
[![Build Status][ci-image]][ci-url]
|
||||||
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||||
|
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||||
|
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
var bytes = require('bytes');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### bytes(number|string value, [options]): number|string|null
|
||||||
|
|
||||||
|
Default export function. Delegates to either `bytes.format` or `bytes.parse` based on the type of `value`.
|
||||||
|
|
||||||
|
**Arguments**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------|----------|--------------------|
|
||||||
|
| value | `number`|`string` | Number value to format or string value to parse |
|
||||||
|
| options | `Object` | Conversion options for `format` |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------|------------------|-------------------------------------------------|
|
||||||
|
| results | `string`|`number`|`null` | Return null upon error. Numeric value in bytes, or string value otherwise. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
bytes(1024);
|
||||||
|
// output: '1KB'
|
||||||
|
|
||||||
|
bytes('1KB');
|
||||||
|
// output: 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
#### bytes.format(number value, [options]): string|null
|
||||||
|
|
||||||
|
Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is
|
||||||
|
rounded.
|
||||||
|
|
||||||
|
**Arguments**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------|----------|--------------------|
|
||||||
|
| value | `number` | Value in bytes |
|
||||||
|
| options | `Object` | Conversion options |
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|-------------------|--------|-----------------------------------------------------------------------------------------|
|
||||||
|
| decimalPlaces | `number`|`null` | Maximum number of decimal places to include in output. Default value to `2`. |
|
||||||
|
| fixedDecimals | `boolean`|`null` | Whether to always display the maximum number of decimal places. Default value to `false` |
|
||||||
|
| thousandsSeparator | `string`|`null` | Example of values: `' '`, `','` and `'.'`... Default value to `''`. |
|
||||||
|
| unit | `string`|`null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). |
|
||||||
|
| unitSeparator | `string`|`null` | Separator to use between number and unit. Default value to `''`. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------|------------------|-------------------------------------------------|
|
||||||
|
| results | `string`|`null` | Return null upon error. String value otherwise. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
bytes.format(1024);
|
||||||
|
// output: '1KB'
|
||||||
|
|
||||||
|
bytes.format(1000);
|
||||||
|
// output: '1000B'
|
||||||
|
|
||||||
|
bytes.format(1000, {thousandsSeparator: ' '});
|
||||||
|
// output: '1 000B'
|
||||||
|
|
||||||
|
bytes.format(1024 * 1.7, {decimalPlaces: 0});
|
||||||
|
// output: '2KB'
|
||||||
|
|
||||||
|
bytes.format(1024, {unitSeparator: ' '});
|
||||||
|
// output: '1 KB'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### bytes.parse(string|number value): number|null
|
||||||
|
|
||||||
|
Parse the string value into an integer in bytes. If no unit is given, or `value`
|
||||||
|
is a number, it is assumed the value is in bytes.
|
||||||
|
|
||||||
|
Supported units and abbreviations are as follows and are case-insensitive:
|
||||||
|
|
||||||
|
* `b` for bytes
|
||||||
|
* `kb` for kilobytes
|
||||||
|
* `mb` for megabytes
|
||||||
|
* `gb` for gigabytes
|
||||||
|
* `tb` for terabytes
|
||||||
|
* `pb` for petabytes
|
||||||
|
|
||||||
|
The units are in powers of two, not ten. This means 1kb = 1024b according to this parser.
|
||||||
|
|
||||||
|
**Arguments**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------------|--------|--------------------|
|
||||||
|
| value | `string`|`number` | String to parse, or number in bytes. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------|-------------|-------------------------|
|
||||||
|
| results | `number`|`null` | Return null upon error. Value in bytes otherwise. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
bytes.parse('1KB');
|
||||||
|
// output: 1024
|
||||||
|
|
||||||
|
bytes.parse('1024');
|
||||||
|
// output: 1024
|
||||||
|
|
||||||
|
bytes.parse(1024);
|
||||||
|
// output: 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[ci-image]: https://badgen.net/github/checks/visionmedia/bytes.js/master?label=ci
|
||||||
|
[ci-url]: https://github.com/visionmedia/bytes.js/actions?query=workflow%3Aci
|
||||||
|
[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master
|
||||||
|
[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master
|
||||||
|
[downloads-image]: https://badgen.net/npm/dm/bytes
|
||||||
|
[downloads-url]: https://npmjs.org/package/bytes
|
||||||
|
[npm-image]: https://badgen.net/npm/v/bytes
|
||||||
|
[npm-url]: https://npmjs.org/package/bytes
|
||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
/*!
|
||||||
|
* bytes
|
||||||
|
* Copyright(c) 2012-2014 TJ Holowaychuk
|
||||||
|
* Copyright(c) 2015 Jed Watson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = bytes;
|
||||||
|
module.exports.format = format;
|
||||||
|
module.exports.parse = parse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module variables.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g;
|
||||||
|
|
||||||
|
var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/;
|
||||||
|
|
||||||
|
var map = {
|
||||||
|
b: 1,
|
||||||
|
kb: 1 << 10,
|
||||||
|
mb: 1 << 20,
|
||||||
|
gb: 1 << 30,
|
||||||
|
tb: Math.pow(1024, 4),
|
||||||
|
pb: Math.pow(1024, 5),
|
||||||
|
};
|
||||||
|
|
||||||
|
var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given value in bytes into a string or parse to string to an integer in bytes.
|
||||||
|
*
|
||||||
|
* @param {string|number} value
|
||||||
|
* @param {{
|
||||||
|
* case: [string],
|
||||||
|
* decimalPlaces: [number]
|
||||||
|
* fixedDecimals: [boolean]
|
||||||
|
* thousandsSeparator: [string]
|
||||||
|
* unitSeparator: [string]
|
||||||
|
* }} [options] bytes options.
|
||||||
|
*
|
||||||
|
* @returns {string|number|null}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function bytes(value, options) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return format(value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the given value in bytes into a string.
|
||||||
|
*
|
||||||
|
* If the value is negative, it is kept as such. If it is a float,
|
||||||
|
* it is rounded.
|
||||||
|
*
|
||||||
|
* @param {number} value
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {number} [options.decimalPlaces=2]
|
||||||
|
* @param {number} [options.fixedDecimals=false]
|
||||||
|
* @param {string} [options.thousandsSeparator=]
|
||||||
|
* @param {string} [options.unit=]
|
||||||
|
* @param {string} [options.unitSeparator=]
|
||||||
|
*
|
||||||
|
* @returns {string|null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function format(value, options) {
|
||||||
|
if (!Number.isFinite(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mag = Math.abs(value);
|
||||||
|
var thousandsSeparator = (options && options.thousandsSeparator) || '';
|
||||||
|
var unitSeparator = (options && options.unitSeparator) || '';
|
||||||
|
var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2;
|
||||||
|
var fixedDecimals = Boolean(options && options.fixedDecimals);
|
||||||
|
var unit = (options && options.unit) || '';
|
||||||
|
|
||||||
|
if (!unit || !map[unit.toLowerCase()]) {
|
||||||
|
if (mag >= map.pb) {
|
||||||
|
unit = 'PB';
|
||||||
|
} else if (mag >= map.tb) {
|
||||||
|
unit = 'TB';
|
||||||
|
} else if (mag >= map.gb) {
|
||||||
|
unit = 'GB';
|
||||||
|
} else if (mag >= map.mb) {
|
||||||
|
unit = 'MB';
|
||||||
|
} else if (mag >= map.kb) {
|
||||||
|
unit = 'KB';
|
||||||
|
} else {
|
||||||
|
unit = 'B';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = value / map[unit.toLowerCase()];
|
||||||
|
var str = val.toFixed(decimalPlaces);
|
||||||
|
|
||||||
|
if (!fixedDecimals) {
|
||||||
|
str = str.replace(formatDecimalsRegExp, '$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thousandsSeparator) {
|
||||||
|
str = str.split('.').map(function (s, i) {
|
||||||
|
return i === 0
|
||||||
|
? s.replace(formatThousandsRegExp, thousandsSeparator)
|
||||||
|
: s
|
||||||
|
}).join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + unitSeparator + unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the string value into an integer in bytes.
|
||||||
|
*
|
||||||
|
* If no unit is given, it is assumed the value is in bytes.
|
||||||
|
*
|
||||||
|
* @param {number|string} val
|
||||||
|
*
|
||||||
|
* @returns {number|null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse(val) {
|
||||||
|
if (typeof val === 'number' && !isNaN(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof val !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if the string passed is valid
|
||||||
|
var results = parseRegExp.exec(val);
|
||||||
|
var floatValue;
|
||||||
|
var unit = 'b';
|
||||||
|
|
||||||
|
if (!results) {
|
||||||
|
// Nothing could be extracted from the given string
|
||||||
|
floatValue = parseInt(val, 10);
|
||||||
|
unit = 'b'
|
||||||
|
} else {
|
||||||
|
// Retrieve the value and the unit
|
||||||
|
floatValue = parseFloat(results[1]);
|
||||||
|
unit = results[4].toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(floatValue)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(map[unit] * floatValue);
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "bytes",
|
||||||
|
"description": "Utility to parse a string bytes to bytes and vice-versa",
|
||||||
|
"version": "3.1.2",
|
||||||
|
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
|
||||||
|
"contributors": [
|
||||||
|
"Jed Watson <jed.watson@me.com>",
|
||||||
|
"Théo FIDRY <theo.fidry@gmail.com>"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"byte",
|
||||||
|
"bytes",
|
||||||
|
"utility",
|
||||||
|
"parse",
|
||||||
|
"parser",
|
||||||
|
"convert",
|
||||||
|
"converter"
|
||||||
|
],
|
||||||
|
"repository": "visionmedia/bytes.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-plugin-markdown": "2.2.1",
|
||||||
|
"mocha": "9.2.0",
|
||||||
|
"nyc": "15.1.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"History.md",
|
||||||
|
"LICENSE",
|
||||||
|
"Readme.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --check-leaks --reporter spec",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
|
||||||
|
"extends": "@ljharb",
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
"func-name-matching": 0,
|
||||||
|
"id-length": 0,
|
||||||
|
"new-cap": [2, {
|
||||||
|
"capIsNewExceptions": [
|
||||||
|
"GetIntrinsic",
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
"no-extra-parens": 0,
|
||||||
|
"no-magic-numbers": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
+12
@@ -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/call-bind-apply-helpers
|
||||||
|
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']
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"all": true,
|
||||||
|
"check-coverage": false,
|
||||||
|
"reporter": ["text-summary", "text", "html", "json"],
|
||||||
|
"exclude": [
|
||||||
|
"coverage",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# 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.0.2](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.1...v1.0.2) - 2025-02-12
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [types] improve inferred types [`e6f9586`](https://github.com/ljharb/call-bind-apply-helpers/commit/e6f95860a3c72879cb861a858cdfb8138fbedec1)
|
||||||
|
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`e43d540`](https://github.com/ljharb/call-bind-apply-helpers/commit/e43d5409f97543bfbb11f345d47d8ce4e066d8c1)
|
||||||
|
|
||||||
|
## [v1.0.1](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.0...v1.0.1) - 2024-12-08
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [types] `reflectApply`: fix types [`4efc396`](https://github.com/ljharb/call-bind-apply-helpers/commit/4efc3965351a4f02cc55e836fa391d3d11ef2ef8)
|
||||||
|
- [Fix] `reflectApply`: oops, Reflect is not a function [`83cc739`](https://github.com/ljharb/call-bind-apply-helpers/commit/83cc7395de6b79b7730bdf092f1436f0b1263c75)
|
||||||
|
- [Dev Deps] update `@arethetypeswrong/cli` [`80bd5d3`](https://github.com/ljharb/call-bind-apply-helpers/commit/80bd5d3ae58b4f6b6995ce439dd5a1bcb178a940)
|
||||||
|
|
||||||
|
## v1.0.0 - 2024-12-05
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Initial implementation, tests, readme [`7879629`](https://github.com/ljharb/call-bind-apply-helpers/commit/78796290f9b7430c9934d6f33d94ae9bc89fce04)
|
||||||
|
- Initial commit [`3f1dc16`](https://github.com/ljharb/call-bind-apply-helpers/commit/3f1dc164afc43285631b114a5f9dd9137b2b952f)
|
||||||
|
- npm init [`081df04`](https://github.com/ljharb/call-bind-apply-helpers/commit/081df048c312fcee400922026f6e97281200a603)
|
||||||
|
- Only apps should have lockfiles [`5b9ca0f`](https://github.com/ljharb/call-bind-apply-helpers/commit/5b9ca0fe8101ebfaf309c549caac4e0a017ed930)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Jordan Harband
|
||||||
|
|
||||||
|
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.
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
# call-bind-apply-helpers <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||||
|
|
||||||
|
[![github actions][actions-image]][actions-url]
|
||||||
|
[![coverage][codecov-image]][codecov-url]
|
||||||
|
[![dependency status][deps-svg]][deps-url]
|
||||||
|
[![dev dependency status][dev-deps-svg]][dev-deps-url]
|
||||||
|
[![License][license-image]][license-url]
|
||||||
|
[![Downloads][downloads-image]][downloads-url]
|
||||||
|
|
||||||
|
[![npm badge][npm-badge-png]][package-url]
|
||||||
|
|
||||||
|
Helper functions around Function call/apply/bind, for use in `call-bind`.
|
||||||
|
|
||||||
|
The only packages that should likely ever use this package directly are `call-bind` and `get-intrinsic`.
|
||||||
|
Please use `call-bind` unless you have a very good reason not to.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save call-bind-apply-helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage/Examples
|
||||||
|
|
||||||
|
```js
|
||||||
|
const assert = require('assert');
|
||||||
|
const callBindBasic = require('call-bind-apply-helpers');
|
||||||
|
|
||||||
|
function f(a, b) {
|
||||||
|
assert.equal(this, 1);
|
||||||
|
assert.equal(a, 2);
|
||||||
|
assert.equal(b, 3);
|
||||||
|
assert.equal(arguments.length, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fBound = callBindBasic([f, 1]);
|
||||||
|
|
||||||
|
delete Function.prototype.call;
|
||||||
|
delete Function.prototype.bind;
|
||||||
|
|
||||||
|
fBound(2, 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Clone the repo, `npm install`, and run `npm test`
|
||||||
|
|
||||||
|
[package-url]: https://npmjs.org/package/call-bind-apply-helpers
|
||||||
|
[npm-version-svg]: https://versionbadg.es/ljharb/call-bind-apply-helpers.svg
|
||||||
|
[deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers.svg
|
||||||
|
[deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers
|
||||||
|
[dev-deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers/dev-status.svg
|
||||||
|
[dev-deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers#info=devDependencies
|
||||||
|
[npm-badge-png]: https://nodei.co/npm/call-bind-apply-helpers.png?downloads=true&stars=true
|
||||||
|
[license-image]: https://img.shields.io/npm/l/call-bind-apply-helpers.svg
|
||||||
|
[license-url]: LICENSE
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/call-bind-apply-helpers.svg
|
||||||
|
[downloads-url]: https://npm-stat.com/charts.html?package=call-bind-apply-helpers
|
||||||
|
[codecov-image]: https://codecov.io/gh/ljharb/call-bind-apply-helpers/branch/main/graphs/badge.svg
|
||||||
|
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bind-apply-helpers/
|
||||||
|
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bind-apply-helpers
|
||||||
|
[actions-url]: https://github.com/ljharb/call-bind-apply-helpers/actions
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export = Reflect.apply;
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bind = require('function-bind');
|
||||||
|
|
||||||
|
var $apply = require('./functionApply');
|
||||||
|
var $call = require('./functionCall');
|
||||||
|
var $reflectApply = require('./reflectApply');
|
||||||
|
|
||||||
|
/** @type {import('./actualApply')} */
|
||||||
|
module.exports = $reflectApply || bind.call($call, $apply);
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import actualApply from './actualApply';
|
||||||
|
|
||||||
|
type TupleSplitHead<T extends any[], N extends number> = T['length'] extends N
|
||||||
|
? T
|
||||||
|
: T extends [...infer R, any]
|
||||||
|
? TupleSplitHead<R, N>
|
||||||
|
: never
|
||||||
|
|
||||||
|
type TupleSplitTail<T, N extends number, O extends any[] = []> = O['length'] extends N
|
||||||
|
? T
|
||||||
|
: T extends [infer F, ...infer R]
|
||||||
|
? TupleSplitTail<[...R], N, [...O, F]>
|
||||||
|
: never
|
||||||
|
|
||||||
|
type TupleSplit<T extends any[], N extends number> = [TupleSplitHead<T, N>, TupleSplitTail<T, N>]
|
||||||
|
|
||||||
|
declare function applyBind(...args: TupleSplit<Parameters<typeof actualApply>, 2>[1]): ReturnType<typeof actualApply>;
|
||||||
|
|
||||||
|
export = applyBind;
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bind = require('function-bind');
|
||||||
|
var $apply = require('./functionApply');
|
||||||
|
var actualApply = require('./actualApply');
|
||||||
|
|
||||||
|
/** @type {import('./applyBind')} */
|
||||||
|
module.exports = function applyBind() {
|
||||||
|
return actualApply(bind, $apply, arguments);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export = Function.prototype.apply;
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('./functionApply')} */
|
||||||
|
module.exports = Function.prototype.apply;
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export = Function.prototype.call;
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('./functionCall')} */
|
||||||
|
module.exports = Function.prototype.call;
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
type RemoveFromTuple<
|
||||||
|
Tuple extends readonly unknown[],
|
||||||
|
RemoveCount extends number,
|
||||||
|
Index extends 1[] = []
|
||||||
|
> = Index["length"] extends RemoveCount
|
||||||
|
? Tuple
|
||||||
|
: Tuple extends [infer First, ...infer Rest]
|
||||||
|
? RemoveFromTuple<Rest, RemoveCount, [...Index, 1]>
|
||||||
|
: Tuple;
|
||||||
|
|
||||||
|
type ConcatTuples<
|
||||||
|
Prefix extends readonly unknown[],
|
||||||
|
Suffix extends readonly unknown[]
|
||||||
|
> = [...Prefix, ...Suffix];
|
||||||
|
|
||||||
|
type ExtractFunctionParams<T> = T extends (this: infer TThis, ...args: infer P extends readonly unknown[]) => infer R
|
||||||
|
? { thisArg: TThis; params: P; returnType: R }
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type BindFunction<
|
||||||
|
T extends (this: any, ...args: any[]) => any,
|
||||||
|
TThis,
|
||||||
|
TBoundArgs extends readonly unknown[],
|
||||||
|
ReceiverBound extends boolean
|
||||||
|
> = ExtractFunctionParams<T> extends {
|
||||||
|
thisArg: infer OrigThis;
|
||||||
|
params: infer P extends readonly unknown[];
|
||||||
|
returnType: infer R;
|
||||||
|
}
|
||||||
|
? ReceiverBound extends true
|
||||||
|
? (...args: RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>) => R extends [OrigThis, ...infer Rest]
|
||||||
|
? [TThis, ...Rest] // Replace `this` with `thisArg`
|
||||||
|
: R
|
||||||
|
: <U, RemainingArgs extends RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>>(
|
||||||
|
thisArg: U,
|
||||||
|
...args: RemainingArgs
|
||||||
|
) => R extends [OrigThis, ...infer Rest]
|
||||||
|
? [U, ...ConcatTuples<TBoundArgs, Rest>] // Preserve bound args in return type
|
||||||
|
: R
|
||||||
|
: never;
|
||||||
|
|
||||||
|
declare function callBind<
|
||||||
|
const T extends (this: any, ...args: any[]) => any,
|
||||||
|
Extracted extends ExtractFunctionParams<T>,
|
||||||
|
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[],
|
||||||
|
const TThis extends Extracted["thisArg"]
|
||||||
|
>(
|
||||||
|
args: [fn: T, thisArg: TThis, ...boundArgs: TBoundArgs]
|
||||||
|
): BindFunction<T, TThis, TBoundArgs, true>;
|
||||||
|
|
||||||
|
declare function callBind<
|
||||||
|
const T extends (this: any, ...args: any[]) => any,
|
||||||
|
Extracted extends ExtractFunctionParams<T>,
|
||||||
|
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[]
|
||||||
|
>(
|
||||||
|
args: [fn: T, ...boundArgs: TBoundArgs]
|
||||||
|
): BindFunction<T, Extracted["thisArg"], TBoundArgs, false>;
|
||||||
|
|
||||||
|
declare function callBind<const TArgs extends readonly unknown[]>(
|
||||||
|
args: [fn: Exclude<TArgs[0], Function>, ...rest: TArgs]
|
||||||
|
): never;
|
||||||
|
|
||||||
|
// export as namespace callBind;
|
||||||
|
export = callBind;
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bind = require('function-bind');
|
||||||
|
var $TypeError = require('es-errors/type');
|
||||||
|
|
||||||
|
var $call = require('./functionCall');
|
||||||
|
var $actualApply = require('./actualApply');
|
||||||
|
|
||||||
|
/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */
|
||||||
|
module.exports = function callBindBasic(args) {
|
||||||
|
if (args.length < 1 || typeof args[0] !== 'function') {
|
||||||
|
throw new $TypeError('a function is required');
|
||||||
|
}
|
||||||
|
return $actualApply(bind, $call, args);
|
||||||
|
};
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"name": "call-bind-apply-helpers",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"description": "Helper functions around Function call/apply/bind, for use in `call-bind`",
|
||||||
|
"main": "index.js",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.js",
|
||||||
|
"./actualApply": "./actualApply.js",
|
||||||
|
"./applyBind": "./applyBind.js",
|
||||||
|
"./functionApply": "./functionApply.js",
|
||||||
|
"./functionCall": "./functionCall.js",
|
||||||
|
"./reflectApply": "./reflectApply.js",
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "npmignore --auto --commentLines=auto",
|
||||||
|
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||||
|
"prepublishOnly": "safe-publish-latest",
|
||||||
|
"prelint": "evalmd README.md",
|
||||||
|
"lint": "eslint --ext=.js,.mjs .",
|
||||||
|
"postlint": "tsc -p . && attw -P",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"tests-only": "nyc tape 'test/**/*.js'",
|
||||||
|
"test": "npm run tests-only",
|
||||||
|
"posttest": "npx npm@'>=10.2' audit --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)\")\""
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ljharb/call-bind-apply-helpers.git"
|
||||||
|
},
|
||||||
|
"author": "Jordan Harband <ljharb@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ljharb/call-bind-apply-helpers/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ljharb/call-bind-apply-helpers#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@arethetypeswrong/cli": "^0.17.3",
|
||||||
|
"@ljharb/eslint-config": "^21.1.1",
|
||||||
|
"@ljharb/tsconfig": "^0.2.3",
|
||||||
|
"@types/for-each": "^0.3.3",
|
||||||
|
"@types/function-bind": "^1.1.10",
|
||||||
|
"@types/object-inspect": "^1.13.0",
|
||||||
|
"@types/tape": "^5.8.1",
|
||||||
|
"auto-changelog": "^2.5.0",
|
||||||
|
"encoding": "^0.1.13",
|
||||||
|
"es-value-fixtures": "^1.7.1",
|
||||||
|
"eslint": "=8.8.0",
|
||||||
|
"evalmd": "^0.0.19",
|
||||||
|
"for-each": "^0.3.5",
|
||||||
|
"has-strict-mode": "^1.1.0",
|
||||||
|
"in-publish": "^2.0.1",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"nyc": "^10.3.2",
|
||||||
|
"object-inspect": "^1.13.4",
|
||||||
|
"safe-publish-latest": "^2.0.0",
|
||||||
|
"tape": "^5.9.0",
|
||||||
|
"typescript": "next"
|
||||||
|
},
|
||||||
|
"testling": {
|
||||||
|
"files": "test/index.js"
|
||||||
|
},
|
||||||
|
"auto-changelog": {
|
||||||
|
"output": "CHANGELOG.md",
|
||||||
|
"template": "keepachangelog",
|
||||||
|
"unreleased": false,
|
||||||
|
"commitLimit": false,
|
||||||
|
"backfillLimit": false,
|
||||||
|
"hideCredit": true
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"ignore": [
|
||||||
|
".github/workflows"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
declare const reflectApply: false | typeof Reflect.apply;
|
||||||
|
|
||||||
|
export = reflectApply;
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('./reflectApply')} */
|
||||||
|
module.exports = typeof Reflect !== 'undefined' && Reflect && Reflect.apply;
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var callBind = require('../');
|
||||||
|
var hasStrictMode = require('has-strict-mode')();
|
||||||
|
var forEach = require('for-each');
|
||||||
|
var inspect = require('object-inspect');
|
||||||
|
var v = require('es-value-fixtures');
|
||||||
|
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('callBindBasic', function (t) {
|
||||||
|
forEach(v.nonFunctions, function (nonFunction) {
|
||||||
|
t['throws'](
|
||||||
|
// @ts-expect-error
|
||||||
|
function () { callBind([nonFunction]); },
|
||||||
|
TypeError,
|
||||||
|
inspect(nonFunction) + ' is not a function'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var sentinel = { sentinel: true };
|
||||||
|
/** @type {<T, A extends number, B extends number>(this: T, a: A, b: B) => [T | undefined, A, B]} */
|
||||||
|
var func = function (a, b) {
|
||||||
|
// eslint-disable-next-line no-invalid-this
|
||||||
|
return [!hasStrictMode && this === global ? undefined : this, a, b];
|
||||||
|
};
|
||||||
|
t.equal(func.length, 2, 'original function length is 2');
|
||||||
|
|
||||||
|
/** type {(thisArg: unknown, a: number, b: number) => [unknown, number, number]} */
|
||||||
|
var bound = callBind([func]);
|
||||||
|
/** type {((a: number, b: number) => [typeof sentinel, typeof a, typeof b])} */
|
||||||
|
var boundR = callBind([func, sentinel]);
|
||||||
|
/** type {((b: number) => [typeof sentinel, number, typeof b])} */
|
||||||
|
var boundArg = callBind([func, sentinel, /** @type {const} */ (1)]);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(bound(), [undefined, undefined, undefined], 'bound func with no args');
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(func(), [undefined, undefined, undefined], 'unbound func with too few args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(bound(1, 2), [hasStrictMode ? 1 : Object(1), 2, undefined], 'bound func too few args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(boundR(), [sentinel, undefined, undefined], 'bound func with receiver, with too few args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(boundArg(), [sentinel, 1, undefined], 'bound func with receiver and arg, with too few args');
|
||||||
|
|
||||||
|
t.deepEqual(func(1, 2), [undefined, 1, 2], 'unbound func with right args');
|
||||||
|
t.deepEqual(bound(1, 2, 3), [hasStrictMode ? 1 : Object(1), 2, 3], 'bound func with right args');
|
||||||
|
t.deepEqual(boundR(1, 2), [sentinel, 1, 2], 'bound func with receiver, with right args');
|
||||||
|
t.deepEqual(boundArg(2), [sentinel, 1, 2], 'bound func with receiver and arg, with right arg');
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(func(1, 2, 3), [undefined, 1, 2], 'unbound func with too many args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(bound(1, 2, 3, 4), [hasStrictMode ? 1 : Object(1), 2, 3], 'bound func with too many args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(boundR(1, 2, 3), [sentinel, 1, 2], 'bound func with receiver, with too many args');
|
||||||
|
// @ts-expect-error
|
||||||
|
t.deepEqual(boundArg(2, 3), [sentinel, 1, 2], 'bound func with receiver and arg, with too many args');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@ljharb/tsconfig",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2021",
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"coverage",
|
||||||
|
],
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
|
||||||
|
"extends": "@ljharb",
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
"new-cap": [2, {
|
||||||
|
"capIsNewExceptions": [
|
||||||
|
"GetIntrinsic",
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}
|
||||||
+12
@@ -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/call-bound
|
||||||
|
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']
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"all": true,
|
||||||
|
"check-coverage": false,
|
||||||
|
"reporter": ["text-summary", "text", "html", "json"],
|
||||||
|
"exclude": [
|
||||||
|
"coverage",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# 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.0.3](https://github.com/ljharb/call-bound/compare/v1.0.2...v1.0.3) - 2024-12-15
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [Refactor] use `call-bind-apply-helpers` instead of `call-bind` [`5e0b134`](https://github.com/ljharb/call-bound/commit/5e0b13496df14fb7d05dae9412f088da8d3f75be)
|
||||||
|
- [Deps] update `get-intrinsic` [`41fc967`](https://github.com/ljharb/call-bound/commit/41fc96732a22c7b7e8f381f93ccc54bb6293be2e)
|
||||||
|
- [readme] fix example [`79a0137`](https://github.com/ljharb/call-bound/commit/79a0137723f7c6d09c9c05452bbf8d5efb5d6e49)
|
||||||
|
- [meta] add `sideEffects` flag [`08b07be`](https://github.com/ljharb/call-bound/commit/08b07be7f1c03f67dc6f3cdaf0906259771859f7)
|
||||||
|
|
||||||
|
## [v1.0.2](https://github.com/ljharb/call-bound/compare/v1.0.1...v1.0.2) - 2024-12-10
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `gopd` [`e6a5ffe`](https://github.com/ljharb/call-bound/commit/e6a5ffe849368fe4f74dfd6cdeca1b9baa39e8d5)
|
||||||
|
- [Deps] update `call-bind`, `get-intrinsic` [`2aeb5b5`](https://github.com/ljharb/call-bound/commit/2aeb5b521dc2b2683d1345c753ea1161de2d1c14)
|
||||||
|
- [types] improve return type [`1a0c9fe`](https://github.com/ljharb/call-bound/commit/1a0c9fe3114471e7ca1f57d104e2efe713bb4871)
|
||||||
|
|
||||||
|
## v1.0.1 - 2024-12-05
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Initial implementation, tests, readme, types [`6d94121`](https://github.com/ljharb/call-bound/commit/6d94121a9243602e506334069f7a03189fe3363d)
|
||||||
|
- Initial commit [`0eae867`](https://github.com/ljharb/call-bound/commit/0eae867334ea025c33e6e91cdecfc9df96680cf9)
|
||||||
|
- npm init [`71b2479`](https://github.com/ljharb/call-bound/commit/71b2479c6723e0b7d91a6b663613067e98b7b275)
|
||||||
|
- Only apps should have lockfiles [`c3754a9`](https://github.com/ljharb/call-bound/commit/c3754a949b7f9132b47e2d18c1729889736741eb)
|
||||||
|
- [actions] skip `npm ls` in node < 10 [`74275a5`](https://github.com/ljharb/call-bound/commit/74275a5186b8caf6309b6b97472bdcb0df4683a8)
|
||||||
|
- [Dev Deps] add missing peer dep [`1354de8`](https://github.com/ljharb/call-bound/commit/1354de8679413e4ae9c523d85f76fa7a5e032d97)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Jordan Harband
|
||||||
|
|
||||||
|
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.
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
# call-bound <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||||
|
|
||||||
|
[![github actions][actions-image]][actions-url]
|
||||||
|
[![coverage][codecov-image]][codecov-url]
|
||||||
|
[![dependency status][deps-svg]][deps-url]
|
||||||
|
[![dev dependency status][dev-deps-svg]][dev-deps-url]
|
||||||
|
[![License][license-image]][license-url]
|
||||||
|
[![Downloads][downloads-image]][downloads-url]
|
||||||
|
|
||||||
|
[![npm badge][npm-badge-png]][package-url]
|
||||||
|
|
||||||
|
Robust call-bound JavaScript intrinsics, using `call-bind` and `get-intrinsic`.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save call-bound
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage/Examples
|
||||||
|
|
||||||
|
```js
|
||||||
|
const assert = require('assert');
|
||||||
|
const callBound = require('call-bound');
|
||||||
|
|
||||||
|
const slice = callBound('Array.prototype.slice');
|
||||||
|
|
||||||
|
delete Function.prototype.call;
|
||||||
|
delete Function.prototype.bind;
|
||||||
|
delete Array.prototype.slice;
|
||||||
|
|
||||||
|
assert.deepEqual(slice([1, 2, 3, 4], 1, -1), [2, 3]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Clone the repo, `npm install`, and run `npm test`
|
||||||
|
|
||||||
|
[package-url]: https://npmjs.org/package/call-bound
|
||||||
|
[npm-version-svg]: https://versionbadg.es/ljharb/call-bound.svg
|
||||||
|
[deps-svg]: https://david-dm.org/ljharb/call-bound.svg
|
||||||
|
[deps-url]: https://david-dm.org/ljharb/call-bound
|
||||||
|
[dev-deps-svg]: https://david-dm.org/ljharb/call-bound/dev-status.svg
|
||||||
|
[dev-deps-url]: https://david-dm.org/ljharb/call-bound#info=devDependencies
|
||||||
|
[npm-badge-png]: https://nodei.co/npm/call-bound.png?downloads=true&stars=true
|
||||||
|
[license-image]: https://img.shields.io/npm/l/call-bound.svg
|
||||||
|
[license-url]: LICENSE
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/call-bound.svg
|
||||||
|
[downloads-url]: https://npm-stat.com/charts.html?package=call-bound
|
||||||
|
[codecov-image]: https://codecov.io/gh/ljharb/call-bound/branch/main/graphs/badge.svg
|
||||||
|
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bound/
|
||||||
|
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bound
|
||||||
|
[actions-url]: https://github.com/ljharb/call-bound/actions
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import callBind from 'call-bind-apply-helpers';
|
||||||
|
|
||||||
|
declare function callBoundIntrinsic(
|
||||||
|
name: string,
|
||||||
|
allowMissing?: false
|
||||||
|
): ReturnType<typeof callBind>;
|
||||||
|
|
||||||
|
declare function callBoundIntrinsic(
|
||||||
|
name: string,
|
||||||
|
allowMissing: true
|
||||||
|
): undefined | ReturnType<typeof callBind>;
|
||||||
|
|
||||||
|
export = callBoundIntrinsic;
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var GetIntrinsic = require('get-intrinsic');
|
||||||
|
|
||||||
|
var callBindBasic = require('call-bind-apply-helpers');
|
||||||
|
|
||||||
|
/** @type {(thisArg: string, searchString: string, position?: number) => number} */
|
||||||
|
var $indexOf = callBindBasic([GetIntrinsic('%String.prototype.indexOf%')]);
|
||||||
|
|
||||||
|
/** @type {import('.')} */
|
||||||
|
module.exports = function callBoundIntrinsic(name, allowMissing) {
|
||||||
|
// eslint-disable-next-line no-extra-parens
|
||||||
|
var intrinsic = /** @type {Parameters<typeof callBindBasic>[0][0]} */ (GetIntrinsic(name, !!allowMissing));
|
||||||
|
if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {
|
||||||
|
return callBindBasic([intrinsic]);
|
||||||
|
}
|
||||||
|
return intrinsic;
|
||||||
|
};
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"name": "call-bound",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"description": "Robust call-bound JavaScript intrinsics, using `call-bind` and `get-intrinsic`.",
|
||||||
|
"main": "index.js",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.js",
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "npmignore --auto --commentLines=auto",
|
||||||
|
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||||
|
"prepublishOnly": "safe-publish-latest",
|
||||||
|
"prelint": "evalmd README.md",
|
||||||
|
"lint": "eslint --ext=.js,.mjs .",
|
||||||
|
"postlint": "tsc -p . && attw -P",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"tests-only": "nyc tape 'test/**/*.js'",
|
||||||
|
"test": "npm run tests-only",
|
||||||
|
"posttest": "npx npm@'>=10.2' audit --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)\")\""
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ljharb/call-bound.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"javascript",
|
||||||
|
"ecmascript",
|
||||||
|
"es",
|
||||||
|
"js",
|
||||||
|
"callbind",
|
||||||
|
"callbound",
|
||||||
|
"call",
|
||||||
|
"bind",
|
||||||
|
"bound",
|
||||||
|
"call-bind",
|
||||||
|
"call-bound",
|
||||||
|
"function",
|
||||||
|
"es-abstract"
|
||||||
|
],
|
||||||
|
"author": "Jordan Harband <ljharb@gmail.com>",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ljharb/call-bound/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ljharb/call-bound#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"get-intrinsic": "^1.2.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@arethetypeswrong/cli": "^0.17.1",
|
||||||
|
"@ljharb/eslint-config": "^21.1.1",
|
||||||
|
"@ljharb/tsconfig": "^0.2.2",
|
||||||
|
"@types/call-bind": "^1.0.5",
|
||||||
|
"@types/get-intrinsic": "^1.2.3",
|
||||||
|
"@types/tape": "^5.6.5",
|
||||||
|
"auto-changelog": "^2.5.0",
|
||||||
|
"encoding": "^0.1.13",
|
||||||
|
"es-value-fixtures": "^1.5.0",
|
||||||
|
"eslint": "=8.8.0",
|
||||||
|
"evalmd": "^0.0.19",
|
||||||
|
"for-each": "^0.3.3",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-strict-mode": "^1.0.1",
|
||||||
|
"in-publish": "^2.0.1",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"nyc": "^10.3.2",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"safe-publish-latest": "^2.0.0",
|
||||||
|
"tape": "^5.9.0",
|
||||||
|
"typescript": "next"
|
||||||
|
},
|
||||||
|
"testling": {
|
||||||
|
"files": "test/index.js"
|
||||||
|
},
|
||||||
|
"auto-changelog": {
|
||||||
|
"output": "CHANGELOG.md",
|
||||||
|
"template": "keepachangelog",
|
||||||
|
"unreleased": false,
|
||||||
|
"commitLimit": false,
|
||||||
|
"backfillLimit": false,
|
||||||
|
"hideCredit": true
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"ignore": [
|
||||||
|
".github/workflows"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
var callBound = require('../');
|
||||||
|
|
||||||
|
test('callBound', function (t) {
|
||||||
|
// static primitive
|
||||||
|
t.equal(callBound('Array.length'), Array.length, 'Array.length yields itself');
|
||||||
|
t.equal(callBound('%Array.length%'), Array.length, '%Array.length% yields itself');
|
||||||
|
|
||||||
|
// static non-function object
|
||||||
|
t.equal(callBound('Array.prototype'), Array.prototype, 'Array.prototype yields itself');
|
||||||
|
t.equal(callBound('%Array.prototype%'), Array.prototype, '%Array.prototype% yields itself');
|
||||||
|
t.equal(callBound('Array.constructor'), Array.constructor, 'Array.constructor yields itself');
|
||||||
|
t.equal(callBound('%Array.constructor%'), Array.constructor, '%Array.constructor% yields itself');
|
||||||
|
|
||||||
|
// static function
|
||||||
|
t.equal(callBound('Date.parse'), Date.parse, 'Date.parse yields itself');
|
||||||
|
t.equal(callBound('%Date.parse%'), Date.parse, '%Date.parse% yields itself');
|
||||||
|
|
||||||
|
// prototype primitive
|
||||||
|
t.equal(callBound('Error.prototype.message'), Error.prototype.message, 'Error.prototype.message yields itself');
|
||||||
|
t.equal(callBound('%Error.prototype.message%'), Error.prototype.message, '%Error.prototype.message% yields itself');
|
||||||
|
|
||||||
|
// prototype function
|
||||||
|
t.notEqual(callBound('Object.prototype.toString'), Object.prototype.toString, 'Object.prototype.toString does not yield itself');
|
||||||
|
t.notEqual(callBound('%Object.prototype.toString%'), Object.prototype.toString, '%Object.prototype.toString% does not yield itself');
|
||||||
|
t.equal(callBound('Object.prototype.toString')(true), Object.prototype.toString.call(true), 'call-bound Object.prototype.toString calls into the original');
|
||||||
|
t.equal(callBound('%Object.prototype.toString%')(true), Object.prototype.toString.call(true), 'call-bound %Object.prototype.toString% calls into the original');
|
||||||
|
|
||||||
|
t['throws'](
|
||||||
|
function () { callBound('does not exist'); },
|
||||||
|
SyntaxError,
|
||||||
|
'nonexistent intrinsic throws'
|
||||||
|
);
|
||||||
|
t['throws'](
|
||||||
|
function () { callBound('does not exist', true); },
|
||||||
|
SyntaxError,
|
||||||
|
'allowMissing arg still throws for unknown intrinsic'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.test('real but absent intrinsic', { skip: typeof WeakRef !== 'undefined' }, function (st) {
|
||||||
|
st['throws'](
|
||||||
|
function () { callBound('WeakRef'); },
|
||||||
|
TypeError,
|
||||||
|
'real but absent intrinsic throws'
|
||||||
|
);
|
||||||
|
st.equal(callBound('WeakRef', true), undefined, 'allowMissing arg avoids exception');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@ljharb/tsconfig",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2021",
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"coverage",
|
||||||
|
],
|
||||||
|
}
|
||||||
+24
@@ -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.
|
||||||
+144
@@ -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
|
||||||
|
}
|
||||||
+55
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
@@ -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
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
0.5.4 / 2021-12-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: safe-buffer@5.2.1
|
||||||
|
|
||||||
|
0.5.3 / 2018-12-17
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Use `safe-buffer` for improved Buffer API
|
||||||
|
|
||||||
|
0.5.2 / 2016-12-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix `parse` to accept any linear whitespace character
|
||||||
|
|
||||||
|
0.5.1 / 2016-01-17
|
||||||
|
==================
|
||||||
|
|
||||||
|
* perf: enable strict mode
|
||||||
|
|
||||||
|
0.5.0 / 2014-10-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add `parse` function
|
||||||
|
|
||||||
|
0.4.0 / 2014-09-21
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Expand non-Unicode `filename` to the full ISO-8859-1 charset
|
||||||
|
|
||||||
|
0.3.0 / 2014-09-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add `fallback` option
|
||||||
|
* Add `type` option
|
||||||
|
|
||||||
|
0.2.0 / 2014-09-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Reduce ambiguity of file names with hex escape in buggy browsers
|
||||||
|
|
||||||
|
0.1.2 / 2014-09-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix periodic invalid Unicode filename header
|
||||||
|
|
||||||
|
0.1.1 / 2014-09-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix invalid characters appearing in `filename*` parameter
|
||||||
|
|
||||||
|
0.1.0 / 2014-09-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Make the `filename` argument optional
|
||||||
|
|
||||||
|
0.0.0 / 2014-09-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Initial release
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2017 Douglas Christopher Wilson
|
||||||
|
|
||||||
|
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.
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
# content-disposition
|
||||||
|
|
||||||
|
[![NPM Version][npm-image]][npm-url]
|
||||||
|
[![NPM Downloads][downloads-image]][downloads-url]
|
||||||
|
[![Node.js Version][node-version-image]][node-version-url]
|
||||||
|
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
||||||
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
Create and parse HTTP `Content-Disposition` header
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install content-disposition
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```js
|
||||||
|
var contentDisposition = require('content-disposition')
|
||||||
|
```
|
||||||
|
|
||||||
|
### contentDisposition(filename, options)
|
||||||
|
|
||||||
|
Create an attachment `Content-Disposition` header value using the given file name,
|
||||||
|
if supplied. The `filename` is optional and if no file name is desired, but you
|
||||||
|
want to specify `options`, set `filename` to `undefined`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
|
||||||
|
```
|
||||||
|
|
||||||
|
**note** HTTP headers are of the ISO-8859-1 character set. If you are writing this
|
||||||
|
header through a means different from `setHeader` in Node.js, you'll want to specify
|
||||||
|
the `'binary'` encoding in Node.js.
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
`contentDisposition` accepts these properties in the options object.
|
||||||
|
|
||||||
|
##### fallback
|
||||||
|
|
||||||
|
If the `filename` option is outside ISO-8859-1, then the file name is actually
|
||||||
|
stored in a supplemental field for clients that support Unicode file names and
|
||||||
|
a ISO-8859-1 version of the file name is automatically generated.
|
||||||
|
|
||||||
|
This specifies the ISO-8859-1 file name to override the automatic generation or
|
||||||
|
disables the generation all together, defaults to `true`.
|
||||||
|
|
||||||
|
- A string will specify the ISO-8859-1 file name to use in place of automatic
|
||||||
|
generation.
|
||||||
|
- `false` will disable including a ISO-8859-1 file name and only include the
|
||||||
|
Unicode version (unless the file name is already ISO-8859-1).
|
||||||
|
- `true` will enable automatic generation if the file name is outside ISO-8859-1.
|
||||||
|
|
||||||
|
If the `filename` option is ISO-8859-1 and this option is specified and has a
|
||||||
|
different value, then the `filename` option is encoded in the extended field
|
||||||
|
and this set as the fallback field, even though they are both ISO-8859-1.
|
||||||
|
|
||||||
|
##### type
|
||||||
|
|
||||||
|
Specifies the disposition type, defaults to `"attachment"`. This can also be
|
||||||
|
`"inline"`, or any other value (all values except inline are treated like
|
||||||
|
`attachment`, but can convey additional information if both parties agree to
|
||||||
|
it). The type is normalized to lower-case.
|
||||||
|
|
||||||
|
### contentDisposition.parse(string)
|
||||||
|
|
||||||
|
```js
|
||||||
|
var disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse a `Content-Disposition` header string. This automatically handles extended
|
||||||
|
("Unicode") parameters by decoding them and providing them under the standard
|
||||||
|
parameter name. This will return an object with the following properties (examples
|
||||||
|
are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`):
|
||||||
|
|
||||||
|
- `type`: The disposition type (always lower case). Example: `'attachment'`
|
||||||
|
|
||||||
|
- `parameters`: An object of the parameters in the disposition (name of parameter
|
||||||
|
always lower case and extended versions replace non-extended versions). Example:
|
||||||
|
`{filename: "€ rates.txt"}`
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Send a file for download
|
||||||
|
|
||||||
|
```js
|
||||||
|
var contentDisposition = require('content-disposition')
|
||||||
|
var destroy = require('destroy')
|
||||||
|
var fs = require('fs')
|
||||||
|
var http = require('http')
|
||||||
|
var onFinished = require('on-finished')
|
||||||
|
|
||||||
|
var filePath = '/path/to/public/plans.pdf'
|
||||||
|
|
||||||
|
http.createServer(function onRequest (req, res) {
|
||||||
|
// set headers
|
||||||
|
res.setHeader('Content-Type', 'application/pdf')
|
||||||
|
res.setHeader('Content-Disposition', contentDisposition(filePath))
|
||||||
|
|
||||||
|
// send file
|
||||||
|
var stream = fs.createReadStream(filePath)
|
||||||
|
stream.pipe(res)
|
||||||
|
onFinished(res, function () {
|
||||||
|
destroy(stream)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1][rfc-2616]
|
||||||
|
- [RFC 5987: Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters][rfc-5987]
|
||||||
|
- [RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)][rfc-6266]
|
||||||
|
- [Test Cases for HTTP Content-Disposition header field (RFC 6266) and the Encodings defined in RFCs 2047, 2231 and 5987][tc-2231]
|
||||||
|
|
||||||
|
[rfc-2616]: https://tools.ietf.org/html/rfc2616
|
||||||
|
[rfc-5987]: https://tools.ietf.org/html/rfc5987
|
||||||
|
[rfc-6266]: https://tools.ietf.org/html/rfc6266
|
||||||
|
[tc-2231]: http://greenbytes.de/tech/tc2231/
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/content-disposition.svg
|
||||||
|
[npm-url]: https://npmjs.org/package/content-disposition
|
||||||
|
[node-version-image]: https://img.shields.io/node/v/content-disposition.svg
|
||||||
|
[node-version-url]: https://nodejs.org/en/download
|
||||||
|
[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg
|
||||||
|
[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg
|
||||||
|
[downloads-url]: https://npmjs.org/package/content-disposition
|
||||||
|
[github-actions-ci-image]: https://img.shields.io/github/workflow/status/jshttp/content-disposition/ci/master?label=ci
|
||||||
|
[github-actions-ci-url]: https://github.com/jshttp/content-disposition?query=workflow%3Aci
|
||||||
+458
@@ -0,0 +1,458 @@
|
|||||||
|
/*!
|
||||||
|
* content-disposition
|
||||||
|
* Copyright(c) 2014-2017 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = contentDisposition
|
||||||
|
module.exports.parse = parse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var basename = require('path').basename
|
||||||
|
var Buffer = require('safe-buffer').Buffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match percent encoding escape.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
|
||||||
|
var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match non-latin1 characters.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match quoted-pair in RFC 2616
|
||||||
|
*
|
||||||
|
* quoted-pair = "\" CHAR
|
||||||
|
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match chars that must be quoted-pair in RFC 2616
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var QUOTE_REGEXP = /([\\"])/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp for various RFC 2616 grammar
|
||||||
|
*
|
||||||
|
* parameter = token "=" ( token | quoted-string )
|
||||||
|
* token = 1*<any CHAR except CTLs or separators>
|
||||||
|
* separators = "(" | ")" | "<" | ">" | "@"
|
||||||
|
* | "," | ";" | ":" | "\" | <">
|
||||||
|
* | "/" | "[" | "]" | "?" | "="
|
||||||
|
* | "{" | "}" | SP | HT
|
||||||
|
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||||
|
* qdtext = <any TEXT except <">>
|
||||||
|
* quoted-pair = "\" CHAR
|
||||||
|
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||||
|
* TEXT = <any OCTET except CTLs, but including LWS>
|
||||||
|
* LWS = [CRLF] 1*( SP | HT )
|
||||||
|
* CRLF = CR LF
|
||||||
|
* CR = <US-ASCII CR, carriage return (13)>
|
||||||
|
* LF = <US-ASCII LF, linefeed (10)>
|
||||||
|
* SP = <US-ASCII SP, space (32)>
|
||||||
|
* HT = <US-ASCII HT, horizontal-tab (9)>
|
||||||
|
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||||
|
* OCTET = <any 8-bit sequence of data>
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g // eslint-disable-line no-control-regex
|
||||||
|
var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
|
||||||
|
var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp for various RFC 5987 grammar
|
||||||
|
*
|
||||||
|
* ext-value = charset "'" [ language ] "'" value-chars
|
||||||
|
* charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||||
|
* mime-charset = 1*mime-charsetc
|
||||||
|
* mime-charsetc = ALPHA / DIGIT
|
||||||
|
* / "!" / "#" / "$" / "%" / "&"
|
||||||
|
* / "+" / "-" / "^" / "_" / "`"
|
||||||
|
* / "{" / "}" / "~"
|
||||||
|
* language = ( 2*3ALPHA [ extlang ] )
|
||||||
|
* / 4ALPHA
|
||||||
|
* / 5*8ALPHA
|
||||||
|
* extlang = *3( "-" 3ALPHA )
|
||||||
|
* value-chars = *( pct-encoded / attr-char )
|
||||||
|
* pct-encoded = "%" HEXDIG HEXDIG
|
||||||
|
* attr-char = ALPHA / DIGIT
|
||||||
|
* / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||||
|
* / "^" / "_" / "`" / "|" / "~"
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp for various RFC 6266 grammar
|
||||||
|
*
|
||||||
|
* disposition-type = "inline" | "attachment" | disp-ext-type
|
||||||
|
* disp-ext-type = token
|
||||||
|
* disposition-parm = filename-parm | disp-ext-parm
|
||||||
|
* filename-parm = "filename" "=" value
|
||||||
|
* | "filename*" "=" ext-value
|
||||||
|
* disp-ext-parm = token "=" value
|
||||||
|
* | ext-token "=" ext-value
|
||||||
|
* ext-token = <the characters in token, followed by "*">
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an attachment Content-Disposition header.
|
||||||
|
*
|
||||||
|
* @param {string} [filename]
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {string} [options.type=attachment]
|
||||||
|
* @param {string|boolean} [options.fallback=true]
|
||||||
|
* @return {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function contentDisposition (filename, options) {
|
||||||
|
var opts = options || {}
|
||||||
|
|
||||||
|
// get type
|
||||||
|
var type = opts.type || 'attachment'
|
||||||
|
|
||||||
|
// get parameters
|
||||||
|
var params = createparams(filename, opts.fallback)
|
||||||
|
|
||||||
|
// format into string
|
||||||
|
return format(new ContentDisposition(type, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create parameters object from filename and fallback.
|
||||||
|
*
|
||||||
|
* @param {string} [filename]
|
||||||
|
* @param {string|boolean} [fallback=true]
|
||||||
|
* @return {object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createparams (filename, fallback) {
|
||||||
|
if (filename === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var params = {}
|
||||||
|
|
||||||
|
if (typeof filename !== 'string') {
|
||||||
|
throw new TypeError('filename must be a string')
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback defaults to true
|
||||||
|
if (fallback === undefined) {
|
||||||
|
fallback = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
|
||||||
|
throw new TypeError('fallback must be a string or boolean')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
|
||||||
|
throw new TypeError('fallback must be ISO-8859-1 string')
|
||||||
|
}
|
||||||
|
|
||||||
|
// restrict to file base name
|
||||||
|
var name = basename(filename)
|
||||||
|
|
||||||
|
// determine if name is suitable for quoted string
|
||||||
|
var isQuotedString = TEXT_REGEXP.test(name)
|
||||||
|
|
||||||
|
// generate fallback name
|
||||||
|
var fallbackName = typeof fallback !== 'string'
|
||||||
|
? fallback && getlatin1(name)
|
||||||
|
: basename(fallback)
|
||||||
|
var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
|
||||||
|
|
||||||
|
// set extended filename parameter
|
||||||
|
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
||||||
|
params['filename*'] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// set filename parameter
|
||||||
|
if (isQuotedString || hasFallback) {
|
||||||
|
params.filename = hasFallback
|
||||||
|
? fallbackName
|
||||||
|
: name
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format object to Content-Disposition header.
|
||||||
|
*
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.type
|
||||||
|
* @param {object} [obj.parameters]
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function format (obj) {
|
||||||
|
var parameters = obj.parameters
|
||||||
|
var type = obj.type
|
||||||
|
|
||||||
|
if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
|
||||||
|
throw new TypeError('invalid type')
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with normalized type
|
||||||
|
var string = String(type).toLowerCase()
|
||||||
|
|
||||||
|
// append parameters
|
||||||
|
if (parameters && typeof parameters === 'object') {
|
||||||
|
var param
|
||||||
|
var params = Object.keys(parameters).sort()
|
||||||
|
|
||||||
|
for (var i = 0; i < params.length; i++) {
|
||||||
|
param = params[i]
|
||||||
|
|
||||||
|
var val = param.substr(-1) === '*'
|
||||||
|
? ustring(parameters[param])
|
||||||
|
: qstring(parameters[param])
|
||||||
|
|
||||||
|
string += '; ' + param + '=' + val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a RFC 5987 field value (gracefully).
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decodefield (str) {
|
||||||
|
var match = EXT_VALUE_REGEXP.exec(str)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new TypeError('invalid extended field value')
|
||||||
|
}
|
||||||
|
|
||||||
|
var charset = match[1].toLowerCase()
|
||||||
|
var encoded = match[2]
|
||||||
|
var value
|
||||||
|
|
||||||
|
// to binary string
|
||||||
|
var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
|
||||||
|
|
||||||
|
switch (charset) {
|
||||||
|
case 'iso-8859-1':
|
||||||
|
value = getlatin1(binary)
|
||||||
|
break
|
||||||
|
case 'utf-8':
|
||||||
|
value = Buffer.from(binary, 'binary').toString('utf8')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new TypeError('unsupported charset in extended field')
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ISO-8859-1 version of string.
|
||||||
|
*
|
||||||
|
* @param {string} val
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getlatin1 (val) {
|
||||||
|
// simple Unicode -> ISO-8859-1 transformation
|
||||||
|
return String(val).replace(NON_LATIN1_REGEXP, '?')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Content-Disposition header string.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @return {object}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse (string) {
|
||||||
|
if (!string || typeof string !== 'string') {
|
||||||
|
throw new TypeError('argument string is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = DISPOSITION_TYPE_REGEXP.exec(string)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new TypeError('invalid type format')
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize type
|
||||||
|
var index = match[0].length
|
||||||
|
var type = match[1].toLowerCase()
|
||||||
|
|
||||||
|
var key
|
||||||
|
var names = []
|
||||||
|
var params = {}
|
||||||
|
var value
|
||||||
|
|
||||||
|
// calculate index to start at
|
||||||
|
index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === ';'
|
||||||
|
? index - 1
|
||||||
|
: index
|
||||||
|
|
||||||
|
// match parameters
|
||||||
|
while ((match = PARAM_REGEXP.exec(string))) {
|
||||||
|
if (match.index !== index) {
|
||||||
|
throw new TypeError('invalid parameter format')
|
||||||
|
}
|
||||||
|
|
||||||
|
index += match[0].length
|
||||||
|
key = match[1].toLowerCase()
|
||||||
|
value = match[2]
|
||||||
|
|
||||||
|
if (names.indexOf(key) !== -1) {
|
||||||
|
throw new TypeError('invalid duplicate parameter')
|
||||||
|
}
|
||||||
|
|
||||||
|
names.push(key)
|
||||||
|
|
||||||
|
if (key.indexOf('*') + 1 === key.length) {
|
||||||
|
// decode extended value
|
||||||
|
key = key.slice(0, -1)
|
||||||
|
value = decodefield(value)
|
||||||
|
|
||||||
|
// overwrite existing value
|
||||||
|
params[key] = value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof params[key] === 'string') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value[0] === '"') {
|
||||||
|
// remove quotes and escapes
|
||||||
|
value = value
|
||||||
|
.substr(1, value.length - 2)
|
||||||
|
.replace(QESC_REGEXP, '$1')
|
||||||
|
}
|
||||||
|
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index !== -1 && index !== string.length) {
|
||||||
|
throw new TypeError('invalid parameter format')
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ContentDisposition(type, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percent decode a single character.
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @param {string} hex
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function pdecode (str, hex) {
|
||||||
|
return String.fromCharCode(parseInt(hex, 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percent encode a single character.
|
||||||
|
*
|
||||||
|
* @param {string} char
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function pencode (char) {
|
||||||
|
return '%' + String(char)
|
||||||
|
.charCodeAt(0)
|
||||||
|
.toString(16)
|
||||||
|
.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quote a string for HTTP.
|
||||||
|
*
|
||||||
|
* @param {string} val
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function qstring (val) {
|
||||||
|
var str = String(val)
|
||||||
|
|
||||||
|
return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a Unicode string for HTTP (RFC 5987).
|
||||||
|
*
|
||||||
|
* @param {string} val
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ustring (val) {
|
||||||
|
var str = String(val)
|
||||||
|
|
||||||
|
// percent encode as UTF-8
|
||||||
|
var encoded = encodeURIComponent(str)
|
||||||
|
.replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)
|
||||||
|
|
||||||
|
return 'UTF-8\'\'' + encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for parsed Content-Disposition header for v8 optimization
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {string} type
|
||||||
|
* @param {object} parameters
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ContentDisposition (type, parameters) {
|
||||||
|
this.type = type
|
||||||
|
this.parameters = parameters
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Feross Aboukhadijeh
|
||||||
|
|
||||||
|
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.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user