chore: add rate limiting
This commit is contained in:
parent
10d2927b31
commit
305ae6a35e
303
app.js
303
app.js
@ -1,4 +1,5 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const API = require('./src/js/index.js');
|
const API = require('./src/js/index.js');
|
||||||
@ -11,7 +12,7 @@ require('./src/js/utils.js');
|
|||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(bodyParser.json({ limit: '10mb' }));
|
// app.use(bodyParser.json({ limit: '10mb' }));
|
||||||
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
||||||
app.use(express.static(__dirname));
|
app.use(express.static(__dirname));
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
@ -29,6 +30,153 @@ app.use(
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let DEFAULT_SSO_TOKEN = '';
|
||||||
|
|
||||||
|
// Try to read the token from a file
|
||||||
|
try {
|
||||||
|
const tokenPath = path.join(__dirname, 'token.txt');
|
||||||
|
if (fs.existsSync(tokenPath)) {
|
||||||
|
DEFAULT_SSO_TOKEN = fs.readFileSync(tokenPath, 'utf8').trim();
|
||||||
|
logger.info('Default SSO token loaded successfully from token.txt');
|
||||||
|
} else {
|
||||||
|
logger.warn('token.txt not found, demo mode may not function correctly');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error loading token file:', { error: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => demoModeRequestTracker.cleanupExpiredEntries(), 3600000); // Clean up every hour
|
||||||
|
|
||||||
|
// Configure rate limiting for demo mode
|
||||||
|
const demoModeRequestTracker = {
|
||||||
|
requests: new Map(),
|
||||||
|
maxRequestsPerHour: 5, // Adjust as needed
|
||||||
|
resetInterval: 60 * 60 * 1000, // 1 hour in milliseconds
|
||||||
|
|
||||||
|
isLimited: function (ip) {
|
||||||
|
const now = Date.now();
|
||||||
|
const userRequests = this.requests.get(ip) || {
|
||||||
|
count: 0,
|
||||||
|
resetTime: now + this.resetInterval,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset counter if the reset time has passed
|
||||||
|
if (now > userRequests.resetTime) {
|
||||||
|
userRequests.count = 0;
|
||||||
|
userRequests.resetTime = now + this.resetInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRequests.count >= this.maxRequestsPerHour;
|
||||||
|
},
|
||||||
|
|
||||||
|
incrementCounter: function (ip) {
|
||||||
|
const now = Date.now();
|
||||||
|
const userRequests = this.requests.get(ip) || {
|
||||||
|
count: 0,
|
||||||
|
resetTime: now + this.resetInterval,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset counter if the reset time has passed
|
||||||
|
if (now > userRequests.resetTime) {
|
||||||
|
userRequests.count = 1; // Start with 1 for this request
|
||||||
|
userRequests.resetTime = now + this.resetInterval;
|
||||||
|
} else {
|
||||||
|
userRequests.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requests.set(ip, userRequests);
|
||||||
|
return userRequests.count;
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanupExpiredEntries: function () {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [ip, data] of this.requests.entries()) {
|
||||||
|
if (now > data.resetTime) {
|
||||||
|
// Either remove entries completely or reset them to 0
|
||||||
|
this.requests.delete(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Demo mode middleware
|
||||||
|
const demoModeMiddleware = (req, res, next) => {
|
||||||
|
// Skip non-API routes
|
||||||
|
if (!req.path.startsWith('/api/')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get client IP for tracking
|
||||||
|
// const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress;
|
||||||
|
const clientIP =
|
||||||
|
req.headers['cf-connecting-ip'] ||
|
||||||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
|
|
||||||
|
// Check if demo mode is active
|
||||||
|
if (DEFAULT_SSO_TOKEN) {
|
||||||
|
// For API endpoints, check rate limit in demo mode
|
||||||
|
if (demoModeRequestTracker.isLimited(clientIP)) {
|
||||||
|
const userRequests = demoModeRequestTracker.requests.get(clientIP);
|
||||||
|
const resetTimeMs = userRequests.resetTime - Date.now();
|
||||||
|
|
||||||
|
// Format time as HH:MM:SS
|
||||||
|
const hours = Math.floor(resetTimeMs / (60 * 60 * 1000));
|
||||||
|
const minutes = Math.floor(
|
||||||
|
(resetTimeMs % (60 * 60 * 1000)) / (60 * 1000)
|
||||||
|
);
|
||||||
|
const seconds = Math.floor((resetTimeMs % (60 * 1000)) / 1000);
|
||||||
|
const timeFormat = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
logger.warn(`Demo mode rate limit exceeded for IP: ${clientIP}`);
|
||||||
|
return res.status(429).json({
|
||||||
|
status: 'error',
|
||||||
|
message: `Please try again in ${timeFormat} or use your own SSO token.`,
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the counter
|
||||||
|
// const count = demoModeRequestTracker.incrementCounter(clientIP);
|
||||||
|
// logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
function incrementDemoCounter(req, ssoToken) {
|
||||||
|
if (ssoToken === DEFAULT_SSO_TOKEN) {
|
||||||
|
const clientIP =
|
||||||
|
req.headers['cf-connecting-ip'] ||
|
||||||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
|
|
||||||
|
const count = demoModeRequestTracker.incrementCounter(clientIP);
|
||||||
|
logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(demoModeMiddleware);
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (req.query.demo === 'true' || req.query.demo === '1') {
|
||||||
|
// Enable demo mode for this session if it's not already enabled
|
||||||
|
if (!req.session) {
|
||||||
|
req.session = {};
|
||||||
|
}
|
||||||
|
req.session.forceDemoMode = true;
|
||||||
|
logger.info('Demo mode enabled via URL parameter');
|
||||||
|
} else if (req.query.demo === 'false' || req.query.demo === '0') {
|
||||||
|
// Disable demo mode for this session
|
||||||
|
if (req.session) {
|
||||||
|
req.session.forceDemoMode = false;
|
||||||
|
}
|
||||||
|
logger.info('Demo mode disabled via URL parameter');
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize key replacements
|
// Initialize key replacements
|
||||||
let keyReplacements = {};
|
let keyReplacements = {};
|
||||||
|
|
||||||
@ -224,15 +372,19 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
let { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys } =
|
||||||
username,
|
req.body;
|
||||||
ssoToken,
|
|
||||||
platform,
|
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
||||||
game,
|
ssoToken = DEFAULT_SSO_TOKEN;
|
||||||
apiCall,
|
logger.info('Using default SSO token for demo mode');
|
||||||
sanitize,
|
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
||||||
replaceKeys,
|
return res.status(200).json({
|
||||||
} = req.body;
|
status: 'error',
|
||||||
|
message: 'SSO Token is required as demo mode is not active',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@ -247,10 +399,6 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("====================="); */
|
logger.debug("====================="); */
|
||||||
|
|
||||||
if (!ssoToken) {
|
|
||||||
return res.status(400).json({ error: 'SSO Token is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// For mapList, username is not required
|
// For mapList, username is not required
|
||||||
if (apiCall !== 'mapList' && !username) {
|
if (apiCall !== 'mapList' && !username) {
|
||||||
return res.status(400).json({ error: 'Username is required' });
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
@ -435,6 +583,8 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
|
incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
||||||
@ -475,7 +625,7 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { username, ssoToken, platform, game, sanitize, replaceKeys } =
|
let { username, ssoToken, platform, game, sanitize, replaceKeys } =
|
||||||
req.body;
|
req.body;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -490,10 +640,19 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("========================"); */
|
logger.debug("========================"); */
|
||||||
|
|
||||||
if (!username || !ssoToken) {
|
if (!username) {
|
||||||
return res
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
.status(400)
|
}
|
||||||
.json({ error: 'Username and SSO Token are required' });
|
|
||||||
|
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
||||||
|
ssoToken = DEFAULT_SSO_TOKEN;
|
||||||
|
logger.info('Using default SSO token for demo mode');
|
||||||
|
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 'error',
|
||||||
|
message: 'SSO Token is required as demo mode is not active',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -584,6 +743,8 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
|
incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
||||||
@ -621,9 +782,7 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { matchId, ssoToken, platform, game, sanitize, replaceKeys } =
|
let { matchId, ssoToken, platform, game, sanitize, replaceKeys } = req.body;
|
||||||
req.body;
|
|
||||||
const mode = 'mp';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@ -637,10 +796,19 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("=========================="); */
|
logger.debug("=========================="); */
|
||||||
|
|
||||||
if (!matchId || !ssoToken) {
|
if (!matchId) {
|
||||||
return res
|
return res.status(400).json({ error: 'Match ID is required' });
|
||||||
.status(400)
|
}
|
||||||
.json({ error: 'Match ID and SSO Token are required' });
|
|
||||||
|
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
||||||
|
ssoToken = DEFAULT_SSO_TOKEN;
|
||||||
|
logger.info('Using default SSO token for demo mode');
|
||||||
|
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 'error',
|
||||||
|
message: 'SSO Token is required as demo mode is not active',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -717,6 +885,8 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
|
incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
||||||
@ -754,7 +924,7 @@ app.post('/api/user', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { username, ssoToken, platform, userCall, sanitize, replaceKeys } =
|
let { username, ssoToken, platform, userCall, sanitize, replaceKeys } =
|
||||||
req.body;
|
req.body;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -769,10 +939,6 @@ app.post('/api/user', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("========================="); */
|
logger.debug("========================="); */
|
||||||
|
|
||||||
if (!ssoToken) {
|
|
||||||
return res.status(400).json({ error: 'SSO Token is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// For eventFeed and identities, username is not required
|
// For eventFeed and identities, username is not required
|
||||||
if (
|
if (
|
||||||
!username &&
|
!username &&
|
||||||
@ -785,6 +951,17 @@ app.post('/api/user', async (req, res) => {
|
|||||||
.json({ error: 'Username is required for this API call' });
|
.json({ error: 'Username is required for this API call' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
||||||
|
ssoToken = DEFAULT_SSO_TOKEN;
|
||||||
|
logger.info('Using default SSO token for demo mode');
|
||||||
|
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 'error',
|
||||||
|
message: 'SSO Token is required as demo mode is not active',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureLogin(ssoToken);
|
await ensureLogin(ssoToken);
|
||||||
} catch (loginError) {
|
} catch (loginError) {
|
||||||
@ -850,6 +1027,8 @@ app.post('/api/user', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
|
incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
||||||
@ -886,23 +1065,33 @@ app.post('/api/search', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { username, ssoToken, platform, sanitize, replaceKeys } = req.body;
|
let { username, ssoToken, platform, sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Request details - Username to search: ${username}, Platform: ${platform}`
|
`Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}`
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.debug("=== SEARCH REQUEST ===");
|
logger.debug("=== MATCH INFO REQUEST ===");
|
||||||
logger.debug(`Search Term: ${username}`);
|
logger.debug(`Match ID: ${matchId}`);
|
||||||
logger.debug(`Platform: ${platform}`);
|
logger.debug(`Platform: ${platform}`);
|
||||||
|
logger.debug(`Game: ${game}`);
|
||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("======================"); */
|
logger.debug("=========================="); */
|
||||||
|
|
||||||
if (!username || !ssoToken) {
|
if (!username) {
|
||||||
return res
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
.status(400)
|
}
|
||||||
.json({ error: 'Username and SSO Token are required' });
|
|
||||||
|
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
||||||
|
ssoToken = DEFAULT_SSO_TOKEN;
|
||||||
|
logger.info('Using default SSO token for demo mode');
|
||||||
|
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 'error',
|
||||||
|
message: 'SSO Token is required as demo mode is not active',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -935,6 +1124,8 @@ app.post('/api/search', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
|
incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
data: processJsonOutput(data, { sanitize, replaceKeys }),
|
||||||
@ -957,7 +1148,10 @@ app.post('/api/search', async (req, res) => {
|
|||||||
// Improved logging endpoint
|
// Improved logging endpoint
|
||||||
app.post('/api/log', (req, res) => {
|
app.post('/api/log', (req, res) => {
|
||||||
const clientIP =
|
const clientIP =
|
||||||
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress;
|
req.headers['cf-connecting-ip'] ||
|
||||||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
const referer = req.headers['referer'];
|
const referer = req.headers['referer'];
|
||||||
const origin = req.headers['origin'];
|
const origin = req.headers['origin'];
|
||||||
@ -1028,9 +1222,32 @@ function storeLogInDatabase(logData) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Basic health check endpoint
|
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: global.Utils.toIsoString(new Date()) });
|
// Check if demo mode is forced via URL parameter
|
||||||
|
const forceDemoMode = req.session && req.session.forceDemoMode === true;
|
||||||
|
|
||||||
|
const isDemoMode = !!DEFAULT_SSO_TOKEN || forceDemoMode;
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
demoMode: isDemoMode,
|
||||||
|
requestsRemaining:
|
||||||
|
isDemoMode ?
|
||||||
|
demoModeRequestTracker.maxRequestsPerHour -
|
||||||
|
(demoModeRequestTracker.requests.get(req.ip)?.count || 0)
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/demo', (req, res) => {
|
||||||
|
// Set a cookie or session variable to enable demo mode
|
||||||
|
if (!req.session) {
|
||||||
|
req.session = {};
|
||||||
|
}
|
||||||
|
req.session.forceDemoMode = true;
|
||||||
|
|
||||||
|
// Redirect to the main app
|
||||||
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve the main HTML file
|
// Serve the main HTML file
|
||||||
|
130
node_modules/.package-lock.json
generated
vendored
130
node_modules/.package-lock.json
generated
vendored
@ -62,6 +62,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@ -508,6 +517,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-of-duty-api": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-of-duty-api/-/call-of-duty-api-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-f5DJ6gQru6f406QVBZkkXOv0gUzFu0hykdkyKRa2Am6iWwGRVzcBK7rq+xHpNI6oTq3EFfw0T70kb38rZaezNA==",
|
||||||
|
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"undici": "^5.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Lierrmm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-of-duty-api/node_modules/undici": {
|
||||||
|
"version": "5.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||||
|
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/camel-case": {
|
"node_modules/camel-case": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
||||||
@ -987,6 +1023,55 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-rate-limit": {
|
||||||
|
"version": "7.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
|
||||||
|
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/express-rate-limit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.7",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.0.2",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"uid-safe": "~2.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/body-parser": {
|
"node_modules/express/node_modules/body-parser": {
|
||||||
"version": "1.20.3",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
@ -1869,6 +1954,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/on-headers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -2045,6 +2139,21 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||||
|
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
@ -2116,6 +2225,15 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/random-bytes": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@ -2849,6 +2967,18 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uid-safe": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "7.6.0",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.6.0.tgz",
|
||||||
|
213
node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js
generated
vendored
Normal file
213
node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const WritableStream = require('node:stream').Writable
|
||||||
|
const inherits = require('node:util').inherits
|
||||||
|
|
||||||
|
const StreamSearch = require('../../streamsearch/sbmh')
|
||||||
|
|
||||||
|
const PartStream = require('./PartStream')
|
||||||
|
const HeaderParser = require('./HeaderParser')
|
||||||
|
|
||||||
|
const DASH = 45
|
||||||
|
const B_ONEDASH = Buffer.from('-')
|
||||||
|
const B_CRLF = Buffer.from('\r\n')
|
||||||
|
const EMPTY_FN = function () {}
|
||||||
|
|
||||||
|
function Dicer (cfg) {
|
||||||
|
if (!(this instanceof Dicer)) { return new Dicer(cfg) }
|
||||||
|
WritableStream.call(this, cfg)
|
||||||
|
|
||||||
|
if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) { throw new TypeError('Boundary required') }
|
||||||
|
|
||||||
|
if (typeof cfg.boundary === 'string') { this.setBoundary(cfg.boundary) } else { this._bparser = undefined }
|
||||||
|
|
||||||
|
this._headerFirst = cfg.headerFirst
|
||||||
|
|
||||||
|
this._dashes = 0
|
||||||
|
this._parts = 0
|
||||||
|
this._finished = false
|
||||||
|
this._realFinish = false
|
||||||
|
this._isPreamble = true
|
||||||
|
this._justMatched = false
|
||||||
|
this._firstWrite = true
|
||||||
|
this._inHeader = true
|
||||||
|
this._part = undefined
|
||||||
|
this._cb = undefined
|
||||||
|
this._ignoreData = false
|
||||||
|
this._partOpts = { highWaterMark: cfg.partHwm }
|
||||||
|
this._pause = false
|
||||||
|
|
||||||
|
const self = this
|
||||||
|
this._hparser = new HeaderParser(cfg)
|
||||||
|
this._hparser.on('header', function (header) {
|
||||||
|
self._inHeader = false
|
||||||
|
self._part.emit('header', header)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
inherits(Dicer, WritableStream)
|
||||||
|
|
||||||
|
Dicer.prototype.emit = function (ev) {
|
||||||
|
if (ev === 'finish' && !this._realFinish) {
|
||||||
|
if (!this._finished) {
|
||||||
|
const self = this
|
||||||
|
process.nextTick(function () {
|
||||||
|
self.emit('error', new Error('Unexpected end of multipart data'))
|
||||||
|
if (self._part && !self._ignoreData) {
|
||||||
|
const type = (self._isPreamble ? 'Preamble' : 'Part')
|
||||||
|
self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data'))
|
||||||
|
self._part.push(null)
|
||||||
|
process.nextTick(function () {
|
||||||
|
self._realFinish = true
|
||||||
|
self.emit('finish')
|
||||||
|
self._realFinish = false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self._realFinish = true
|
||||||
|
self.emit('finish')
|
||||||
|
self._realFinish = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else { WritableStream.prototype.emit.apply(this, arguments) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype._write = function (data, encoding, cb) {
|
||||||
|
// ignore unexpected data (e.g. extra trailer data after finished)
|
||||||
|
if (!this._hparser && !this._bparser) { return cb() }
|
||||||
|
|
||||||
|
if (this._headerFirst && this._isPreamble) {
|
||||||
|
if (!this._part) {
|
||||||
|
this._part = new PartStream(this._partOpts)
|
||||||
|
if (this.listenerCount('preamble') !== 0) { this.emit('preamble', this._part) } else { this._ignore() }
|
||||||
|
}
|
||||||
|
const r = this._hparser.push(data)
|
||||||
|
if (!this._inHeader && r !== undefined && r < data.length) { data = data.slice(r) } else { return cb() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// allows for "easier" testing
|
||||||
|
if (this._firstWrite) {
|
||||||
|
this._bparser.push(B_CRLF)
|
||||||
|
this._firstWrite = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bparser.push(data)
|
||||||
|
|
||||||
|
if (this._pause) { this._cb = cb } else { cb() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype.reset = function () {
|
||||||
|
this._part = undefined
|
||||||
|
this._bparser = undefined
|
||||||
|
this._hparser = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype.setBoundary = function (boundary) {
|
||||||
|
const self = this
|
||||||
|
this._bparser = new StreamSearch('\r\n--' + boundary)
|
||||||
|
this._bparser.on('info', function (isMatch, data, start, end) {
|
||||||
|
self._oninfo(isMatch, data, start, end)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype._ignore = function () {
|
||||||
|
if (this._part && !this._ignoreData) {
|
||||||
|
this._ignoreData = true
|
||||||
|
this._part.on('error', EMPTY_FN)
|
||||||
|
// we must perform some kind of read on the stream even though we are
|
||||||
|
// ignoring the data, otherwise node's Readable stream will not emit 'end'
|
||||||
|
// after pushing null to the stream
|
||||||
|
this._part.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype._oninfo = function (isMatch, data, start, end) {
|
||||||
|
let buf; const self = this; let i = 0; let r; let shouldWriteMore = true
|
||||||
|
|
||||||
|
if (!this._part && this._justMatched && data) {
|
||||||
|
while (this._dashes < 2 && (start + i) < end) {
|
||||||
|
if (data[start + i] === DASH) {
|
||||||
|
++i
|
||||||
|
++this._dashes
|
||||||
|
} else {
|
||||||
|
if (this._dashes) { buf = B_ONEDASH }
|
||||||
|
this._dashes = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._dashes === 2) {
|
||||||
|
if ((start + i) < end && this.listenerCount('trailer') !== 0) { this.emit('trailer', data.slice(start + i, end)) }
|
||||||
|
this.reset()
|
||||||
|
this._finished = true
|
||||||
|
// no more parts will be added
|
||||||
|
if (self._parts === 0) {
|
||||||
|
self._realFinish = true
|
||||||
|
self.emit('finish')
|
||||||
|
self._realFinish = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._dashes) { return }
|
||||||
|
}
|
||||||
|
if (this._justMatched) { this._justMatched = false }
|
||||||
|
if (!this._part) {
|
||||||
|
this._part = new PartStream(this._partOpts)
|
||||||
|
this._part._read = function (n) {
|
||||||
|
self._unpause()
|
||||||
|
}
|
||||||
|
if (this._isPreamble && this.listenerCount('preamble') !== 0) {
|
||||||
|
this.emit('preamble', this._part)
|
||||||
|
} else if (this._isPreamble !== true && this.listenerCount('part') !== 0) {
|
||||||
|
this.emit('part', this._part)
|
||||||
|
} else {
|
||||||
|
this._ignore()
|
||||||
|
}
|
||||||
|
if (!this._isPreamble) { this._inHeader = true }
|
||||||
|
}
|
||||||
|
if (data && start < end && !this._ignoreData) {
|
||||||
|
if (this._isPreamble || !this._inHeader) {
|
||||||
|
if (buf) { shouldWriteMore = this._part.push(buf) }
|
||||||
|
shouldWriteMore = this._part.push(data.slice(start, end))
|
||||||
|
if (!shouldWriteMore) { this._pause = true }
|
||||||
|
} else if (!this._isPreamble && this._inHeader) {
|
||||||
|
if (buf) { this._hparser.push(buf) }
|
||||||
|
r = this._hparser.push(data.slice(start, end))
|
||||||
|
if (!this._inHeader && r !== undefined && r < end) { this._oninfo(false, data, start + r, end) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMatch) {
|
||||||
|
this._hparser.reset()
|
||||||
|
if (this._isPreamble) { this._isPreamble = false } else {
|
||||||
|
if (start !== end) {
|
||||||
|
++this._parts
|
||||||
|
this._part.on('end', function () {
|
||||||
|
if (--self._parts === 0) {
|
||||||
|
if (self._finished) {
|
||||||
|
self._realFinish = true
|
||||||
|
self.emit('finish')
|
||||||
|
self._realFinish = false
|
||||||
|
} else {
|
||||||
|
self._unpause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._part.push(null)
|
||||||
|
this._part = undefined
|
||||||
|
this._ignoreData = false
|
||||||
|
this._justMatched = true
|
||||||
|
this._dashes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dicer.prototype._unpause = function () {
|
||||||
|
if (!this._pause) { return }
|
||||||
|
|
||||||
|
this._pause = false
|
||||||
|
if (this._cb) {
|
||||||
|
const cb = this._cb
|
||||||
|
this._cb = undefined
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Dicer
|
100
node_modules/@fastify/busboy/deps/dicer/lib/HeaderParser.js
generated
vendored
Normal file
100
node_modules/@fastify/busboy/deps/dicer/lib/HeaderParser.js
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const EventEmitter = require('node:events').EventEmitter
|
||||||
|
const inherits = require('node:util').inherits
|
||||||
|
const getLimit = require('../../../lib/utils/getLimit')
|
||||||
|
|
||||||
|
const StreamSearch = require('../../streamsearch/sbmh')
|
||||||
|
|
||||||
|
const B_DCRLF = Buffer.from('\r\n\r\n')
|
||||||
|
const RE_CRLF = /\r\n/g
|
||||||
|
const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/ // eslint-disable-line no-control-regex
|
||||||
|
|
||||||
|
function HeaderParser (cfg) {
|
||||||
|
EventEmitter.call(this)
|
||||||
|
|
||||||
|
cfg = cfg || {}
|
||||||
|
const self = this
|
||||||
|
this.nread = 0
|
||||||
|
this.maxed = false
|
||||||
|
this.npairs = 0
|
||||||
|
this.maxHeaderPairs = getLimit(cfg, 'maxHeaderPairs', 2000)
|
||||||
|
this.maxHeaderSize = getLimit(cfg, 'maxHeaderSize', 80 * 1024)
|
||||||
|
this.buffer = ''
|
||||||
|
this.header = {}
|
||||||
|
this.finished = false
|
||||||
|
this.ss = new StreamSearch(B_DCRLF)
|
||||||
|
this.ss.on('info', function (isMatch, data, start, end) {
|
||||||
|
if (data && !self.maxed) {
|
||||||
|
if (self.nread + end - start >= self.maxHeaderSize) {
|
||||||
|
end = self.maxHeaderSize - self.nread + start
|
||||||
|
self.nread = self.maxHeaderSize
|
||||||
|
self.maxed = true
|
||||||
|
} else { self.nread += (end - start) }
|
||||||
|
|
||||||
|
self.buffer += data.toString('binary', start, end)
|
||||||
|
}
|
||||||
|
if (isMatch) { self._finish() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
inherits(HeaderParser, EventEmitter)
|
||||||
|
|
||||||
|
HeaderParser.prototype.push = function (data) {
|
||||||
|
const r = this.ss.push(data)
|
||||||
|
if (this.finished) { return r }
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderParser.prototype.reset = function () {
|
||||||
|
this.finished = false
|
||||||
|
this.buffer = ''
|
||||||
|
this.header = {}
|
||||||
|
this.ss.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderParser.prototype._finish = function () {
|
||||||
|
if (this.buffer) { this._parseHeader() }
|
||||||
|
this.ss.matches = this.ss.maxMatches
|
||||||
|
const header = this.header
|
||||||
|
this.header = {}
|
||||||
|
this.buffer = ''
|
||||||
|
this.finished = true
|
||||||
|
this.nread = this.npairs = 0
|
||||||
|
this.maxed = false
|
||||||
|
this.emit('header', header)
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderParser.prototype._parseHeader = function () {
|
||||||
|
if (this.npairs === this.maxHeaderPairs) { return }
|
||||||
|
|
||||||
|
const lines = this.buffer.split(RE_CRLF)
|
||||||
|
const len = lines.length
|
||||||
|
let m, h
|
||||||
|
|
||||||
|
for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
|
||||||
|
if (lines[i].length === 0) { continue }
|
||||||
|
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
|
||||||
|
// folded header content
|
||||||
|
// RFC2822 says to just remove the CRLF and not the whitespace following
|
||||||
|
// it, so we follow the RFC and include the leading whitespace ...
|
||||||
|
if (h) {
|
||||||
|
this.header[h][this.header[h].length - 1] += lines[i]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const posColon = lines[i].indexOf(':')
|
||||||
|
if (
|
||||||
|
posColon === -1 ||
|
||||||
|
posColon === 0
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m = RE_HDR.exec(lines[i])
|
||||||
|
h = m[1].toLowerCase()
|
||||||
|
this.header[h] = this.header[h] || []
|
||||||
|
this.header[h].push((m[2] || ''))
|
||||||
|
if (++this.npairs === this.maxHeaderPairs) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HeaderParser
|
13
node_modules/@fastify/busboy/deps/dicer/lib/PartStream.js
generated
vendored
Normal file
13
node_modules/@fastify/busboy/deps/dicer/lib/PartStream.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const inherits = require('node:util').inherits
|
||||||
|
const ReadableStream = require('node:stream').Readable
|
||||||
|
|
||||||
|
function PartStream (opts) {
|
||||||
|
ReadableStream.call(this, opts)
|
||||||
|
}
|
||||||
|
inherits(PartStream, ReadableStream)
|
||||||
|
|
||||||
|
PartStream.prototype._read = function (n) {}
|
||||||
|
|
||||||
|
module.exports = PartStream
|
164
node_modules/@fastify/busboy/deps/dicer/lib/dicer.d.ts
generated
vendored
Normal file
164
node_modules/@fastify/busboy/deps/dicer/lib/dicer.d.ts
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Type definitions for dicer 0.2
|
||||||
|
// Project: https://github.com/mscdex/dicer
|
||||||
|
// Definitions by: BendingBender <https://github.com/BendingBender>
|
||||||
|
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||||
|
// TypeScript Version: 2.2
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import stream = require("stream");
|
||||||
|
|
||||||
|
// tslint:disable:unified-signatures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very fast streaming multipart parser for node.js.
|
||||||
|
* Dicer is a WritableStream
|
||||||
|
*
|
||||||
|
* Dicer (special) events:
|
||||||
|
* - on('finish', ()) - Emitted when all parts have been parsed and the Dicer instance has been ended.
|
||||||
|
* - on('part', (stream: PartStream)) - Emitted when a new part has been found.
|
||||||
|
* - on('preamble', (stream: PartStream)) - Emitted for preamble if you should happen to need it (can usually be ignored).
|
||||||
|
* - on('trailer', (data: Buffer)) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too).
|
||||||
|
*/
|
||||||
|
export class Dicer extends stream.Writable {
|
||||||
|
/**
|
||||||
|
* Creates and returns a new Dicer instance with the following valid config settings:
|
||||||
|
*
|
||||||
|
* @param config The configuration to use
|
||||||
|
*/
|
||||||
|
constructor(config: Dicer.Config);
|
||||||
|
/**
|
||||||
|
* Sets the boundary to use for parsing and performs some initialization needed for parsing.
|
||||||
|
* You should only need to use this if you set headerFirst to true in the constructor and are parsing the boundary from the preamble header.
|
||||||
|
*
|
||||||
|
* @param boundary The boundary to use
|
||||||
|
*/
|
||||||
|
setBoundary(boundary: string): void;
|
||||||
|
addListener(event: "finish", listener: () => void): this;
|
||||||
|
addListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
addListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
addListener(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
addListener(event: "close", listener: () => void): this;
|
||||||
|
addListener(event: "drain", listener: () => void): this;
|
||||||
|
addListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
addListener(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
addListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
addListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
on(event: "finish", listener: () => void): this;
|
||||||
|
on(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
on(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
on(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
on(event: "close", listener: () => void): this;
|
||||||
|
on(event: "drain", listener: () => void): this;
|
||||||
|
on(event: "error", listener: (err: Error) => void): this;
|
||||||
|
on(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
on(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
on(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
once(event: "finish", listener: () => void): this;
|
||||||
|
once(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
once(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
once(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
once(event: "close", listener: () => void): this;
|
||||||
|
once(event: "drain", listener: () => void): this;
|
||||||
|
once(event: "error", listener: (err: Error) => void): this;
|
||||||
|
once(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
once(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
once(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
prependListener(event: "finish", listener: () => void): this;
|
||||||
|
prependListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
prependListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
prependListener(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
prependListener(event: "close", listener: () => void): this;
|
||||||
|
prependListener(event: "drain", listener: () => void): this;
|
||||||
|
prependListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
prependListener(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
prependListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
prependListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
prependOnceListener(event: "finish", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
prependOnceListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
prependOnceListener(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
prependOnceListener(event: "close", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "drain", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
prependOnceListener(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
prependOnceListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
removeListener(event: "finish", listener: () => void): this;
|
||||||
|
removeListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
removeListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
|
||||||
|
removeListener(event: "trailer", listener: (data: Buffer) => void): this;
|
||||||
|
removeListener(event: "close", listener: () => void): this;
|
||||||
|
removeListener(event: "drain", listener: () => void): this;
|
||||||
|
removeListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
removeListener(event: "pipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
removeListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
|
||||||
|
removeListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace Dicer {
|
||||||
|
interface Config {
|
||||||
|
/**
|
||||||
|
* This is the boundary used to detect the beginning of a new part.
|
||||||
|
*/
|
||||||
|
boundary?: string | undefined;
|
||||||
|
/**
|
||||||
|
* If true, preamble header parsing will be performed first.
|
||||||
|
*/
|
||||||
|
headerFirst?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* The maximum number of header key=>value pairs to parse Default: 2000 (same as node's http).
|
||||||
|
*/
|
||||||
|
maxHeaderPairs?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PartStream is a _ReadableStream_
|
||||||
|
*
|
||||||
|
* PartStream (special) events:
|
||||||
|
* - on('header', (header: object)) - An object containing the header for this particular part. Each property value is an array of one or more string values.
|
||||||
|
*/
|
||||||
|
interface PartStream extends stream.Readable {
|
||||||
|
addListener(event: "header", listener: (header: object) => void): this;
|
||||||
|
addListener(event: "close", listener: () => void): this;
|
||||||
|
addListener(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
addListener(event: "end", listener: () => void): this;
|
||||||
|
addListener(event: "readable", listener: () => void): this;
|
||||||
|
addListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
addListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
on(event: "header", listener: (header: object) => void): this;
|
||||||
|
on(event: "close", listener: () => void): this;
|
||||||
|
on(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
on(event: "end", listener: () => void): this;
|
||||||
|
on(event: "readable", listener: () => void): this;
|
||||||
|
on(event: "error", listener: (err: Error) => void): this;
|
||||||
|
on(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
once(event: "header", listener: (header: object) => void): this;
|
||||||
|
once(event: "close", listener: () => void): this;
|
||||||
|
once(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
once(event: "end", listener: () => void): this;
|
||||||
|
once(event: "readable", listener: () => void): this;
|
||||||
|
once(event: "error", listener: (err: Error) => void): this;
|
||||||
|
once(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
prependListener(event: "header", listener: (header: object) => void): this;
|
||||||
|
prependListener(event: "close", listener: () => void): this;
|
||||||
|
prependListener(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
prependListener(event: "end", listener: () => void): this;
|
||||||
|
prependListener(event: "readable", listener: () => void): this;
|
||||||
|
prependListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
prependListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
prependOnceListener(event: "header", listener: (header: object) => void): this;
|
||||||
|
prependOnceListener(event: "close", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
prependOnceListener(event: "end", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "readable", listener: () => void): this;
|
||||||
|
prependOnceListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
removeListener(event: "header", listener: (header: object) => void): this;
|
||||||
|
removeListener(event: "close", listener: () => void): this;
|
||||||
|
removeListener(event: "data", listener: (chunk: Buffer | string) => void): this;
|
||||||
|
removeListener(event: "end", listener: () => void): this;
|
||||||
|
removeListener(event: "readable", listener: () => void): this;
|
||||||
|
removeListener(event: "error", listener: (err: Error) => void): this;
|
||||||
|
removeListener(event: string, listener: (...args: any[]) => void): this;
|
||||||
|
}
|
||||||
|
}
|
228
node_modules/@fastify/busboy/deps/streamsearch/sbmh.js
generated
vendored
Normal file
228
node_modules/@fastify/busboy/deps/streamsearch/sbmh.js
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright Brian White. All rights reserved.
|
||||||
|
*
|
||||||
|
* @see https://github.com/mscdex/streamsearch
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation
|
||||||
|
* by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool
|
||||||
|
*/
|
||||||
|
const EventEmitter = require('node:events').EventEmitter
|
||||||
|
const inherits = require('node:util').inherits
|
||||||
|
|
||||||
|
function SBMH (needle) {
|
||||||
|
if (typeof needle === 'string') {
|
||||||
|
needle = Buffer.from(needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Buffer.isBuffer(needle)) {
|
||||||
|
throw new TypeError('The needle has to be a String or a Buffer.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const needleLength = needle.length
|
||||||
|
|
||||||
|
if (needleLength === 0) {
|
||||||
|
throw new Error('The needle cannot be an empty String/Buffer.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needleLength > 256) {
|
||||||
|
throw new Error('The needle cannot have a length bigger than 256.')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maxMatches = Infinity
|
||||||
|
this.matches = 0
|
||||||
|
|
||||||
|
this._occ = new Array(256)
|
||||||
|
.fill(needleLength) // Initialize occurrence table.
|
||||||
|
this._lookbehind_size = 0
|
||||||
|
this._needle = needle
|
||||||
|
this._bufpos = 0
|
||||||
|
|
||||||
|
this._lookbehind = Buffer.alloc(needleLength)
|
||||||
|
|
||||||
|
// Populate occurrence table with analysis of the needle,
|
||||||
|
// ignoring last letter.
|
||||||
|
for (var i = 0; i < needleLength - 1; ++i) { // eslint-disable-line no-var
|
||||||
|
this._occ[needle[i]] = needleLength - 1 - i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inherits(SBMH, EventEmitter)
|
||||||
|
|
||||||
|
SBMH.prototype.reset = function () {
|
||||||
|
this._lookbehind_size = 0
|
||||||
|
this.matches = 0
|
||||||
|
this._bufpos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
SBMH.prototype.push = function (chunk, pos) {
|
||||||
|
if (!Buffer.isBuffer(chunk)) {
|
||||||
|
chunk = Buffer.from(chunk, 'binary')
|
||||||
|
}
|
||||||
|
const chlen = chunk.length
|
||||||
|
this._bufpos = pos || 0
|
||||||
|
let r
|
||||||
|
while (r !== chlen && this.matches < this.maxMatches) { r = this._sbmh_feed(chunk) }
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
SBMH.prototype._sbmh_feed = function (data) {
|
||||||
|
const len = data.length
|
||||||
|
const needle = this._needle
|
||||||
|
const needleLength = needle.length
|
||||||
|
const lastNeedleChar = needle[needleLength - 1]
|
||||||
|
|
||||||
|
// Positive: points to a position in `data`
|
||||||
|
// pos == 3 points to data[3]
|
||||||
|
// Negative: points to a position in the lookbehind buffer
|
||||||
|
// pos == -2 points to lookbehind[lookbehind_size - 2]
|
||||||
|
let pos = -this._lookbehind_size
|
||||||
|
let ch
|
||||||
|
|
||||||
|
if (pos < 0) {
|
||||||
|
// Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool
|
||||||
|
// search with character lookup code that considers both the
|
||||||
|
// lookbehind buffer and the current round's haystack data.
|
||||||
|
//
|
||||||
|
// Loop until
|
||||||
|
// there is a match.
|
||||||
|
// or until
|
||||||
|
// we've moved past the position that requires the
|
||||||
|
// lookbehind buffer. In this case we switch to the
|
||||||
|
// optimized loop.
|
||||||
|
// or until
|
||||||
|
// the character to look at lies outside the haystack.
|
||||||
|
while (pos < 0 && pos <= len - needleLength) {
|
||||||
|
ch = this._sbmh_lookup_char(data, pos + needleLength - 1)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ch === lastNeedleChar &&
|
||||||
|
this._sbmh_memcmp(data, pos, needleLength - 1)
|
||||||
|
) {
|
||||||
|
this._lookbehind_size = 0
|
||||||
|
++this.matches
|
||||||
|
this.emit('info', true)
|
||||||
|
|
||||||
|
return (this._bufpos = pos + needleLength)
|
||||||
|
}
|
||||||
|
pos += this._occ[ch]
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match.
|
||||||
|
|
||||||
|
if (pos < 0) {
|
||||||
|
// There's too few data for Boyer-Moore-Horspool to run,
|
||||||
|
// so let's use a different algorithm to skip as much as
|
||||||
|
// we can.
|
||||||
|
// Forward pos until
|
||||||
|
// the trailing part of lookbehind + data
|
||||||
|
// looks like the beginning of the needle
|
||||||
|
// or until
|
||||||
|
// pos == 0
|
||||||
|
while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) { ++pos }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
// Discard lookbehind buffer.
|
||||||
|
this.emit('info', false, this._lookbehind, 0, this._lookbehind_size)
|
||||||
|
this._lookbehind_size = 0
|
||||||
|
} else {
|
||||||
|
// Cut off part of the lookbehind buffer that has
|
||||||
|
// been processed and append the entire haystack
|
||||||
|
// into it.
|
||||||
|
const bytesToCutOff = this._lookbehind_size + pos
|
||||||
|
if (bytesToCutOff > 0) {
|
||||||
|
// The cut off data is guaranteed not to contain the needle.
|
||||||
|
this.emit('info', false, this._lookbehind, 0, bytesToCutOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._lookbehind.copy(this._lookbehind, 0, bytesToCutOff,
|
||||||
|
this._lookbehind_size - bytesToCutOff)
|
||||||
|
this._lookbehind_size -= bytesToCutOff
|
||||||
|
|
||||||
|
data.copy(this._lookbehind, this._lookbehind_size)
|
||||||
|
this._lookbehind_size += len
|
||||||
|
|
||||||
|
this._bufpos = len
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += (pos >= 0) * this._bufpos
|
||||||
|
|
||||||
|
// Lookbehind buffer is now empty. We only need to check if the
|
||||||
|
// needle is in the haystack.
|
||||||
|
if (data.indexOf(needle, pos) !== -1) {
|
||||||
|
pos = data.indexOf(needle, pos)
|
||||||
|
++this.matches
|
||||||
|
if (pos > 0) { this.emit('info', true, data, this._bufpos, pos) } else { this.emit('info', true) }
|
||||||
|
|
||||||
|
return (this._bufpos = pos + needleLength)
|
||||||
|
} else {
|
||||||
|
pos = len - needleLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was no match. If there's trailing haystack data that we cannot
|
||||||
|
// match yet using the Boyer-Moore-Horspool algorithm (because the trailing
|
||||||
|
// data is less than the needle size) then match using a modified
|
||||||
|
// algorithm that starts matching from the beginning instead of the end.
|
||||||
|
// Whatever trailing data is left after running this algorithm is added to
|
||||||
|
// the lookbehind buffer.
|
||||||
|
while (
|
||||||
|
pos < len &&
|
||||||
|
(
|
||||||
|
data[pos] !== needle[0] ||
|
||||||
|
(
|
||||||
|
(Buffer.compare(
|
||||||
|
data.subarray(pos, pos + len - pos),
|
||||||
|
needle.subarray(0, len - pos)
|
||||||
|
) !== 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
++pos
|
||||||
|
}
|
||||||
|
if (pos < len) {
|
||||||
|
data.copy(this._lookbehind, 0, pos, pos + (len - pos))
|
||||||
|
this._lookbehind_size = len - pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything until pos is guaranteed not to contain needle data.
|
||||||
|
if (pos > 0) { this.emit('info', false, data, this._bufpos, pos < len ? pos : len) }
|
||||||
|
|
||||||
|
this._bufpos = len
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
|
||||||
|
SBMH.prototype._sbmh_lookup_char = function (data, pos) {
|
||||||
|
return (pos < 0)
|
||||||
|
? this._lookbehind[this._lookbehind_size + pos]
|
||||||
|
: data[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
SBMH.prototype._sbmh_memcmp = function (data, pos, len) {
|
||||||
|
for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
|
||||||
|
if (this._sbmh_lookup_char(data, pos + i) !== this._needle[i]) { return false }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SBMH
|
196
node_modules/@fastify/busboy/lib/main.d.ts
generated
vendored
Normal file
196
node_modules/@fastify/busboy/lib/main.d.ts
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// Definitions by: Jacob Baskin <https://github.com/jacobbaskin>
|
||||||
|
// BendingBender <https://github.com/BendingBender>
|
||||||
|
// Igor Savin <https://github.com/kibertoad>
|
||||||
|
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import * as http from 'http';
|
||||||
|
import { Readable, Writable } from 'stream';
|
||||||
|
export { Dicer } from "../deps/dicer/lib/dicer";
|
||||||
|
|
||||||
|
export const Busboy: BusboyConstructor;
|
||||||
|
export default Busboy;
|
||||||
|
|
||||||
|
export interface BusboyConfig {
|
||||||
|
/**
|
||||||
|
* These are the HTTP headers of the incoming request, which are used by individual parsers.
|
||||||
|
*/
|
||||||
|
headers: BusboyHeaders;
|
||||||
|
/**
|
||||||
|
* `highWaterMark` to use for this Busboy instance.
|
||||||
|
* @default WritableStream default.
|
||||||
|
*/
|
||||||
|
highWaterMark?: number | undefined;
|
||||||
|
/**
|
||||||
|
* highWaterMark to use for file streams.
|
||||||
|
* @default ReadableStream default.
|
||||||
|
*/
|
||||||
|
fileHwm?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Default character set to use when one isn't defined.
|
||||||
|
* @default 'utf8'
|
||||||
|
*/
|
||||||
|
defCharset?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Detect if a Part is a file.
|
||||||
|
*
|
||||||
|
* By default a file is detected if contentType
|
||||||
|
* is application/octet-stream or fileName is not
|
||||||
|
* undefined.
|
||||||
|
*
|
||||||
|
* Modify this to handle e.g. Blobs.
|
||||||
|
*/
|
||||||
|
isPartAFile?: (fieldName: string | undefined, contentType: string | undefined, fileName: string | undefined) => boolean;
|
||||||
|
/**
|
||||||
|
* If paths in the multipart 'filename' field shall be preserved.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
preservePath?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Various limits on incoming data.
|
||||||
|
*/
|
||||||
|
limits?:
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* Max field name size (in bytes)
|
||||||
|
* @default 100 bytes
|
||||||
|
*/
|
||||||
|
fieldNameSize?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Max field value size (in bytes)
|
||||||
|
* @default 1MB
|
||||||
|
*/
|
||||||
|
fieldSize?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Max number of non-file fields
|
||||||
|
* @default Infinity
|
||||||
|
*/
|
||||||
|
fields?: number | undefined;
|
||||||
|
/**
|
||||||
|
* For multipart forms, the max file size (in bytes)
|
||||||
|
* @default Infinity
|
||||||
|
*/
|
||||||
|
fileSize?: number | undefined;
|
||||||
|
/**
|
||||||
|
* For multipart forms, the max number of file fields
|
||||||
|
* @default Infinity
|
||||||
|
*/
|
||||||
|
files?: number | undefined;
|
||||||
|
/**
|
||||||
|
* For multipart forms, the max number of parts (fields + files)
|
||||||
|
* @default Infinity
|
||||||
|
*/
|
||||||
|
parts?: number | undefined;
|
||||||
|
/**
|
||||||
|
* For multipart forms, the max number of header key=>value pairs to parse
|
||||||
|
* @default 2000
|
||||||
|
*/
|
||||||
|
headerPairs?: number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For multipart forms, the max size of a header part
|
||||||
|
* @default 81920
|
||||||
|
*/
|
||||||
|
headerSize?: number | undefined;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BusboyHeaders = { 'content-type': string } & http.IncomingHttpHeaders;
|
||||||
|
|
||||||
|
export interface BusboyFileStream extends
|
||||||
|
Readable {
|
||||||
|
|
||||||
|
truncated: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes that have been read so far.
|
||||||
|
*/
|
||||||
|
bytesRead: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Busboy extends Writable {
|
||||||
|
addListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
on<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
once<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
removeListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
off<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
prependListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
|
||||||
|
prependOnceListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
|
||||||
|
|
||||||
|
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BusboyEvents {
|
||||||
|
/**
|
||||||
|
* Emitted for each new file form field found.
|
||||||
|
*
|
||||||
|
* * Note: if you listen for this event, you should always handle the `stream` no matter if you care about the
|
||||||
|
* file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents),
|
||||||
|
* otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any**
|
||||||
|
* incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically
|
||||||
|
* and safely discarded (these discarded files do still count towards `files` and `parts` limits).
|
||||||
|
* * If a configured file size limit was reached, `stream` will both have a boolean property `truncated`
|
||||||
|
* (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens.
|
||||||
|
*
|
||||||
|
* @param listener.transferEncoding Contains the 'Content-Transfer-Encoding' value for the file stream.
|
||||||
|
* @param listener.mimeType Contains the 'Content-Type' value for the file stream.
|
||||||
|
*/
|
||||||
|
file: (
|
||||||
|
fieldname: string,
|
||||||
|
stream: BusboyFileStream,
|
||||||
|
filename: string,
|
||||||
|
transferEncoding: string,
|
||||||
|
mimeType: string,
|
||||||
|
) => void;
|
||||||
|
/**
|
||||||
|
* Emitted for each new non-file field found.
|
||||||
|
*/
|
||||||
|
field: (
|
||||||
|
fieldname: string,
|
||||||
|
value: string,
|
||||||
|
fieldnameTruncated: boolean,
|
||||||
|
valueTruncated: boolean,
|
||||||
|
transferEncoding: string,
|
||||||
|
mimeType: string,
|
||||||
|
) => void;
|
||||||
|
finish: () => void;
|
||||||
|
/**
|
||||||
|
* Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted.
|
||||||
|
*/
|
||||||
|
partsLimit: () => void;
|
||||||
|
/**
|
||||||
|
* Emitted when specified `files` limit has been reached. No more 'file' events will be emitted.
|
||||||
|
*/
|
||||||
|
filesLimit: () => void;
|
||||||
|
/**
|
||||||
|
* Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted.
|
||||||
|
*/
|
||||||
|
fieldsLimit: () => void;
|
||||||
|
error: (error: unknown) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BusboyConstructor {
|
||||||
|
(options: BusboyConfig): Busboy;
|
||||||
|
|
||||||
|
new(options: BusboyConfig): Busboy;
|
||||||
|
}
|
||||||
|
|
85
node_modules/@fastify/busboy/lib/main.js
generated
vendored
Normal file
85
node_modules/@fastify/busboy/lib/main.js
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const WritableStream = require('node:stream').Writable
|
||||||
|
const { inherits } = require('node:util')
|
||||||
|
const Dicer = require('../deps/dicer/lib/Dicer')
|
||||||
|
|
||||||
|
const MultipartParser = require('./types/multipart')
|
||||||
|
const UrlencodedParser = require('./types/urlencoded')
|
||||||
|
const parseParams = require('./utils/parseParams')
|
||||||
|
|
||||||
|
function Busboy (opts) {
|
||||||
|
if (!(this instanceof Busboy)) { return new Busboy(opts) }
|
||||||
|
|
||||||
|
if (typeof opts !== 'object') {
|
||||||
|
throw new TypeError('Busboy expected an options-Object.')
|
||||||
|
}
|
||||||
|
if (typeof opts.headers !== 'object') {
|
||||||
|
throw new TypeError('Busboy expected an options-Object with headers-attribute.')
|
||||||
|
}
|
||||||
|
if (typeof opts.headers['content-type'] !== 'string') {
|
||||||
|
throw new TypeError('Missing Content-Type-header.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
headers,
|
||||||
|
...streamOptions
|
||||||
|
} = opts
|
||||||
|
|
||||||
|
this.opts = {
|
||||||
|
autoDestroy: false,
|
||||||
|
...streamOptions
|
||||||
|
}
|
||||||
|
WritableStream.call(this, this.opts)
|
||||||
|
|
||||||
|
this._done = false
|
||||||
|
this._parser = this.getParserByHeaders(headers)
|
||||||
|
this._finished = false
|
||||||
|
}
|
||||||
|
inherits(Busboy, WritableStream)
|
||||||
|
|
||||||
|
Busboy.prototype.emit = function (ev) {
|
||||||
|
if (ev === 'finish') {
|
||||||
|
if (!this._done) {
|
||||||
|
this._parser?.end()
|
||||||
|
return
|
||||||
|
} else if (this._finished) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._finished = true
|
||||||
|
}
|
||||||
|
WritableStream.prototype.emit.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
Busboy.prototype.getParserByHeaders = function (headers) {
|
||||||
|
const parsed = parseParams(headers['content-type'])
|
||||||
|
|
||||||
|
const cfg = {
|
||||||
|
defCharset: this.opts.defCharset,
|
||||||
|
fileHwm: this.opts.fileHwm,
|
||||||
|
headers,
|
||||||
|
highWaterMark: this.opts.highWaterMark,
|
||||||
|
isPartAFile: this.opts.isPartAFile,
|
||||||
|
limits: this.opts.limits,
|
||||||
|
parsedConType: parsed,
|
||||||
|
preservePath: this.opts.preservePath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MultipartParser.detect.test(parsed[0])) {
|
||||||
|
return new MultipartParser(this, cfg)
|
||||||
|
}
|
||||||
|
if (UrlencodedParser.detect.test(parsed[0])) {
|
||||||
|
return new UrlencodedParser(this, cfg)
|
||||||
|
}
|
||||||
|
throw new Error('Unsupported Content-Type.')
|
||||||
|
}
|
||||||
|
|
||||||
|
Busboy.prototype._write = function (chunk, encoding, cb) {
|
||||||
|
this._parser.write(chunk, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Busboy
|
||||||
|
module.exports.default = Busboy
|
||||||
|
module.exports.Busboy = Busboy
|
||||||
|
|
||||||
|
module.exports.Dicer = Dicer
|
306
node_modules/@fastify/busboy/lib/types/multipart.js
generated
vendored
Normal file
306
node_modules/@fastify/busboy/lib/types/multipart.js
generated
vendored
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// * support 1 nested multipart level
|
||||||
|
// (see second multipart example here:
|
||||||
|
// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data)
|
||||||
|
// * support limits.fieldNameSize
|
||||||
|
// -- this will require modifications to utils.parseParams
|
||||||
|
|
||||||
|
const { Readable } = require('node:stream')
|
||||||
|
const { inherits } = require('node:util')
|
||||||
|
|
||||||
|
const Dicer = require('../../deps/dicer/lib/Dicer')
|
||||||
|
|
||||||
|
const parseParams = require('../utils/parseParams')
|
||||||
|
const decodeText = require('../utils/decodeText')
|
||||||
|
const basename = require('../utils/basename')
|
||||||
|
const getLimit = require('../utils/getLimit')
|
||||||
|
|
||||||
|
const RE_BOUNDARY = /^boundary$/i
|
||||||
|
const RE_FIELD = /^form-data$/i
|
||||||
|
const RE_CHARSET = /^charset$/i
|
||||||
|
const RE_FILENAME = /^filename$/i
|
||||||
|
const RE_NAME = /^name$/i
|
||||||
|
|
||||||
|
Multipart.detect = /^multipart\/form-data/i
|
||||||
|
function Multipart (boy, cfg) {
|
||||||
|
let i
|
||||||
|
let len
|
||||||
|
const self = this
|
||||||
|
let boundary
|
||||||
|
const limits = cfg.limits
|
||||||
|
const isPartAFile = cfg.isPartAFile || ((fieldName, contentType, fileName) => (contentType === 'application/octet-stream' || fileName !== undefined))
|
||||||
|
const parsedConType = cfg.parsedConType || []
|
||||||
|
const defCharset = cfg.defCharset || 'utf8'
|
||||||
|
const preservePath = cfg.preservePath
|
||||||
|
const fileOpts = { highWaterMark: cfg.fileHwm }
|
||||||
|
|
||||||
|
for (i = 0, len = parsedConType.length; i < len; ++i) {
|
||||||
|
if (Array.isArray(parsedConType[i]) &&
|
||||||
|
RE_BOUNDARY.test(parsedConType[i][0])) {
|
||||||
|
boundary = parsedConType[i][1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFinished () {
|
||||||
|
if (nends === 0 && finished && !boy._done) {
|
||||||
|
finished = false
|
||||||
|
self.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof boundary !== 'string') { throw new Error('Multipart: Boundary not found') }
|
||||||
|
|
||||||
|
const fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024)
|
||||||
|
const fileSizeLimit = getLimit(limits, 'fileSize', Infinity)
|
||||||
|
const filesLimit = getLimit(limits, 'files', Infinity)
|
||||||
|
const fieldsLimit = getLimit(limits, 'fields', Infinity)
|
||||||
|
const partsLimit = getLimit(limits, 'parts', Infinity)
|
||||||
|
const headerPairsLimit = getLimit(limits, 'headerPairs', 2000)
|
||||||
|
const headerSizeLimit = getLimit(limits, 'headerSize', 80 * 1024)
|
||||||
|
|
||||||
|
let nfiles = 0
|
||||||
|
let nfields = 0
|
||||||
|
let nends = 0
|
||||||
|
let curFile
|
||||||
|
let curField
|
||||||
|
let finished = false
|
||||||
|
|
||||||
|
this._needDrain = false
|
||||||
|
this._pause = false
|
||||||
|
this._cb = undefined
|
||||||
|
this._nparts = 0
|
||||||
|
this._boy = boy
|
||||||
|
|
||||||
|
const parserCfg = {
|
||||||
|
boundary,
|
||||||
|
maxHeaderPairs: headerPairsLimit,
|
||||||
|
maxHeaderSize: headerSizeLimit,
|
||||||
|
partHwm: fileOpts.highWaterMark,
|
||||||
|
highWaterMark: cfg.highWaterMark
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parser = new Dicer(parserCfg)
|
||||||
|
this.parser.on('drain', function () {
|
||||||
|
self._needDrain = false
|
||||||
|
if (self._cb && !self._pause) {
|
||||||
|
const cb = self._cb
|
||||||
|
self._cb = undefined
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}).on('part', function onPart (part) {
|
||||||
|
if (++self._nparts > partsLimit) {
|
||||||
|
self.parser.removeListener('part', onPart)
|
||||||
|
self.parser.on('part', skipPart)
|
||||||
|
boy.hitPartsLimit = true
|
||||||
|
boy.emit('partsLimit')
|
||||||
|
return skipPart(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hack because streams2 _always_ doesn't emit 'end' until nextTick, so let
|
||||||
|
// us emit 'end' early since we know the part has ended if we are already
|
||||||
|
// seeing the next part
|
||||||
|
if (curField) {
|
||||||
|
const field = curField
|
||||||
|
field.emit('end')
|
||||||
|
field.removeAllListeners('end')
|
||||||
|
}
|
||||||
|
|
||||||
|
part.on('header', function (header) {
|
||||||
|
let contype
|
||||||
|
let fieldname
|
||||||
|
let parsed
|
||||||
|
let charset
|
||||||
|
let encoding
|
||||||
|
let filename
|
||||||
|
let nsize = 0
|
||||||
|
|
||||||
|
if (header['content-type']) {
|
||||||
|
parsed = parseParams(header['content-type'][0])
|
||||||
|
if (parsed[0]) {
|
||||||
|
contype = parsed[0].toLowerCase()
|
||||||
|
for (i = 0, len = parsed.length; i < len; ++i) {
|
||||||
|
if (RE_CHARSET.test(parsed[i][0])) {
|
||||||
|
charset = parsed[i][1].toLowerCase()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contype === undefined) { contype = 'text/plain' }
|
||||||
|
if (charset === undefined) { charset = defCharset }
|
||||||
|
|
||||||
|
if (header['content-disposition']) {
|
||||||
|
parsed = parseParams(header['content-disposition'][0])
|
||||||
|
if (!RE_FIELD.test(parsed[0])) { return skipPart(part) }
|
||||||
|
for (i = 0, len = parsed.length; i < len; ++i) {
|
||||||
|
if (RE_NAME.test(parsed[i][0])) {
|
||||||
|
fieldname = parsed[i][1]
|
||||||
|
} else if (RE_FILENAME.test(parsed[i][0])) {
|
||||||
|
filename = parsed[i][1]
|
||||||
|
if (!preservePath) { filename = basename(filename) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { return skipPart(part) }
|
||||||
|
|
||||||
|
if (header['content-transfer-encoding']) { encoding = header['content-transfer-encoding'][0].toLowerCase() } else { encoding = '7bit' }
|
||||||
|
|
||||||
|
let onData,
|
||||||
|
onEnd
|
||||||
|
|
||||||
|
if (isPartAFile(fieldname, contype, filename)) {
|
||||||
|
// file/binary field
|
||||||
|
if (nfiles === filesLimit) {
|
||||||
|
if (!boy.hitFilesLimit) {
|
||||||
|
boy.hitFilesLimit = true
|
||||||
|
boy.emit('filesLimit')
|
||||||
|
}
|
||||||
|
return skipPart(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
++nfiles
|
||||||
|
|
||||||
|
if (boy.listenerCount('file') === 0) {
|
||||||
|
self.parser._ignore()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
++nends
|
||||||
|
const file = new FileStream(fileOpts)
|
||||||
|
curFile = file
|
||||||
|
file.on('end', function () {
|
||||||
|
--nends
|
||||||
|
self._pause = false
|
||||||
|
checkFinished()
|
||||||
|
if (self._cb && !self._needDrain) {
|
||||||
|
const cb = self._cb
|
||||||
|
self._cb = undefined
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
file._read = function (n) {
|
||||||
|
if (!self._pause) { return }
|
||||||
|
self._pause = false
|
||||||
|
if (self._cb && !self._needDrain) {
|
||||||
|
const cb = self._cb
|
||||||
|
self._cb = undefined
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boy.emit('file', fieldname, file, filename, encoding, contype)
|
||||||
|
|
||||||
|
onData = function (data) {
|
||||||
|
if ((nsize += data.length) > fileSizeLimit) {
|
||||||
|
const extralen = fileSizeLimit - nsize + data.length
|
||||||
|
if (extralen > 0) { file.push(data.slice(0, extralen)) }
|
||||||
|
file.truncated = true
|
||||||
|
file.bytesRead = fileSizeLimit
|
||||||
|
part.removeAllListeners('data')
|
||||||
|
file.emit('limit')
|
||||||
|
return
|
||||||
|
} else if (!file.push(data)) { self._pause = true }
|
||||||
|
|
||||||
|
file.bytesRead = nsize
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnd = function () {
|
||||||
|
curFile = undefined
|
||||||
|
file.push(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// non-file field
|
||||||
|
if (nfields === fieldsLimit) {
|
||||||
|
if (!boy.hitFieldsLimit) {
|
||||||
|
boy.hitFieldsLimit = true
|
||||||
|
boy.emit('fieldsLimit')
|
||||||
|
}
|
||||||
|
return skipPart(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
++nfields
|
||||||
|
++nends
|
||||||
|
let buffer = ''
|
||||||
|
let truncated = false
|
||||||
|
curField = part
|
||||||
|
|
||||||
|
onData = function (data) {
|
||||||
|
if ((nsize += data.length) > fieldSizeLimit) {
|
||||||
|
const extralen = (fieldSizeLimit - (nsize - data.length))
|
||||||
|
buffer += data.toString('binary', 0, extralen)
|
||||||
|
truncated = true
|
||||||
|
part.removeAllListeners('data')
|
||||||
|
} else { buffer += data.toString('binary') }
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnd = function () {
|
||||||
|
curField = undefined
|
||||||
|
if (buffer.length) { buffer = decodeText(buffer, 'binary', charset) }
|
||||||
|
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype)
|
||||||
|
--nends
|
||||||
|
checkFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become
|
||||||
|
broken. Streams2/streams3 is a huge black box of confusion, but
|
||||||
|
somehow overriding the sync state seems to fix things again (and still
|
||||||
|
seems to work for previous node versions).
|
||||||
|
*/
|
||||||
|
part._readableState.sync = false
|
||||||
|
|
||||||
|
part.on('data', onData)
|
||||||
|
part.on('end', onEnd)
|
||||||
|
}).on('error', function (err) {
|
||||||
|
if (curFile) { curFile.emit('error', err) }
|
||||||
|
})
|
||||||
|
}).on('error', function (err) {
|
||||||
|
boy.emit('error', err)
|
||||||
|
}).on('finish', function () {
|
||||||
|
finished = true
|
||||||
|
checkFinished()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Multipart.prototype.write = function (chunk, cb) {
|
||||||
|
const r = this.parser.write(chunk)
|
||||||
|
if (r && !this._pause) {
|
||||||
|
cb()
|
||||||
|
} else {
|
||||||
|
this._needDrain = !r
|
||||||
|
this._cb = cb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Multipart.prototype.end = function () {
|
||||||
|
const self = this
|
||||||
|
|
||||||
|
if (self.parser.writable) {
|
||||||
|
self.parser.end()
|
||||||
|
} else if (!self._boy._done) {
|
||||||
|
process.nextTick(function () {
|
||||||
|
self._boy._done = true
|
||||||
|
self._boy.emit('finish')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipPart (part) {
|
||||||
|
part.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileStream (opts) {
|
||||||
|
Readable.call(this, opts)
|
||||||
|
|
||||||
|
this.bytesRead = 0
|
||||||
|
|
||||||
|
this.truncated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(FileStream, Readable)
|
||||||
|
|
||||||
|
FileStream.prototype._read = function (n) {}
|
||||||
|
|
||||||
|
module.exports = Multipart
|
190
node_modules/@fastify/busboy/lib/types/urlencoded.js
generated
vendored
Normal file
190
node_modules/@fastify/busboy/lib/types/urlencoded.js
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Decoder = require('../utils/Decoder')
|
||||||
|
const decodeText = require('../utils/decodeText')
|
||||||
|
const getLimit = require('../utils/getLimit')
|
||||||
|
|
||||||
|
const RE_CHARSET = /^charset$/i
|
||||||
|
|
||||||
|
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i
|
||||||
|
function UrlEncoded (boy, cfg) {
|
||||||
|
const limits = cfg.limits
|
||||||
|
const parsedConType = cfg.parsedConType
|
||||||
|
this.boy = boy
|
||||||
|
|
||||||
|
this.fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024)
|
||||||
|
this.fieldNameSizeLimit = getLimit(limits, 'fieldNameSize', 100)
|
||||||
|
this.fieldsLimit = getLimit(limits, 'fields', Infinity)
|
||||||
|
|
||||||
|
let charset
|
||||||
|
for (var i = 0, len = parsedConType.length; i < len; ++i) { // eslint-disable-line no-var
|
||||||
|
if (Array.isArray(parsedConType[i]) &&
|
||||||
|
RE_CHARSET.test(parsedConType[i][0])) {
|
||||||
|
charset = parsedConType[i][1].toLowerCase()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charset === undefined) { charset = cfg.defCharset || 'utf8' }
|
||||||
|
|
||||||
|
this.decoder = new Decoder()
|
||||||
|
this.charset = charset
|
||||||
|
this._fields = 0
|
||||||
|
this._state = 'key'
|
||||||
|
this._checkingBytes = true
|
||||||
|
this._bytesKey = 0
|
||||||
|
this._bytesVal = 0
|
||||||
|
this._key = ''
|
||||||
|
this._val = ''
|
||||||
|
this._keyTrunc = false
|
||||||
|
this._valTrunc = false
|
||||||
|
this._hitLimit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlEncoded.prototype.write = function (data, cb) {
|
||||||
|
if (this._fields === this.fieldsLimit) {
|
||||||
|
if (!this.boy.hitFieldsLimit) {
|
||||||
|
this.boy.hitFieldsLimit = true
|
||||||
|
this.boy.emit('fieldsLimit')
|
||||||
|
}
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
let idxeq; let idxamp; let i; let p = 0; const len = data.length
|
||||||
|
|
||||||
|
while (p < len) {
|
||||||
|
if (this._state === 'key') {
|
||||||
|
idxeq = idxamp = undefined
|
||||||
|
for (i = p; i < len; ++i) {
|
||||||
|
if (!this._checkingBytes) { ++p }
|
||||||
|
if (data[i] === 0x3D/* = */) {
|
||||||
|
idxeq = i
|
||||||
|
break
|
||||||
|
} else if (data[i] === 0x26/* & */) {
|
||||||
|
idxamp = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) {
|
||||||
|
this._hitLimit = true
|
||||||
|
break
|
||||||
|
} else if (this._checkingBytes) { ++this._bytesKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idxeq !== undefined) {
|
||||||
|
// key with assignment
|
||||||
|
if (idxeq > p) { this._key += this.decoder.write(data.toString('binary', p, idxeq)) }
|
||||||
|
this._state = 'val'
|
||||||
|
|
||||||
|
this._hitLimit = false
|
||||||
|
this._checkingBytes = true
|
||||||
|
this._val = ''
|
||||||
|
this._bytesVal = 0
|
||||||
|
this._valTrunc = false
|
||||||
|
this.decoder.reset()
|
||||||
|
|
||||||
|
p = idxeq + 1
|
||||||
|
} else if (idxamp !== undefined) {
|
||||||
|
// key with no assignment
|
||||||
|
++this._fields
|
||||||
|
let key; const keyTrunc = this._keyTrunc
|
||||||
|
if (idxamp > p) { key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))) } else { key = this._key }
|
||||||
|
|
||||||
|
this._hitLimit = false
|
||||||
|
this._checkingBytes = true
|
||||||
|
this._key = ''
|
||||||
|
this._bytesKey = 0
|
||||||
|
this._keyTrunc = false
|
||||||
|
this.decoder.reset()
|
||||||
|
|
||||||
|
if (key.length) {
|
||||||
|
this.boy.emit('field', decodeText(key, 'binary', this.charset),
|
||||||
|
'',
|
||||||
|
keyTrunc,
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
|
||||||
|
p = idxamp + 1
|
||||||
|
if (this._fields === this.fieldsLimit) { return cb() }
|
||||||
|
} else if (this._hitLimit) {
|
||||||
|
// we may not have hit the actual limit if there are encoded bytes...
|
||||||
|
if (i > p) { this._key += this.decoder.write(data.toString('binary', p, i)) }
|
||||||
|
p = i
|
||||||
|
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) {
|
||||||
|
// yep, we actually did hit the limit
|
||||||
|
this._checkingBytes = false
|
||||||
|
this._keyTrunc = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (p < len) { this._key += this.decoder.write(data.toString('binary', p)) }
|
||||||
|
p = len
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idxamp = undefined
|
||||||
|
for (i = p; i < len; ++i) {
|
||||||
|
if (!this._checkingBytes) { ++p }
|
||||||
|
if (data[i] === 0x26/* & */) {
|
||||||
|
idxamp = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) {
|
||||||
|
this._hitLimit = true
|
||||||
|
break
|
||||||
|
} else if (this._checkingBytes) { ++this._bytesVal }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idxamp !== undefined) {
|
||||||
|
++this._fields
|
||||||
|
if (idxamp > p) { this._val += this.decoder.write(data.toString('binary', p, idxamp)) }
|
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
|
||||||
|
decodeText(this._val, 'binary', this.charset),
|
||||||
|
this._keyTrunc,
|
||||||
|
this._valTrunc)
|
||||||
|
this._state = 'key'
|
||||||
|
|
||||||
|
this._hitLimit = false
|
||||||
|
this._checkingBytes = true
|
||||||
|
this._key = ''
|
||||||
|
this._bytesKey = 0
|
||||||
|
this._keyTrunc = false
|
||||||
|
this.decoder.reset()
|
||||||
|
|
||||||
|
p = idxamp + 1
|
||||||
|
if (this._fields === this.fieldsLimit) { return cb() }
|
||||||
|
} else if (this._hitLimit) {
|
||||||
|
// we may not have hit the actual limit if there are encoded bytes...
|
||||||
|
if (i > p) { this._val += this.decoder.write(data.toString('binary', p, i)) }
|
||||||
|
p = i
|
||||||
|
if ((this._val === '' && this.fieldSizeLimit === 0) ||
|
||||||
|
(this._bytesVal = this._val.length) === this.fieldSizeLimit) {
|
||||||
|
// yep, we actually did hit the limit
|
||||||
|
this._checkingBytes = false
|
||||||
|
this._valTrunc = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (p < len) { this._val += this.decoder.write(data.toString('binary', p)) }
|
||||||
|
p = len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlEncoded.prototype.end = function () {
|
||||||
|
if (this.boy._done) { return }
|
||||||
|
|
||||||
|
if (this._state === 'key' && this._key.length > 0) {
|
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
|
||||||
|
'',
|
||||||
|
this._keyTrunc,
|
||||||
|
false)
|
||||||
|
} else if (this._state === 'val') {
|
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
|
||||||
|
decodeText(this._val, 'binary', this.charset),
|
||||||
|
this._keyTrunc,
|
||||||
|
this._valTrunc)
|
||||||
|
}
|
||||||
|
this.boy._done = true
|
||||||
|
this.boy.emit('finish')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UrlEncoded
|
54
node_modules/@fastify/busboy/lib/utils/Decoder.js
generated
vendored
Normal file
54
node_modules/@fastify/busboy/lib/utils/Decoder.js
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const RE_PLUS = /\+/g
|
||||||
|
|
||||||
|
const HEX = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
]
|
||||||
|
|
||||||
|
function Decoder () {
|
||||||
|
this.buffer = undefined
|
||||||
|
}
|
||||||
|
Decoder.prototype.write = function (str) {
|
||||||
|
// Replace '+' with ' ' before decoding
|
||||||
|
str = str.replace(RE_PLUS, ' ')
|
||||||
|
let res = ''
|
||||||
|
let i = 0; let p = 0; const len = str.length
|
||||||
|
for (; i < len; ++i) {
|
||||||
|
if (this.buffer !== undefined) {
|
||||||
|
if (!HEX[str.charCodeAt(i)]) {
|
||||||
|
res += '%' + this.buffer
|
||||||
|
this.buffer = undefined
|
||||||
|
--i // retry character
|
||||||
|
} else {
|
||||||
|
this.buffer += str[i]
|
||||||
|
++p
|
||||||
|
if (this.buffer.length === 2) {
|
||||||
|
res += String.fromCharCode(parseInt(this.buffer, 16))
|
||||||
|
this.buffer = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (str[i] === '%') {
|
||||||
|
if (i > p) {
|
||||||
|
res += str.substring(p, i)
|
||||||
|
p = i
|
||||||
|
}
|
||||||
|
this.buffer = ''
|
||||||
|
++p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p < len && this.buffer === undefined) { res += str.substring(p) }
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
Decoder.prototype.reset = function () {
|
||||||
|
this.buffer = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Decoder
|
14
node_modules/@fastify/busboy/lib/utils/basename.js
generated
vendored
Normal file
14
node_modules/@fastify/busboy/lib/utils/basename.js
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function basename (path) {
|
||||||
|
if (typeof path !== 'string') { return '' }
|
||||||
|
for (var i = path.length - 1; i >= 0; --i) { // eslint-disable-line no-var
|
||||||
|
switch (path.charCodeAt(i)) {
|
||||||
|
case 0x2F: // '/'
|
||||||
|
case 0x5C: // '\'
|
||||||
|
path = path.slice(i + 1)
|
||||||
|
return (path === '..' || path === '.' ? '' : path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (path === '..' || path === '.' ? '' : path)
|
||||||
|
}
|
114
node_modules/@fastify/busboy/lib/utils/decodeText.js
generated
vendored
Normal file
114
node_modules/@fastify/busboy/lib/utils/decodeText.js
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// Node has always utf-8
|
||||||
|
const utf8Decoder = new TextDecoder('utf-8')
|
||||||
|
const textDecoders = new Map([
|
||||||
|
['utf-8', utf8Decoder],
|
||||||
|
['utf8', utf8Decoder]
|
||||||
|
])
|
||||||
|
|
||||||
|
function getDecoder (charset) {
|
||||||
|
let lc
|
||||||
|
while (true) {
|
||||||
|
switch (charset) {
|
||||||
|
case 'utf-8':
|
||||||
|
case 'utf8':
|
||||||
|
return decoders.utf8
|
||||||
|
case 'latin1':
|
||||||
|
case 'ascii': // TODO: Make these a separate, strict decoder?
|
||||||
|
case 'us-ascii':
|
||||||
|
case 'iso-8859-1':
|
||||||
|
case 'iso8859-1':
|
||||||
|
case 'iso88591':
|
||||||
|
case 'iso_8859-1':
|
||||||
|
case 'windows-1252':
|
||||||
|
case 'iso_8859-1:1987':
|
||||||
|
case 'cp1252':
|
||||||
|
case 'x-cp1252':
|
||||||
|
return decoders.latin1
|
||||||
|
case 'utf16le':
|
||||||
|
case 'utf-16le':
|
||||||
|
case 'ucs2':
|
||||||
|
case 'ucs-2':
|
||||||
|
return decoders.utf16le
|
||||||
|
case 'base64':
|
||||||
|
return decoders.base64
|
||||||
|
default:
|
||||||
|
if (lc === undefined) {
|
||||||
|
lc = true
|
||||||
|
charset = charset.toLowerCase()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return decoders.other.bind(charset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoders = {
|
||||||
|
utf8: (data, sourceEncoding) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = Buffer.from(data, sourceEncoding)
|
||||||
|
}
|
||||||
|
return data.utf8Slice(0, data.length)
|
||||||
|
},
|
||||||
|
|
||||||
|
latin1: (data, sourceEncoding) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return data.latin1Slice(0, data.length)
|
||||||
|
},
|
||||||
|
|
||||||
|
utf16le: (data, sourceEncoding) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = Buffer.from(data, sourceEncoding)
|
||||||
|
}
|
||||||
|
return data.ucs2Slice(0, data.length)
|
||||||
|
},
|
||||||
|
|
||||||
|
base64: (data, sourceEncoding) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = Buffer.from(data, sourceEncoding)
|
||||||
|
}
|
||||||
|
return data.base64Slice(0, data.length)
|
||||||
|
},
|
||||||
|
|
||||||
|
other: (data, sourceEncoding) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = Buffer.from(data, sourceEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textDecoders.has(this.toString())) {
|
||||||
|
try {
|
||||||
|
return textDecoders.get(this).decode(data)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return typeof data === 'string'
|
||||||
|
? data
|
||||||
|
: data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeText (text, sourceEncoding, destEncoding) {
|
||||||
|
if (text) {
|
||||||
|
return getDecoder(destEncoding)(text, sourceEncoding)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = decodeText
|
16
node_modules/@fastify/busboy/lib/utils/getLimit.js
generated
vendored
Normal file
16
node_modules/@fastify/busboy/lib/utils/getLimit.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function getLimit (limits, name, defaultLimit) {
|
||||||
|
if (
|
||||||
|
!limits ||
|
||||||
|
limits[name] === undefined ||
|
||||||
|
limits[name] === null
|
||||||
|
) { return defaultLimit }
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof limits[name] !== 'number' ||
|
||||||
|
isNaN(limits[name])
|
||||||
|
) { throw new TypeError('Limit ' + name + ' is not a valid number') }
|
||||||
|
|
||||||
|
return limits[name]
|
||||||
|
}
|
196
node_modules/@fastify/busboy/lib/utils/parseParams.js
generated
vendored
Normal file
196
node_modules/@fastify/busboy/lib/utils/parseParams.js
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/* eslint-disable object-property-newline */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const decodeText = require('./decodeText')
|
||||||
|
|
||||||
|
const RE_ENCODED = /%[a-fA-F0-9][a-fA-F0-9]/g
|
||||||
|
|
||||||
|
const EncodedLookup = {
|
||||||
|
'%00': '\x00', '%01': '\x01', '%02': '\x02', '%03': '\x03', '%04': '\x04',
|
||||||
|
'%05': '\x05', '%06': '\x06', '%07': '\x07', '%08': '\x08', '%09': '\x09',
|
||||||
|
'%0a': '\x0a', '%0A': '\x0a', '%0b': '\x0b', '%0B': '\x0b', '%0c': '\x0c',
|
||||||
|
'%0C': '\x0c', '%0d': '\x0d', '%0D': '\x0d', '%0e': '\x0e', '%0E': '\x0e',
|
||||||
|
'%0f': '\x0f', '%0F': '\x0f', '%10': '\x10', '%11': '\x11', '%12': '\x12',
|
||||||
|
'%13': '\x13', '%14': '\x14', '%15': '\x15', '%16': '\x16', '%17': '\x17',
|
||||||
|
'%18': '\x18', '%19': '\x19', '%1a': '\x1a', '%1A': '\x1a', '%1b': '\x1b',
|
||||||
|
'%1B': '\x1b', '%1c': '\x1c', '%1C': '\x1c', '%1d': '\x1d', '%1D': '\x1d',
|
||||||
|
'%1e': '\x1e', '%1E': '\x1e', '%1f': '\x1f', '%1F': '\x1f', '%20': '\x20',
|
||||||
|
'%21': '\x21', '%22': '\x22', '%23': '\x23', '%24': '\x24', '%25': '\x25',
|
||||||
|
'%26': '\x26', '%27': '\x27', '%28': '\x28', '%29': '\x29', '%2a': '\x2a',
|
||||||
|
'%2A': '\x2a', '%2b': '\x2b', '%2B': '\x2b', '%2c': '\x2c', '%2C': '\x2c',
|
||||||
|
'%2d': '\x2d', '%2D': '\x2d', '%2e': '\x2e', '%2E': '\x2e', '%2f': '\x2f',
|
||||||
|
'%2F': '\x2f', '%30': '\x30', '%31': '\x31', '%32': '\x32', '%33': '\x33',
|
||||||
|
'%34': '\x34', '%35': '\x35', '%36': '\x36', '%37': '\x37', '%38': '\x38',
|
||||||
|
'%39': '\x39', '%3a': '\x3a', '%3A': '\x3a', '%3b': '\x3b', '%3B': '\x3b',
|
||||||
|
'%3c': '\x3c', '%3C': '\x3c', '%3d': '\x3d', '%3D': '\x3d', '%3e': '\x3e',
|
||||||
|
'%3E': '\x3e', '%3f': '\x3f', '%3F': '\x3f', '%40': '\x40', '%41': '\x41',
|
||||||
|
'%42': '\x42', '%43': '\x43', '%44': '\x44', '%45': '\x45', '%46': '\x46',
|
||||||
|
'%47': '\x47', '%48': '\x48', '%49': '\x49', '%4a': '\x4a', '%4A': '\x4a',
|
||||||
|
'%4b': '\x4b', '%4B': '\x4b', '%4c': '\x4c', '%4C': '\x4c', '%4d': '\x4d',
|
||||||
|
'%4D': '\x4d', '%4e': '\x4e', '%4E': '\x4e', '%4f': '\x4f', '%4F': '\x4f',
|
||||||
|
'%50': '\x50', '%51': '\x51', '%52': '\x52', '%53': '\x53', '%54': '\x54',
|
||||||
|
'%55': '\x55', '%56': '\x56', '%57': '\x57', '%58': '\x58', '%59': '\x59',
|
||||||
|
'%5a': '\x5a', '%5A': '\x5a', '%5b': '\x5b', '%5B': '\x5b', '%5c': '\x5c',
|
||||||
|
'%5C': '\x5c', '%5d': '\x5d', '%5D': '\x5d', '%5e': '\x5e', '%5E': '\x5e',
|
||||||
|
'%5f': '\x5f', '%5F': '\x5f', '%60': '\x60', '%61': '\x61', '%62': '\x62',
|
||||||
|
'%63': '\x63', '%64': '\x64', '%65': '\x65', '%66': '\x66', '%67': '\x67',
|
||||||
|
'%68': '\x68', '%69': '\x69', '%6a': '\x6a', '%6A': '\x6a', '%6b': '\x6b',
|
||||||
|
'%6B': '\x6b', '%6c': '\x6c', '%6C': '\x6c', '%6d': '\x6d', '%6D': '\x6d',
|
||||||
|
'%6e': '\x6e', '%6E': '\x6e', '%6f': '\x6f', '%6F': '\x6f', '%70': '\x70',
|
||||||
|
'%71': '\x71', '%72': '\x72', '%73': '\x73', '%74': '\x74', '%75': '\x75',
|
||||||
|
'%76': '\x76', '%77': '\x77', '%78': '\x78', '%79': '\x79', '%7a': '\x7a',
|
||||||
|
'%7A': '\x7a', '%7b': '\x7b', '%7B': '\x7b', '%7c': '\x7c', '%7C': '\x7c',
|
||||||
|
'%7d': '\x7d', '%7D': '\x7d', '%7e': '\x7e', '%7E': '\x7e', '%7f': '\x7f',
|
||||||
|
'%7F': '\x7f', '%80': '\x80', '%81': '\x81', '%82': '\x82', '%83': '\x83',
|
||||||
|
'%84': '\x84', '%85': '\x85', '%86': '\x86', '%87': '\x87', '%88': '\x88',
|
||||||
|
'%89': '\x89', '%8a': '\x8a', '%8A': '\x8a', '%8b': '\x8b', '%8B': '\x8b',
|
||||||
|
'%8c': '\x8c', '%8C': '\x8c', '%8d': '\x8d', '%8D': '\x8d', '%8e': '\x8e',
|
||||||
|
'%8E': '\x8e', '%8f': '\x8f', '%8F': '\x8f', '%90': '\x90', '%91': '\x91',
|
||||||
|
'%92': '\x92', '%93': '\x93', '%94': '\x94', '%95': '\x95', '%96': '\x96',
|
||||||
|
'%97': '\x97', '%98': '\x98', '%99': '\x99', '%9a': '\x9a', '%9A': '\x9a',
|
||||||
|
'%9b': '\x9b', '%9B': '\x9b', '%9c': '\x9c', '%9C': '\x9c', '%9d': '\x9d',
|
||||||
|
'%9D': '\x9d', '%9e': '\x9e', '%9E': '\x9e', '%9f': '\x9f', '%9F': '\x9f',
|
||||||
|
'%a0': '\xa0', '%A0': '\xa0', '%a1': '\xa1', '%A1': '\xa1', '%a2': '\xa2',
|
||||||
|
'%A2': '\xa2', '%a3': '\xa3', '%A3': '\xa3', '%a4': '\xa4', '%A4': '\xa4',
|
||||||
|
'%a5': '\xa5', '%A5': '\xa5', '%a6': '\xa6', '%A6': '\xa6', '%a7': '\xa7',
|
||||||
|
'%A7': '\xa7', '%a8': '\xa8', '%A8': '\xa8', '%a9': '\xa9', '%A9': '\xa9',
|
||||||
|
'%aa': '\xaa', '%Aa': '\xaa', '%aA': '\xaa', '%AA': '\xaa', '%ab': '\xab',
|
||||||
|
'%Ab': '\xab', '%aB': '\xab', '%AB': '\xab', '%ac': '\xac', '%Ac': '\xac',
|
||||||
|
'%aC': '\xac', '%AC': '\xac', '%ad': '\xad', '%Ad': '\xad', '%aD': '\xad',
|
||||||
|
'%AD': '\xad', '%ae': '\xae', '%Ae': '\xae', '%aE': '\xae', '%AE': '\xae',
|
||||||
|
'%af': '\xaf', '%Af': '\xaf', '%aF': '\xaf', '%AF': '\xaf', '%b0': '\xb0',
|
||||||
|
'%B0': '\xb0', '%b1': '\xb1', '%B1': '\xb1', '%b2': '\xb2', '%B2': '\xb2',
|
||||||
|
'%b3': '\xb3', '%B3': '\xb3', '%b4': '\xb4', '%B4': '\xb4', '%b5': '\xb5',
|
||||||
|
'%B5': '\xb5', '%b6': '\xb6', '%B6': '\xb6', '%b7': '\xb7', '%B7': '\xb7',
|
||||||
|
'%b8': '\xb8', '%B8': '\xb8', '%b9': '\xb9', '%B9': '\xb9', '%ba': '\xba',
|
||||||
|
'%Ba': '\xba', '%bA': '\xba', '%BA': '\xba', '%bb': '\xbb', '%Bb': '\xbb',
|
||||||
|
'%bB': '\xbb', '%BB': '\xbb', '%bc': '\xbc', '%Bc': '\xbc', '%bC': '\xbc',
|
||||||
|
'%BC': '\xbc', '%bd': '\xbd', '%Bd': '\xbd', '%bD': '\xbd', '%BD': '\xbd',
|
||||||
|
'%be': '\xbe', '%Be': '\xbe', '%bE': '\xbe', '%BE': '\xbe', '%bf': '\xbf',
|
||||||
|
'%Bf': '\xbf', '%bF': '\xbf', '%BF': '\xbf', '%c0': '\xc0', '%C0': '\xc0',
|
||||||
|
'%c1': '\xc1', '%C1': '\xc1', '%c2': '\xc2', '%C2': '\xc2', '%c3': '\xc3',
|
||||||
|
'%C3': '\xc3', '%c4': '\xc4', '%C4': '\xc4', '%c5': '\xc5', '%C5': '\xc5',
|
||||||
|
'%c6': '\xc6', '%C6': '\xc6', '%c7': '\xc7', '%C7': '\xc7', '%c8': '\xc8',
|
||||||
|
'%C8': '\xc8', '%c9': '\xc9', '%C9': '\xc9', '%ca': '\xca', '%Ca': '\xca',
|
||||||
|
'%cA': '\xca', '%CA': '\xca', '%cb': '\xcb', '%Cb': '\xcb', '%cB': '\xcb',
|
||||||
|
'%CB': '\xcb', '%cc': '\xcc', '%Cc': '\xcc', '%cC': '\xcc', '%CC': '\xcc',
|
||||||
|
'%cd': '\xcd', '%Cd': '\xcd', '%cD': '\xcd', '%CD': '\xcd', '%ce': '\xce',
|
||||||
|
'%Ce': '\xce', '%cE': '\xce', '%CE': '\xce', '%cf': '\xcf', '%Cf': '\xcf',
|
||||||
|
'%cF': '\xcf', '%CF': '\xcf', '%d0': '\xd0', '%D0': '\xd0', '%d1': '\xd1',
|
||||||
|
'%D1': '\xd1', '%d2': '\xd2', '%D2': '\xd2', '%d3': '\xd3', '%D3': '\xd3',
|
||||||
|
'%d4': '\xd4', '%D4': '\xd4', '%d5': '\xd5', '%D5': '\xd5', '%d6': '\xd6',
|
||||||
|
'%D6': '\xd6', '%d7': '\xd7', '%D7': '\xd7', '%d8': '\xd8', '%D8': '\xd8',
|
||||||
|
'%d9': '\xd9', '%D9': '\xd9', '%da': '\xda', '%Da': '\xda', '%dA': '\xda',
|
||||||
|
'%DA': '\xda', '%db': '\xdb', '%Db': '\xdb', '%dB': '\xdb', '%DB': '\xdb',
|
||||||
|
'%dc': '\xdc', '%Dc': '\xdc', '%dC': '\xdc', '%DC': '\xdc', '%dd': '\xdd',
|
||||||
|
'%Dd': '\xdd', '%dD': '\xdd', '%DD': '\xdd', '%de': '\xde', '%De': '\xde',
|
||||||
|
'%dE': '\xde', '%DE': '\xde', '%df': '\xdf', '%Df': '\xdf', '%dF': '\xdf',
|
||||||
|
'%DF': '\xdf', '%e0': '\xe0', '%E0': '\xe0', '%e1': '\xe1', '%E1': '\xe1',
|
||||||
|
'%e2': '\xe2', '%E2': '\xe2', '%e3': '\xe3', '%E3': '\xe3', '%e4': '\xe4',
|
||||||
|
'%E4': '\xe4', '%e5': '\xe5', '%E5': '\xe5', '%e6': '\xe6', '%E6': '\xe6',
|
||||||
|
'%e7': '\xe7', '%E7': '\xe7', '%e8': '\xe8', '%E8': '\xe8', '%e9': '\xe9',
|
||||||
|
'%E9': '\xe9', '%ea': '\xea', '%Ea': '\xea', '%eA': '\xea', '%EA': '\xea',
|
||||||
|
'%eb': '\xeb', '%Eb': '\xeb', '%eB': '\xeb', '%EB': '\xeb', '%ec': '\xec',
|
||||||
|
'%Ec': '\xec', '%eC': '\xec', '%EC': '\xec', '%ed': '\xed', '%Ed': '\xed',
|
||||||
|
'%eD': '\xed', '%ED': '\xed', '%ee': '\xee', '%Ee': '\xee', '%eE': '\xee',
|
||||||
|
'%EE': '\xee', '%ef': '\xef', '%Ef': '\xef', '%eF': '\xef', '%EF': '\xef',
|
||||||
|
'%f0': '\xf0', '%F0': '\xf0', '%f1': '\xf1', '%F1': '\xf1', '%f2': '\xf2',
|
||||||
|
'%F2': '\xf2', '%f3': '\xf3', '%F3': '\xf3', '%f4': '\xf4', '%F4': '\xf4',
|
||||||
|
'%f5': '\xf5', '%F5': '\xf5', '%f6': '\xf6', '%F6': '\xf6', '%f7': '\xf7',
|
||||||
|
'%F7': '\xf7', '%f8': '\xf8', '%F8': '\xf8', '%f9': '\xf9', '%F9': '\xf9',
|
||||||
|
'%fa': '\xfa', '%Fa': '\xfa', '%fA': '\xfa', '%FA': '\xfa', '%fb': '\xfb',
|
||||||
|
'%Fb': '\xfb', '%fB': '\xfb', '%FB': '\xfb', '%fc': '\xfc', '%Fc': '\xfc',
|
||||||
|
'%fC': '\xfc', '%FC': '\xfc', '%fd': '\xfd', '%Fd': '\xfd', '%fD': '\xfd',
|
||||||
|
'%FD': '\xfd', '%fe': '\xfe', '%Fe': '\xfe', '%fE': '\xfe', '%FE': '\xfe',
|
||||||
|
'%ff': '\xff', '%Ff': '\xff', '%fF': '\xff', '%FF': '\xff'
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodedReplacer (match) {
|
||||||
|
return EncodedLookup[match]
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATE_KEY = 0
|
||||||
|
const STATE_VALUE = 1
|
||||||
|
const STATE_CHARSET = 2
|
||||||
|
const STATE_LANG = 3
|
||||||
|
|
||||||
|
function parseParams (str) {
|
||||||
|
const res = []
|
||||||
|
let state = STATE_KEY
|
||||||
|
let charset = ''
|
||||||
|
let inquote = false
|
||||||
|
let escaping = false
|
||||||
|
let p = 0
|
||||||
|
let tmp = ''
|
||||||
|
const len = str.length
|
||||||
|
|
||||||
|
for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
|
||||||
|
const char = str[i]
|
||||||
|
if (char === '\\' && inquote) {
|
||||||
|
if (escaping) { escaping = false } else {
|
||||||
|
escaping = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if (char === '"') {
|
||||||
|
if (!escaping) {
|
||||||
|
if (inquote) {
|
||||||
|
inquote = false
|
||||||
|
state = STATE_KEY
|
||||||
|
} else { inquote = true }
|
||||||
|
continue
|
||||||
|
} else { escaping = false }
|
||||||
|
} else {
|
||||||
|
if (escaping && inquote) { tmp += '\\' }
|
||||||
|
escaping = false
|
||||||
|
if ((state === STATE_CHARSET || state === STATE_LANG) && char === "'") {
|
||||||
|
if (state === STATE_CHARSET) {
|
||||||
|
state = STATE_LANG
|
||||||
|
charset = tmp.substring(1)
|
||||||
|
} else { state = STATE_VALUE }
|
||||||
|
tmp = ''
|
||||||
|
continue
|
||||||
|
} else if (state === STATE_KEY &&
|
||||||
|
(char === '*' || char === '=') &&
|
||||||
|
res.length) {
|
||||||
|
state = char === '*'
|
||||||
|
? STATE_CHARSET
|
||||||
|
: STATE_VALUE
|
||||||
|
res[p] = [tmp, undefined]
|
||||||
|
tmp = ''
|
||||||
|
continue
|
||||||
|
} else if (!inquote && char === ';') {
|
||||||
|
state = STATE_KEY
|
||||||
|
if (charset) {
|
||||||
|
if (tmp.length) {
|
||||||
|
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer),
|
||||||
|
'binary',
|
||||||
|
charset)
|
||||||
|
}
|
||||||
|
charset = ''
|
||||||
|
} else if (tmp.length) {
|
||||||
|
tmp = decodeText(tmp, 'binary', 'utf8')
|
||||||
|
}
|
||||||
|
if (res[p] === undefined) { res[p] = tmp } else { res[p][1] = tmp }
|
||||||
|
tmp = ''
|
||||||
|
++p
|
||||||
|
continue
|
||||||
|
} else if (!inquote && (char === ' ' || char === '\t')) { continue }
|
||||||
|
}
|
||||||
|
tmp += char
|
||||||
|
}
|
||||||
|
if (charset && tmp.length) {
|
||||||
|
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer),
|
||||||
|
'binary',
|
||||||
|
charset)
|
||||||
|
} else if (tmp) {
|
||||||
|
tmp = decodeText(tmp, 'binary', 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res[p] === undefined) {
|
||||||
|
if (tmp) { res[p] = tmp }
|
||||||
|
} else { res[p][1] = tmp }
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parseParams
|
86
node_modules/@fastify/busboy/package.json
generated
vendored
Normal file
86
node_modules/@fastify/busboy/package.json
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "@fastify/busboy",
|
||||||
|
"version": "2.1.1",
|
||||||
|
"private": false,
|
||||||
|
"author": "Brian White <mscdex@mscdex.net>",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Igor Savin",
|
||||||
|
"email": "kibertoad@gmail.com",
|
||||||
|
"url": "https://github.com/kibertoad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aras Abbasi",
|
||||||
|
"email": "aras.abbasi@gmail.com",
|
||||||
|
"url": "https://github.com/uzlopak"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A streaming parser for HTML form data for node.js",
|
||||||
|
"main": "lib/main",
|
||||||
|
"type": "commonjs",
|
||||||
|
"types": "lib/main.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"bench:busboy": "cd benchmarks && npm install && npm run benchmark-fastify",
|
||||||
|
"bench:dicer": "node bench/dicer/dicer-bench-multipart-parser.js",
|
||||||
|
"coveralls": "nyc report --reporter=lcov",
|
||||||
|
"lint": "npm run lint:standard",
|
||||||
|
"lint:everything": "npm run lint && npm run test:types",
|
||||||
|
"lint:fix": "standard --fix",
|
||||||
|
"lint:standard": "standard --verbose | snazzy",
|
||||||
|
"test:mocha": "tap",
|
||||||
|
"test:types": "tsd",
|
||||||
|
"test:coverage": "nyc npm run test",
|
||||||
|
"test": "npm run test:mocha"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.1.0",
|
||||||
|
"busboy": "^1.0.0",
|
||||||
|
"photofinish": "^1.8.0",
|
||||||
|
"snazzy": "^9.0.0",
|
||||||
|
"standard": "^17.0.0",
|
||||||
|
"tap": "^16.3.8",
|
||||||
|
"tinybench": "^2.5.1",
|
||||||
|
"tsd": "^0.30.0",
|
||||||
|
"typescript": "^5.0.2"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"uploads",
|
||||||
|
"forms",
|
||||||
|
"multipart",
|
||||||
|
"form-data"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/fastify/busboy.git"
|
||||||
|
},
|
||||||
|
"tsd": {
|
||||||
|
"directory": "test/types",
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES2017"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"globals": [
|
||||||
|
"describe",
|
||||||
|
"it"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"bench"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
|
"lib/*",
|
||||||
|
"deps/encoding/*",
|
||||||
|
"deps/dicer/lib",
|
||||||
|
"deps/streamsearch/",
|
||||||
|
"deps/dicer/LICENSE"
|
||||||
|
]
|
||||||
|
}
|
838
node_modules/express-rate-limit/dist/index.cjs
generated
vendored
Normal file
838
node_modules/express-rate-limit/dist/index.cjs
generated
vendored
Normal file
@ -0,0 +1,838 @@
|
|||||||
|
"use strict";
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// source/index.ts
|
||||||
|
var source_exports = {};
|
||||||
|
__export(source_exports, {
|
||||||
|
MemoryStore: () => MemoryStore,
|
||||||
|
default: () => lib_default,
|
||||||
|
rateLimit: () => lib_default
|
||||||
|
});
|
||||||
|
module.exports = __toCommonJS(source_exports);
|
||||||
|
|
||||||
|
// source/headers.ts
|
||||||
|
var import_node_buffer = require("buffer");
|
||||||
|
var import_node_crypto = require("crypto");
|
||||||
|
var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
|
||||||
|
var getResetSeconds = (resetTime, windowMs) => {
|
||||||
|
let resetSeconds = void 0;
|
||||||
|
if (resetTime) {
|
||||||
|
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
||||||
|
resetSeconds = Math.max(0, deltaSeconds);
|
||||||
|
} else if (windowMs) {
|
||||||
|
resetSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
}
|
||||||
|
return resetSeconds;
|
||||||
|
};
|
||||||
|
var getPartitionKey = (key) => {
|
||||||
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
||||||
|
hash.update(key);
|
||||||
|
const partitionKey = hash.digest("hex").slice(0, 12);
|
||||||
|
return import_node_buffer.Buffer.from(partitionKey).toString("base64");
|
||||||
|
};
|
||||||
|
var setLegacyHeaders = (response, info) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
response.setHeader("X-RateLimit-Limit", info.limit.toString());
|
||||||
|
response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
|
||||||
|
if (info.resetTime instanceof Date) {
|
||||||
|
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
||||||
|
response.setHeader(
|
||||||
|
"X-RateLimit-Reset",
|
||||||
|
Math.ceil(info.resetTime.getTime() / 1e3).toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var setDraft6Headers = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime);
|
||||||
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
||||||
|
response.setHeader("RateLimit-Limit", info.limit.toString());
|
||||||
|
response.setHeader("RateLimit-Remaining", info.remaining.toString());
|
||||||
|
if (resetSeconds)
|
||||||
|
response.setHeader("RateLimit-Reset", resetSeconds.toString());
|
||||||
|
};
|
||||||
|
var setDraft7Headers = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
||||||
|
response.setHeader(
|
||||||
|
"RateLimit",
|
||||||
|
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
var setDraft8Headers = (response, info, windowMs, name, key) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
const partitionKey = getPartitionKey(key);
|
||||||
|
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
|
||||||
|
const header = `r=${info.remaining}; t=${resetSeconds}`;
|
||||||
|
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
|
||||||
|
response.append("RateLimit", `"${name}"; ${header}`);
|
||||||
|
};
|
||||||
|
var setRetryAfterHeader = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
response.setHeader("Retry-After", resetSeconds.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/validations.ts
|
||||||
|
var import_node_net = require("net");
|
||||||
|
var ValidationError = class extends Error {
|
||||||
|
/**
|
||||||
|
* The code must be a string, in snake case and all capital, that starts with
|
||||||
|
* the substring `ERR_ERL_`.
|
||||||
|
*
|
||||||
|
* The message must be a string, starting with an uppercase character,
|
||||||
|
* describing the issue in detail.
|
||||||
|
*/
|
||||||
|
constructor(code, message) {
|
||||||
|
const url = `https://express-rate-limit.github.io/${code}/`;
|
||||||
|
super(`${message} See ${url} for more information.`);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.code = code;
|
||||||
|
this.help = url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ChangeWarning = class extends ValidationError {
|
||||||
|
};
|
||||||
|
var usedStores = /* @__PURE__ */ new Set();
|
||||||
|
var singleCountKeys = /* @__PURE__ */ new WeakMap();
|
||||||
|
var validations = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
enabled: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// Should be EnabledValidations type, but that's a circular reference
|
||||||
|
disable() {
|
||||||
|
for (const k of Object.keys(this.enabled))
|
||||||
|
this.enabled[k] = false;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks whether the IP address is valid, and that it does not have a port
|
||||||
|
* number in it.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
||||||
|
*
|
||||||
|
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
ip(ip) {
|
||||||
|
if (ip === void 0) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNDEFINED_IP_ADDRESS",
|
||||||
|
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!(0, import_node_net.isIP)(ip)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_INVALID_IP_ADDRESS",
|
||||||
|
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is not set to `true`.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
trustProxy(request) {
|
||||||
|
if (request.app.get("trust proxy") === true) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
|
||||||
|
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
||||||
|
* header is present.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
xForwardedForHeader(request) {
|
||||||
|
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
|
||||||
|
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures totalHits value from store is a positive integer.
|
||||||
|
*
|
||||||
|
* @param hits {any} - The `totalHits` returned by the store.
|
||||||
|
*/
|
||||||
|
positiveHits(hits) {
|
||||||
|
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_INVALID_HITS",
|
||||||
|
`The totalHits value returned from the store must be a positive integer, got ${hits}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||||
|
*/
|
||||||
|
unsharedStore(store) {
|
||||||
|
if (usedStores.has(store)) {
|
||||||
|
const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_STORE_REUSE",
|
||||||
|
`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
usedStores.add(store);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures a given key is incremented only once per request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param store {Store} - The store class.
|
||||||
|
* @param key {string} - The key used to store the client's hit count.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
singleCount(request, store, key) {
|
||||||
|
let storeKeys = singleCountKeys.get(request);
|
||||||
|
if (!storeKeys) {
|
||||||
|
storeKeys = /* @__PURE__ */ new Map();
|
||||||
|
singleCountKeys.set(request, storeKeys);
|
||||||
|
}
|
||||||
|
const storeKey = store.localKeys ? store : store.constructor.name;
|
||||||
|
let keys = storeKeys.get(storeKey);
|
||||||
|
if (!keys) {
|
||||||
|
keys = [];
|
||||||
|
storeKeys.set(storeKey, keys);
|
||||||
|
}
|
||||||
|
const prefixedKey = `${store.prefix ?? ""}${key}`;
|
||||||
|
if (keys.includes(prefixedKey)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_DOUBLE_COUNT",
|
||||||
|
`The hit count for ${key} was incremented more than once for a single request.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
keys.push(prefixedKey);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
|
||||||
|
* changing in the next major release.
|
||||||
|
*
|
||||||
|
* @param limit {number} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
limit(limit) {
|
||||||
|
if (limit === 0) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_MAX_ZERO",
|
||||||
|
`Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
||||||
|
* and will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
draftPolliHeaders(draft_polli_ratelimit_headers) {
|
||||||
|
if (draft_polli_ratelimit_headers) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
|
||||||
|
`The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the `onLimitReached` option is deprecated and
|
||||||
|
* will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onLimitReached(onLimitReached) {
|
||||||
|
if (onLimitReached) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
||||||
|
`The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||||
|
*
|
||||||
|
* @param version {any | undefined} - The version passed by the user.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersDraftVersion(version) {
|
||||||
|
if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
|
||||||
|
const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
|
||||||
|
`standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user when the selected headers option requires a reset time but
|
||||||
|
* the store does not provide one.
|
||||||
|
*
|
||||||
|
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersResetTime(resetTime) {
|
||||||
|
if (!resetTime) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_HEADERS_NO_RESET",
|
||||||
|
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks the options.validate setting to ensure that only recognized
|
||||||
|
* validations are enabled or disabled.
|
||||||
|
*
|
||||||
|
* If any unrecognized values are found, an error is logged that
|
||||||
|
* includes the list of supported vaidations.
|
||||||
|
*/
|
||||||
|
validationsConfig() {
|
||||||
|
const supportedValidations = Object.keys(this).filter(
|
||||||
|
(k) => !["enabled", "disable"].includes(k)
|
||||||
|
);
|
||||||
|
supportedValidations.push("default");
|
||||||
|
for (const key of Object.keys(this.enabled)) {
|
||||||
|
if (!supportedValidations.includes(key)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNKNOWN_VALIDATION",
|
||||||
|
`options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
|
||||||
|
", "
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks to see if the instance was created inside of a request handler,
|
||||||
|
* which would prevent it from working correctly, with the default memory
|
||||||
|
* store (or any other store with localKeys.)
|
||||||
|
*/
|
||||||
|
creationStack(store) {
|
||||||
|
const { stack } = new Error(
|
||||||
|
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
|
||||||
|
);
|
||||||
|
if (stack?.includes("Layer.handle [as handle_request]")) {
|
||||||
|
if (!store.localKeys) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||||
|
"express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||||
|
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var getValidations = (_enabled) => {
|
||||||
|
let enabled;
|
||||||
|
if (typeof _enabled === "boolean") {
|
||||||
|
enabled = {
|
||||||
|
default: _enabled
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
enabled = {
|
||||||
|
default: true,
|
||||||
|
..._enabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const wrappedValidations = {
|
||||||
|
enabled
|
||||||
|
};
|
||||||
|
for (const [name, validation] of Object.entries(validations)) {
|
||||||
|
if (typeof validation === "function")
|
||||||
|
wrappedValidations[name] = (...args) => {
|
||||||
|
if (!(enabled[name] ?? enabled.default)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
;
|
||||||
|
validation.apply(
|
||||||
|
wrappedValidations,
|
||||||
|
args
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ChangeWarning)
|
||||||
|
console.warn(error);
|
||||||
|
else
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return wrappedValidations;
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/memory-store.ts
|
||||||
|
var MemoryStore = class {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||||
|
* addresses or API keys).
|
||||||
|
*
|
||||||
|
* They are split into two to avoid having to iterate through the entire set to
|
||||||
|
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||||
|
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||||
|
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||||
|
* known to be expired and can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
this.previous = /* @__PURE__ */ new Map();
|
||||||
|
this.current = /* @__PURE__ */ new Map();
|
||||||
|
/**
|
||||||
|
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||||
|
* cannot affect other instances.
|
||||||
|
*/
|
||||||
|
this.localKeys = true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method that initializes the store.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init(options) {
|
||||||
|
this.windowMs = options.windowMs;
|
||||||
|
if (this.interval)
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.clearExpired();
|
||||||
|
}, this.windowMs);
|
||||||
|
if (this.interval.unref)
|
||||||
|
this.interval.unref();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async get(key) {
|
||||||
|
return this.current.get(key) ?? this.previous.get(key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async increment(key) {
|
||||||
|
const client = this.getClient(key);
|
||||||
|
const now = Date.now();
|
||||||
|
if (client.resetTime.getTime() <= now) {
|
||||||
|
this.resetClient(client, now);
|
||||||
|
}
|
||||||
|
client.totalHits++;
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async decrement(key) {
|
||||||
|
const client = this.getClient(key);
|
||||||
|
if (client.totalHits > 0)
|
||||||
|
client.totalHits--;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async resetKey(key) {
|
||||||
|
this.current.delete(key);
|
||||||
|
this.previous.delete(key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async resetAll() {
|
||||||
|
this.current.clear();
|
||||||
|
this.previous.clear();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to stop the timer (if currently running) and prevent any memory
|
||||||
|
* leaks.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shutdown() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
void this.resetAll();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Recycles a client by setting its hit count to zero, and reset time to
|
||||||
|
* `windowMs` milliseconds from now.
|
||||||
|
*
|
||||||
|
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||||
|
* `current` and `previous` maps.
|
||||||
|
*
|
||||||
|
* @param client {Client} - The client to recycle.
|
||||||
|
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||||
|
*
|
||||||
|
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||||
|
*/
|
||||||
|
resetClient(client, now = Date.now()) {
|
||||||
|
client.totalHits = 0;
|
||||||
|
client.resetTime.setTime(now + this.windowMs);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||||
|
* returned is in the `current` map.
|
||||||
|
*
|
||||||
|
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||||
|
*
|
||||||
|
* @returns {Client} - The requested client.
|
||||||
|
*/
|
||||||
|
getClient(key) {
|
||||||
|
if (this.current.has(key))
|
||||||
|
return this.current.get(key);
|
||||||
|
let client;
|
||||||
|
if (this.previous.has(key)) {
|
||||||
|
client = this.previous.get(key);
|
||||||
|
this.previous.delete(key);
|
||||||
|
} else {
|
||||||
|
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
|
||||||
|
this.resetClient(client);
|
||||||
|
}
|
||||||
|
this.current.set(key, client);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Move current clients to previous, create a new map for current.
|
||||||
|
*
|
||||||
|
* This function is called every `windowMs`.
|
||||||
|
*/
|
||||||
|
clearExpired() {
|
||||||
|
this.previous = this.current;
|
||||||
|
this.current = /* @__PURE__ */ new Map();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/lib.ts
|
||||||
|
var isLegacyStore = (store) => (
|
||||||
|
// Check that `incr` exists but `increment` does not - store authors might want
|
||||||
|
// to keep both around for backwards compatibility.
|
||||||
|
typeof store.incr === "function" && typeof store.increment !== "function"
|
||||||
|
);
|
||||||
|
var promisifyStore = (passedStore) => {
|
||||||
|
if (!isLegacyStore(passedStore)) {
|
||||||
|
return passedStore;
|
||||||
|
}
|
||||||
|
const legacyStore = passedStore;
|
||||||
|
class PromisifiedStore {
|
||||||
|
async increment(key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
legacyStore.incr(
|
||||||
|
key,
|
||||||
|
(error, totalHits, resetTime) => {
|
||||||
|
if (error)
|
||||||
|
reject(error);
|
||||||
|
resolve({ totalHits, resetTime });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async decrement(key) {
|
||||||
|
return legacyStore.decrement(key);
|
||||||
|
}
|
||||||
|
async resetKey(key) {
|
||||||
|
return legacyStore.resetKey(key);
|
||||||
|
}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
async resetAll() {
|
||||||
|
if (typeof legacyStore.resetAll === "function")
|
||||||
|
return legacyStore.resetAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new PromisifiedStore();
|
||||||
|
};
|
||||||
|
var getOptionsFromConfig = (config) => {
|
||||||
|
const { validations: validations2, ...directlyPassableEntries } = config;
|
||||||
|
return {
|
||||||
|
...directlyPassableEntries,
|
||||||
|
validate: validations2.enabled
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var omitUndefinedOptions = (passedOptions) => {
|
||||||
|
const omittedOptions = {};
|
||||||
|
for (const k of Object.keys(passedOptions)) {
|
||||||
|
const key = k;
|
||||||
|
if (passedOptions[key] !== void 0) {
|
||||||
|
omittedOptions[key] = passedOptions[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return omittedOptions;
|
||||||
|
};
|
||||||
|
var parseOptions = (passedOptions) => {
|
||||||
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
||||||
|
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
|
||||||
|
validations2.validationsConfig();
|
||||||
|
validations2.draftPolliHeaders(
|
||||||
|
// @ts-expect-error see the note above.
|
||||||
|
notUndefinedOptions.draft_polli_ratelimit_headers
|
||||||
|
);
|
||||||
|
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
|
||||||
|
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
|
||||||
|
if (standardHeaders === true)
|
||||||
|
standardHeaders = "draft-6";
|
||||||
|
const config = {
|
||||||
|
windowMs: 60 * 1e3,
|
||||||
|
limit: passedOptions.max ?? 5,
|
||||||
|
// `max` is deprecated, but support it anyways.
|
||||||
|
message: "Too many requests, please try again later.",
|
||||||
|
statusCode: 429,
|
||||||
|
legacyHeaders: passedOptions.headers ?? true,
|
||||||
|
identifier(request, _response) {
|
||||||
|
let duration = "";
|
||||||
|
const property = config.requestPropertyName;
|
||||||
|
const { limit } = request[property];
|
||||||
|
const seconds = config.windowMs / 1e3;
|
||||||
|
const minutes = config.windowMs / (1e3 * 60);
|
||||||
|
const hours = config.windowMs / (1e3 * 60 * 60);
|
||||||
|
const days = config.windowMs / (1e3 * 60 * 60 * 24);
|
||||||
|
if (seconds < 60)
|
||||||
|
duration = `${seconds}sec`;
|
||||||
|
else if (minutes < 60)
|
||||||
|
duration = `${minutes}min`;
|
||||||
|
else if (hours < 24)
|
||||||
|
duration = `${hours}hr${hours > 1 ? "s" : ""}`;
|
||||||
|
else
|
||||||
|
duration = `${days}day${days > 1 ? "s" : ""}`;
|
||||||
|
return `${limit}-in-${duration}`;
|
||||||
|
},
|
||||||
|
requestPropertyName: "rateLimit",
|
||||||
|
skipFailedRequests: false,
|
||||||
|
skipSuccessfulRequests: false,
|
||||||
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
||||||
|
skip: (_request, _response) => false,
|
||||||
|
keyGenerator(request, _response) {
|
||||||
|
validations2.ip(request.ip);
|
||||||
|
validations2.trustProxy(request);
|
||||||
|
validations2.xForwardedForHeader(request);
|
||||||
|
return request.ip;
|
||||||
|
},
|
||||||
|
async handler(request, response, _next, _optionsUsed) {
|
||||||
|
response.status(config.statusCode);
|
||||||
|
const message = typeof config.message === "function" ? await config.message(
|
||||||
|
request,
|
||||||
|
response
|
||||||
|
) : config.message;
|
||||||
|
if (!response.writableEnded) {
|
||||||
|
response.send(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
passOnStoreError: false,
|
||||||
|
// Allow the default options to be overriden by the passed options.
|
||||||
|
...notUndefinedOptions,
|
||||||
|
// `standardHeaders` is resolved into a draft version above, use that.
|
||||||
|
standardHeaders,
|
||||||
|
// Note that this field is declared after the user's options are spread in,
|
||||||
|
// so that this field doesn't get overriden with an un-promisified store!
|
||||||
|
store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
|
||||||
|
// Print an error to the console if a few known misconfigurations are detected.
|
||||||
|
validations: validations2
|
||||||
|
};
|
||||||
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
|
||||||
|
throw new TypeError(
|
||||||
|
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
var handleAsyncErrors = (fn) => async (request, response, next) => {
|
||||||
|
try {
|
||||||
|
await Promise.resolve(fn(request, response, next)).catch(next);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var rateLimit = (passedOptions) => {
|
||||||
|
const config = parseOptions(passedOptions ?? {});
|
||||||
|
const options = getOptionsFromConfig(config);
|
||||||
|
config.validations.creationStack(config.store);
|
||||||
|
config.validations.unsharedStore(config.store);
|
||||||
|
if (typeof config.store.init === "function")
|
||||||
|
config.store.init(options);
|
||||||
|
const middleware = handleAsyncErrors(
|
||||||
|
async (request, response, next) => {
|
||||||
|
const skip = await config.skip(request, response);
|
||||||
|
if (skip) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const augmentedRequest = request;
|
||||||
|
const key = await config.keyGenerator(request, response);
|
||||||
|
let totalHits = 0;
|
||||||
|
let resetTime;
|
||||||
|
try {
|
||||||
|
const incrementResult = await config.store.increment(key);
|
||||||
|
totalHits = incrementResult.totalHits;
|
||||||
|
resetTime = incrementResult.resetTime;
|
||||||
|
} catch (error) {
|
||||||
|
if (config.passOnStoreError) {
|
||||||
|
console.error(
|
||||||
|
"express-rate-limit: error from store, allowing request without rate-limiting.",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
config.validations.positiveHits(totalHits);
|
||||||
|
config.validations.singleCount(request, config.store, key);
|
||||||
|
const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
|
||||||
|
const limit = await retrieveLimit;
|
||||||
|
config.validations.limit(limit);
|
||||||
|
const info = {
|
||||||
|
limit,
|
||||||
|
used: totalHits,
|
||||||
|
remaining: Math.max(limit - totalHits, 0),
|
||||||
|
resetTime
|
||||||
|
};
|
||||||
|
Object.defineProperty(info, "current", {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
value: totalHits
|
||||||
|
});
|
||||||
|
augmentedRequest[config.requestPropertyName] = info;
|
||||||
|
if (config.legacyHeaders && !response.headersSent) {
|
||||||
|
setLegacyHeaders(response, info);
|
||||||
|
}
|
||||||
|
if (config.standardHeaders && !response.headersSent) {
|
||||||
|
switch (config.standardHeaders) {
|
||||||
|
case "draft-6": {
|
||||||
|
setDraft6Headers(response, info, config.windowMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "draft-7": {
|
||||||
|
config.validations.headersResetTime(info.resetTime);
|
||||||
|
setDraft7Headers(response, info, config.windowMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "draft-8": {
|
||||||
|
const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
|
||||||
|
const name = await retrieveName;
|
||||||
|
config.validations.headersResetTime(info.resetTime);
|
||||||
|
setDraft8Headers(response, info, config.windowMs, name, key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
config.validations.headersDraftVersion(config.standardHeaders);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
||||||
|
let decremented = false;
|
||||||
|
const decrementKey = async () => {
|
||||||
|
if (!decremented) {
|
||||||
|
await config.store.decrement(key);
|
||||||
|
decremented = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (config.skipFailedRequests) {
|
||||||
|
response.on("finish", async () => {
|
||||||
|
if (!await config.requestWasSuccessful(request, response))
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
response.on("close", async () => {
|
||||||
|
if (!response.writableEnded)
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
response.on("error", async () => {
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.skipSuccessfulRequests) {
|
||||||
|
response.on("finish", async () => {
|
||||||
|
if (await config.requestWasSuccessful(request, response))
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.validations.disable();
|
||||||
|
if (totalHits > limit) {
|
||||||
|
if (config.legacyHeaders || config.standardHeaders) {
|
||||||
|
setRetryAfterHeader(response, info, config.windowMs);
|
||||||
|
}
|
||||||
|
config.handler(request, response, next, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const getThrowFn = () => {
|
||||||
|
throw new Error("The current store does not support the get/getKey method");
|
||||||
|
};
|
||||||
|
middleware.resetKey = config.store.resetKey.bind(config.store);
|
||||||
|
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
|
||||||
|
return middleware;
|
||||||
|
};
|
||||||
|
var lib_default = rateLimit;
|
||||||
|
// Annotate the CommonJS export names for ESM import in node:
|
||||||
|
0 && (module.exports = {
|
||||||
|
MemoryStore,
|
||||||
|
rateLimit
|
||||||
|
});
|
||||||
|
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;
|
584
node_modules/express-rate-limit/dist/index.d.cts
generated
vendored
Normal file
584
node_modules/express-rate-limit/dist/index.d.cts
generated
vendored
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
// Generated by dts-bundle-generator v8.0.1
|
||||||
|
|
||||||
|
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
||||||
|
|
||||||
|
declare const validations: {
|
||||||
|
enabled: {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
|
disable(): void;
|
||||||
|
/**
|
||||||
|
* Checks whether the IP address is valid, and that it does not have a port
|
||||||
|
* number in it.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
||||||
|
*
|
||||||
|
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
ip(ip: string | undefined): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is not set to `true`.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
trustProxy(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
||||||
|
* header is present.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
xForwardedForHeader(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Ensures totalHits value from store is a positive integer.
|
||||||
|
*
|
||||||
|
* @param hits {any} - The `totalHits` returned by the store.
|
||||||
|
*/
|
||||||
|
positiveHits(hits: any): void;
|
||||||
|
/**
|
||||||
|
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||||
|
*/
|
||||||
|
unsharedStore(store: Store): void;
|
||||||
|
/**
|
||||||
|
* Ensures a given key is incremented only once per request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param store {Store} - The store class.
|
||||||
|
* @param key {string} - The key used to store the client's hit count.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
singleCount(request: Request, store: Store, key: string): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
|
||||||
|
* changing in the next major release.
|
||||||
|
*
|
||||||
|
* @param limit {number} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
limit(limit: number): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
||||||
|
* and will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `onLimitReached` option is deprecated and
|
||||||
|
* will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onLimitReached(onLimitReached?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||||
|
*
|
||||||
|
* @param version {any | undefined} - The version passed by the user.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersDraftVersion(version?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when the selected headers option requires a reset time but
|
||||||
|
* the store does not provide one.
|
||||||
|
*
|
||||||
|
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersResetTime(resetTime?: Date): void;
|
||||||
|
/**
|
||||||
|
* Checks the options.validate setting to ensure that only recognized
|
||||||
|
* validations are enabled or disabled.
|
||||||
|
*
|
||||||
|
* If any unrecognized values are found, an error is logged that
|
||||||
|
* includes the list of supported vaidations.
|
||||||
|
*/
|
||||||
|
validationsConfig(): void;
|
||||||
|
/**
|
||||||
|
* Checks to see if the instance was created inside of a request handler,
|
||||||
|
* which would prevent it from working correctly, with the default memory
|
||||||
|
* store (or any other store with localKeys.)
|
||||||
|
*/
|
||||||
|
creationStack(store: Store): void;
|
||||||
|
};
|
||||||
|
export type Validations = typeof validations;
|
||||||
|
declare const SUPPORTED_DRAFT_VERSIONS: string[];
|
||||||
|
/**
|
||||||
|
* Callback that fires when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @param error {Error | undefined} - The error that occurred, if any.
|
||||||
|
* @param totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @param resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to generate/retrieve a value based on the
|
||||||
|
* incoming request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
*
|
||||||
|
* @returns {T} - The value needed.
|
||||||
|
*/
|
||||||
|
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Event callback that is triggered on a client's first request that exceeds the limit
|
||||||
|
* but not for subsequent requests. May be used for logging, etc. Should *not*
|
||||||
|
* send a response.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Data returned from the `Store` when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @property totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type ClientRateLimitInfo = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
export type IncrementResponse = ClientRateLimitInfo;
|
||||||
|
/**
|
||||||
|
* A modified Express request handler with the rate limit functions.
|
||||||
|
*/
|
||||||
|
export type RateLimitRequestHandler = RequestHandler & {
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - Implement the `Store` interface instead.
|
||||||
|
*/
|
||||||
|
export type LegacyStore = {
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
|
||||||
|
*/
|
||||||
|
incr: (key: string, callback: IncrementCallback) => void;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => void;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*/
|
||||||
|
export type Store = {
|
||||||
|
/**
|
||||||
|
* Method that initializes the store, and has access to the options passed to
|
||||||
|
* the middleware too.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init?: (options: Options) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to shutdown the store, stop timers, and release all resources.
|
||||||
|
*/
|
||||||
|
shutdown?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Flag to indicate that keys incremented in one instance of this store can
|
||||||
|
* not affect other instances. Typically false if a database is used, true for
|
||||||
|
* MemoryStore.
|
||||||
|
*
|
||||||
|
* Used to help detect double-counting misconfigurations.
|
||||||
|
*/
|
||||||
|
localKeys?: boolean;
|
||||||
|
/**
|
||||||
|
* Optional value that the store prepends to keys
|
||||||
|
*
|
||||||
|
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
|
||||||
|
*/
|
||||||
|
prefix?: string;
|
||||||
|
};
|
||||||
|
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
|
||||||
|
/**
|
||||||
|
* Validate configuration object for enabling or disabling specific validations.
|
||||||
|
*
|
||||||
|
* The keys must also be keys in the validations object, except `enable`, `disable`,
|
||||||
|
* and `default`.
|
||||||
|
*/
|
||||||
|
export type EnabledValidations = {
|
||||||
|
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The configuration options for the rate limiter.
|
||||||
|
*/
|
||||||
|
export type Options = {
|
||||||
|
/**
|
||||||
|
* How long we should remember the requests.
|
||||||
|
*
|
||||||
|
* Defaults to `60000` ms (= 1 minute).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* Defaults to `5`.
|
||||||
|
*/
|
||||||
|
limit: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* The response body to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `'Too many requests, please try again later.'`
|
||||||
|
*/
|
||||||
|
message: any | ValueDeterminingMiddleware<any>;
|
||||||
|
/**
|
||||||
|
* The HTTP status code to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
||||||
|
*/
|
||||||
|
statusCode: number;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* Defaults to `true` (for backward compatibility).
|
||||||
|
*/
|
||||||
|
legacyHeaders: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
||||||
|
*
|
||||||
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
||||||
|
*/
|
||||||
|
standardHeaders: boolean | DraftHeadersVersion;
|
||||||
|
/**
|
||||||
|
* The name used to identify the quota policy in the `RateLimit` headers as per
|
||||||
|
* the 8th draft of the IETF specification.
|
||||||
|
*
|
||||||
|
* Defaults to `{limit}-in-{window}`.
|
||||||
|
*/
|
||||||
|
identifier: string | ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* The name of the property on the request object to store the rate limit info.
|
||||||
|
*
|
||||||
|
* Defaults to `rateLimit`.
|
||||||
|
*/
|
||||||
|
requestPropertyName: string;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
||||||
|
* or 5XX status.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipFailedRequests: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a
|
||||||
|
* status code less than 400.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipSuccessfulRequests: boolean;
|
||||||
|
/**
|
||||||
|
* Method to generate custom identifiers for clients.
|
||||||
|
*
|
||||||
|
* By default, the client's IP address is used.
|
||||||
|
*/
|
||||||
|
keyGenerator: ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* By default, sends back the `statusCode` and `message` set via the options.
|
||||||
|
*/
|
||||||
|
handler: RateLimitExceededEventHandler;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to determine whether or not this request
|
||||||
|
* counts towards a client's quota.
|
||||||
|
*
|
||||||
|
* By default, skips no requests.
|
||||||
|
*/
|
||||||
|
skip: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
||||||
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
||||||
|
*
|
||||||
|
* By default, requests with a response status code less than 400 are considered
|
||||||
|
* successful.
|
||||||
|
*/
|
||||||
|
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* The `Store` to use to store the hit count for each client.
|
||||||
|
*
|
||||||
|
* By default, the built-in `MemoryStore` will be used.
|
||||||
|
*/
|
||||||
|
store: Store | LegacyStore;
|
||||||
|
/**
|
||||||
|
* The list of validation checks that should run.
|
||||||
|
*/
|
||||||
|
validate: boolean | EnabledValidations;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
|
||||||
|
*/
|
||||||
|
headers?: boolean;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
|
||||||
|
* be removed from the library in the foreseeable future.
|
||||||
|
*/
|
||||||
|
max?: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* If the Store generates an error, allow the request to pass.
|
||||||
|
*/
|
||||||
|
passOnStoreError: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The extended request object that includes information about the client's
|
||||||
|
* rate limit.
|
||||||
|
*/
|
||||||
|
export type AugmentedRequest = Request & {
|
||||||
|
[key: string]: RateLimitInfo;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The rate limit related information for each client included in the
|
||||||
|
* Express request object.
|
||||||
|
*/
|
||||||
|
export type RateLimitInfo = {
|
||||||
|
limit: number;
|
||||||
|
used: number;
|
||||||
|
remaining: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create an instance of IP rate-limiting middleware for Express.
|
||||||
|
*
|
||||||
|
* @param passedOptions {Options} - Options to configure the rate limiter.
|
||||||
|
*
|
||||||
|
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
|
||||||
|
/**
|
||||||
|
* The record that stores information about a client - namely, how many times
|
||||||
|
* they have hit the endpoint, and when their hit count resets.
|
||||||
|
*
|
||||||
|
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
|
||||||
|
*/
|
||||||
|
export type Client = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A `Store` that stores the hit count for each client in memory.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare class MemoryStore implements Store {
|
||||||
|
/**
|
||||||
|
* The duration of time before which all hit counts are reset (in milliseconds).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||||
|
* addresses or API keys).
|
||||||
|
*
|
||||||
|
* They are split into two to avoid having to iterate through the entire set to
|
||||||
|
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||||
|
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||||
|
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||||
|
* known to be expired and can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
previous: Map<string, Client>;
|
||||||
|
current: Map<string, Client>;
|
||||||
|
/**
|
||||||
|
* A reference to the active timer.
|
||||||
|
*/
|
||||||
|
interval?: NodeJS.Timeout;
|
||||||
|
/**
|
||||||
|
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||||
|
* cannot affect other instances.
|
||||||
|
*/
|
||||||
|
localKeys: boolean;
|
||||||
|
/**
|
||||||
|
* Method that initializes the store.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init(options: Options): void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
increment(key: string): Promise<ClientRateLimitInfo>;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
decrement(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetKey(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetAll(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to stop the timer (if currently running) and prevent any memory
|
||||||
|
* leaks.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shutdown(): void;
|
||||||
|
/**
|
||||||
|
* Recycles a client by setting its hit count to zero, and reset time to
|
||||||
|
* `windowMs` milliseconds from now.
|
||||||
|
*
|
||||||
|
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||||
|
* `current` and `previous` maps.
|
||||||
|
*
|
||||||
|
* @param client {Client} - The client to recycle.
|
||||||
|
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||||
|
*
|
||||||
|
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||||
|
*/
|
||||||
|
private resetClient;
|
||||||
|
/**
|
||||||
|
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||||
|
* returned is in the `current` map.
|
||||||
|
*
|
||||||
|
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||||
|
*
|
||||||
|
* @returns {Client} - The requested client.
|
||||||
|
*/
|
||||||
|
private getClient;
|
||||||
|
/**
|
||||||
|
* Move current clients to previous, create a new map for current.
|
||||||
|
*
|
||||||
|
* This function is called every `windowMs`.
|
||||||
|
*/
|
||||||
|
private clearExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
rateLimit as default,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
584
node_modules/express-rate-limit/dist/index.d.mts
generated
vendored
Normal file
584
node_modules/express-rate-limit/dist/index.d.mts
generated
vendored
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
// Generated by dts-bundle-generator v8.0.1
|
||||||
|
|
||||||
|
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
||||||
|
|
||||||
|
declare const validations: {
|
||||||
|
enabled: {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
|
disable(): void;
|
||||||
|
/**
|
||||||
|
* Checks whether the IP address is valid, and that it does not have a port
|
||||||
|
* number in it.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
||||||
|
*
|
||||||
|
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
ip(ip: string | undefined): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is not set to `true`.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
trustProxy(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
||||||
|
* header is present.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
xForwardedForHeader(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Ensures totalHits value from store is a positive integer.
|
||||||
|
*
|
||||||
|
* @param hits {any} - The `totalHits` returned by the store.
|
||||||
|
*/
|
||||||
|
positiveHits(hits: any): void;
|
||||||
|
/**
|
||||||
|
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||||
|
*/
|
||||||
|
unsharedStore(store: Store): void;
|
||||||
|
/**
|
||||||
|
* Ensures a given key is incremented only once per request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param store {Store} - The store class.
|
||||||
|
* @param key {string} - The key used to store the client's hit count.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
singleCount(request: Request, store: Store, key: string): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
|
||||||
|
* changing in the next major release.
|
||||||
|
*
|
||||||
|
* @param limit {number} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
limit(limit: number): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
||||||
|
* and will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `onLimitReached` option is deprecated and
|
||||||
|
* will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onLimitReached(onLimitReached?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||||
|
*
|
||||||
|
* @param version {any | undefined} - The version passed by the user.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersDraftVersion(version?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when the selected headers option requires a reset time but
|
||||||
|
* the store does not provide one.
|
||||||
|
*
|
||||||
|
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersResetTime(resetTime?: Date): void;
|
||||||
|
/**
|
||||||
|
* Checks the options.validate setting to ensure that only recognized
|
||||||
|
* validations are enabled or disabled.
|
||||||
|
*
|
||||||
|
* If any unrecognized values are found, an error is logged that
|
||||||
|
* includes the list of supported vaidations.
|
||||||
|
*/
|
||||||
|
validationsConfig(): void;
|
||||||
|
/**
|
||||||
|
* Checks to see if the instance was created inside of a request handler,
|
||||||
|
* which would prevent it from working correctly, with the default memory
|
||||||
|
* store (or any other store with localKeys.)
|
||||||
|
*/
|
||||||
|
creationStack(store: Store): void;
|
||||||
|
};
|
||||||
|
export type Validations = typeof validations;
|
||||||
|
declare const SUPPORTED_DRAFT_VERSIONS: string[];
|
||||||
|
/**
|
||||||
|
* Callback that fires when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @param error {Error | undefined} - The error that occurred, if any.
|
||||||
|
* @param totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @param resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to generate/retrieve a value based on the
|
||||||
|
* incoming request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
*
|
||||||
|
* @returns {T} - The value needed.
|
||||||
|
*/
|
||||||
|
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Event callback that is triggered on a client's first request that exceeds the limit
|
||||||
|
* but not for subsequent requests. May be used for logging, etc. Should *not*
|
||||||
|
* send a response.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Data returned from the `Store` when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @property totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type ClientRateLimitInfo = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
export type IncrementResponse = ClientRateLimitInfo;
|
||||||
|
/**
|
||||||
|
* A modified Express request handler with the rate limit functions.
|
||||||
|
*/
|
||||||
|
export type RateLimitRequestHandler = RequestHandler & {
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - Implement the `Store` interface instead.
|
||||||
|
*/
|
||||||
|
export type LegacyStore = {
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
|
||||||
|
*/
|
||||||
|
incr: (key: string, callback: IncrementCallback) => void;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => void;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*/
|
||||||
|
export type Store = {
|
||||||
|
/**
|
||||||
|
* Method that initializes the store, and has access to the options passed to
|
||||||
|
* the middleware too.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init?: (options: Options) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to shutdown the store, stop timers, and release all resources.
|
||||||
|
*/
|
||||||
|
shutdown?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Flag to indicate that keys incremented in one instance of this store can
|
||||||
|
* not affect other instances. Typically false if a database is used, true for
|
||||||
|
* MemoryStore.
|
||||||
|
*
|
||||||
|
* Used to help detect double-counting misconfigurations.
|
||||||
|
*/
|
||||||
|
localKeys?: boolean;
|
||||||
|
/**
|
||||||
|
* Optional value that the store prepends to keys
|
||||||
|
*
|
||||||
|
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
|
||||||
|
*/
|
||||||
|
prefix?: string;
|
||||||
|
};
|
||||||
|
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
|
||||||
|
/**
|
||||||
|
* Validate configuration object for enabling or disabling specific validations.
|
||||||
|
*
|
||||||
|
* The keys must also be keys in the validations object, except `enable`, `disable`,
|
||||||
|
* and `default`.
|
||||||
|
*/
|
||||||
|
export type EnabledValidations = {
|
||||||
|
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The configuration options for the rate limiter.
|
||||||
|
*/
|
||||||
|
export type Options = {
|
||||||
|
/**
|
||||||
|
* How long we should remember the requests.
|
||||||
|
*
|
||||||
|
* Defaults to `60000` ms (= 1 minute).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* Defaults to `5`.
|
||||||
|
*/
|
||||||
|
limit: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* The response body to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `'Too many requests, please try again later.'`
|
||||||
|
*/
|
||||||
|
message: any | ValueDeterminingMiddleware<any>;
|
||||||
|
/**
|
||||||
|
* The HTTP status code to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
||||||
|
*/
|
||||||
|
statusCode: number;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* Defaults to `true` (for backward compatibility).
|
||||||
|
*/
|
||||||
|
legacyHeaders: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
||||||
|
*
|
||||||
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
||||||
|
*/
|
||||||
|
standardHeaders: boolean | DraftHeadersVersion;
|
||||||
|
/**
|
||||||
|
* The name used to identify the quota policy in the `RateLimit` headers as per
|
||||||
|
* the 8th draft of the IETF specification.
|
||||||
|
*
|
||||||
|
* Defaults to `{limit}-in-{window}`.
|
||||||
|
*/
|
||||||
|
identifier: string | ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* The name of the property on the request object to store the rate limit info.
|
||||||
|
*
|
||||||
|
* Defaults to `rateLimit`.
|
||||||
|
*/
|
||||||
|
requestPropertyName: string;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
||||||
|
* or 5XX status.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipFailedRequests: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a
|
||||||
|
* status code less than 400.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipSuccessfulRequests: boolean;
|
||||||
|
/**
|
||||||
|
* Method to generate custom identifiers for clients.
|
||||||
|
*
|
||||||
|
* By default, the client's IP address is used.
|
||||||
|
*/
|
||||||
|
keyGenerator: ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* By default, sends back the `statusCode` and `message` set via the options.
|
||||||
|
*/
|
||||||
|
handler: RateLimitExceededEventHandler;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to determine whether or not this request
|
||||||
|
* counts towards a client's quota.
|
||||||
|
*
|
||||||
|
* By default, skips no requests.
|
||||||
|
*/
|
||||||
|
skip: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
||||||
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
||||||
|
*
|
||||||
|
* By default, requests with a response status code less than 400 are considered
|
||||||
|
* successful.
|
||||||
|
*/
|
||||||
|
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* The `Store` to use to store the hit count for each client.
|
||||||
|
*
|
||||||
|
* By default, the built-in `MemoryStore` will be used.
|
||||||
|
*/
|
||||||
|
store: Store | LegacyStore;
|
||||||
|
/**
|
||||||
|
* The list of validation checks that should run.
|
||||||
|
*/
|
||||||
|
validate: boolean | EnabledValidations;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
|
||||||
|
*/
|
||||||
|
headers?: boolean;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
|
||||||
|
* be removed from the library in the foreseeable future.
|
||||||
|
*/
|
||||||
|
max?: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* If the Store generates an error, allow the request to pass.
|
||||||
|
*/
|
||||||
|
passOnStoreError: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The extended request object that includes information about the client's
|
||||||
|
* rate limit.
|
||||||
|
*/
|
||||||
|
export type AugmentedRequest = Request & {
|
||||||
|
[key: string]: RateLimitInfo;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The rate limit related information for each client included in the
|
||||||
|
* Express request object.
|
||||||
|
*/
|
||||||
|
export type RateLimitInfo = {
|
||||||
|
limit: number;
|
||||||
|
used: number;
|
||||||
|
remaining: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create an instance of IP rate-limiting middleware for Express.
|
||||||
|
*
|
||||||
|
* @param passedOptions {Options} - Options to configure the rate limiter.
|
||||||
|
*
|
||||||
|
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
|
||||||
|
/**
|
||||||
|
* The record that stores information about a client - namely, how many times
|
||||||
|
* they have hit the endpoint, and when their hit count resets.
|
||||||
|
*
|
||||||
|
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
|
||||||
|
*/
|
||||||
|
export type Client = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A `Store` that stores the hit count for each client in memory.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare class MemoryStore implements Store {
|
||||||
|
/**
|
||||||
|
* The duration of time before which all hit counts are reset (in milliseconds).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||||
|
* addresses or API keys).
|
||||||
|
*
|
||||||
|
* They are split into two to avoid having to iterate through the entire set to
|
||||||
|
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||||
|
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||||
|
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||||
|
* known to be expired and can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
previous: Map<string, Client>;
|
||||||
|
current: Map<string, Client>;
|
||||||
|
/**
|
||||||
|
* A reference to the active timer.
|
||||||
|
*/
|
||||||
|
interval?: NodeJS.Timeout;
|
||||||
|
/**
|
||||||
|
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||||
|
* cannot affect other instances.
|
||||||
|
*/
|
||||||
|
localKeys: boolean;
|
||||||
|
/**
|
||||||
|
* Method that initializes the store.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init(options: Options): void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
increment(key: string): Promise<ClientRateLimitInfo>;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
decrement(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetKey(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetAll(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to stop the timer (if currently running) and prevent any memory
|
||||||
|
* leaks.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shutdown(): void;
|
||||||
|
/**
|
||||||
|
* Recycles a client by setting its hit count to zero, and reset time to
|
||||||
|
* `windowMs` milliseconds from now.
|
||||||
|
*
|
||||||
|
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||||
|
* `current` and `previous` maps.
|
||||||
|
*
|
||||||
|
* @param client {Client} - The client to recycle.
|
||||||
|
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||||
|
*
|
||||||
|
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||||
|
*/
|
||||||
|
private resetClient;
|
||||||
|
/**
|
||||||
|
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||||
|
* returned is in the `current` map.
|
||||||
|
*
|
||||||
|
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||||
|
*
|
||||||
|
* @returns {Client} - The requested client.
|
||||||
|
*/
|
||||||
|
private getClient;
|
||||||
|
/**
|
||||||
|
* Move current clients to previous, create a new map for current.
|
||||||
|
*
|
||||||
|
* This function is called every `windowMs`.
|
||||||
|
*/
|
||||||
|
private clearExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
rateLimit as default,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
584
node_modules/express-rate-limit/dist/index.d.ts
generated
vendored
Normal file
584
node_modules/express-rate-limit/dist/index.d.ts
generated
vendored
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
// Generated by dts-bundle-generator v8.0.1
|
||||||
|
|
||||||
|
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
||||||
|
|
||||||
|
declare const validations: {
|
||||||
|
enabled: {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
|
disable(): void;
|
||||||
|
/**
|
||||||
|
* Checks whether the IP address is valid, and that it does not have a port
|
||||||
|
* number in it.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
||||||
|
*
|
||||||
|
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
ip(ip: string | undefined): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is not set to `true`.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
trustProxy(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
||||||
|
* header is present.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
xForwardedForHeader(request: Request): void;
|
||||||
|
/**
|
||||||
|
* Ensures totalHits value from store is a positive integer.
|
||||||
|
*
|
||||||
|
* @param hits {any} - The `totalHits` returned by the store.
|
||||||
|
*/
|
||||||
|
positiveHits(hits: any): void;
|
||||||
|
/**
|
||||||
|
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||||
|
*/
|
||||||
|
unsharedStore(store: Store): void;
|
||||||
|
/**
|
||||||
|
* Ensures a given key is incremented only once per request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param store {Store} - The store class.
|
||||||
|
* @param key {string} - The key used to store the client's hit count.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
singleCount(request: Request, store: Store, key: string): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
|
||||||
|
* changing in the next major release.
|
||||||
|
*
|
||||||
|
* @param limit {number} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
limit(limit: number): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
||||||
|
* and will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
draftPolliHeaders(draft_polli_ratelimit_headers?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user that the `onLimitReached` option is deprecated and
|
||||||
|
* will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onLimitReached(onLimitReached?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||||
|
*
|
||||||
|
* @param version {any | undefined} - The version passed by the user.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersDraftVersion(version?: any): void;
|
||||||
|
/**
|
||||||
|
* Warns the user when the selected headers option requires a reset time but
|
||||||
|
* the store does not provide one.
|
||||||
|
*
|
||||||
|
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersResetTime(resetTime?: Date): void;
|
||||||
|
/**
|
||||||
|
* Checks the options.validate setting to ensure that only recognized
|
||||||
|
* validations are enabled or disabled.
|
||||||
|
*
|
||||||
|
* If any unrecognized values are found, an error is logged that
|
||||||
|
* includes the list of supported vaidations.
|
||||||
|
*/
|
||||||
|
validationsConfig(): void;
|
||||||
|
/**
|
||||||
|
* Checks to see if the instance was created inside of a request handler,
|
||||||
|
* which would prevent it from working correctly, with the default memory
|
||||||
|
* store (or any other store with localKeys.)
|
||||||
|
*/
|
||||||
|
creationStack(store: Store): void;
|
||||||
|
};
|
||||||
|
export type Validations = typeof validations;
|
||||||
|
declare const SUPPORTED_DRAFT_VERSIONS: string[];
|
||||||
|
/**
|
||||||
|
* Callback that fires when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @param error {Error | undefined} - The error that occurred, if any.
|
||||||
|
* @param totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @param resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to generate/retrieve a value based on the
|
||||||
|
* incoming request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
*
|
||||||
|
* @returns {T} - The value needed.
|
||||||
|
*/
|
||||||
|
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Event callback that is triggered on a client's first request that exceeds the limit
|
||||||
|
* but not for subsequent requests. May be used for logging, etc. Should *not*
|
||||||
|
* send a response.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param response {Response} - The Express response object.
|
||||||
|
* @param optionsUsed {Options} - The options used to set up the middleware.
|
||||||
|
*/
|
||||||
|
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
|
||||||
|
/**
|
||||||
|
* Data returned from the `Store` when a client's hit counter is incremented.
|
||||||
|
*
|
||||||
|
* @property totalHits {number} - The number of hits for that client so far.
|
||||||
|
* @property resetTime {Date | undefined} - The time when the counter resets.
|
||||||
|
*/
|
||||||
|
export type ClientRateLimitInfo = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
export type IncrementResponse = ClientRateLimitInfo;
|
||||||
|
/**
|
||||||
|
* A modified Express request handler with the rate limit functions.
|
||||||
|
*/
|
||||||
|
export type RateLimitRequestHandler = RequestHandler & {
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
getKey: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - Implement the `Store` interface instead.
|
||||||
|
*/
|
||||||
|
export type LegacyStore = {
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
|
||||||
|
*/
|
||||||
|
incr: (key: string, callback: IncrementCallback) => void;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => void;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* An interface that all hit counter stores must implement.
|
||||||
|
*/
|
||||||
|
export type Store = {
|
||||||
|
/**
|
||||||
|
* Method that initializes the store, and has access to the options passed to
|
||||||
|
* the middleware too.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init?: (options: Options) => void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
|
||||||
|
*/
|
||||||
|
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
decrement: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*/
|
||||||
|
resetKey: (key: string) => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*/
|
||||||
|
resetAll?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Method to shutdown the store, stop timers, and release all resources.
|
||||||
|
*/
|
||||||
|
shutdown?: () => Promise<void> | void;
|
||||||
|
/**
|
||||||
|
* Flag to indicate that keys incremented in one instance of this store can
|
||||||
|
* not affect other instances. Typically false if a database is used, true for
|
||||||
|
* MemoryStore.
|
||||||
|
*
|
||||||
|
* Used to help detect double-counting misconfigurations.
|
||||||
|
*/
|
||||||
|
localKeys?: boolean;
|
||||||
|
/**
|
||||||
|
* Optional value that the store prepends to keys
|
||||||
|
*
|
||||||
|
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
|
||||||
|
*/
|
||||||
|
prefix?: string;
|
||||||
|
};
|
||||||
|
export type DraftHeadersVersion = (typeof SUPPORTED_DRAFT_VERSIONS)[number];
|
||||||
|
/**
|
||||||
|
* Validate configuration object for enabling or disabling specific validations.
|
||||||
|
*
|
||||||
|
* The keys must also be keys in the validations object, except `enable`, `disable`,
|
||||||
|
* and `default`.
|
||||||
|
*/
|
||||||
|
export type EnabledValidations = {
|
||||||
|
[key in keyof Omit<Validations, "enabled" | "disable"> | "default"]?: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The configuration options for the rate limiter.
|
||||||
|
*/
|
||||||
|
export type Options = {
|
||||||
|
/**
|
||||||
|
* How long we should remember the requests.
|
||||||
|
*
|
||||||
|
* Defaults to `60000` ms (= 1 minute).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* Defaults to `5`.
|
||||||
|
*/
|
||||||
|
limit: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* The response body to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `'Too many requests, please try again later.'`
|
||||||
|
*/
|
||||||
|
message: any | ValueDeterminingMiddleware<any>;
|
||||||
|
/**
|
||||||
|
* The HTTP status code to send back when a client is rate limited.
|
||||||
|
*
|
||||||
|
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
|
||||||
|
*/
|
||||||
|
statusCode: number;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* Defaults to `true` (for backward compatibility).
|
||||||
|
*/
|
||||||
|
legacyHeaders: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
|
||||||
|
*
|
||||||
|
* Defaults to `false` (for backward compatibility, but its use is recommended).
|
||||||
|
*/
|
||||||
|
standardHeaders: boolean | DraftHeadersVersion;
|
||||||
|
/**
|
||||||
|
* The name used to identify the quota policy in the `RateLimit` headers as per
|
||||||
|
* the 8th draft of the IETF specification.
|
||||||
|
*
|
||||||
|
* Defaults to `{limit}-in-{window}`.
|
||||||
|
*/
|
||||||
|
identifier: string | ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* The name of the property on the request object to store the rate limit info.
|
||||||
|
*
|
||||||
|
* Defaults to `rateLimit`.
|
||||||
|
*/
|
||||||
|
requestPropertyName: string;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a 4XX
|
||||||
|
* or 5XX status.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipFailedRequests: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the library will (by default) skip all requests that have a
|
||||||
|
* status code less than 400.
|
||||||
|
*
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
skipSuccessfulRequests: boolean;
|
||||||
|
/**
|
||||||
|
* Method to generate custom identifiers for clients.
|
||||||
|
*
|
||||||
|
* By default, the client's IP address is used.
|
||||||
|
*/
|
||||||
|
keyGenerator: ValueDeterminingMiddleware<string>;
|
||||||
|
/**
|
||||||
|
* Express request handler that sends back a response when a client is
|
||||||
|
* rate-limited.
|
||||||
|
*
|
||||||
|
* By default, sends back the `statusCode` and `message` set via the options.
|
||||||
|
*/
|
||||||
|
handler: RateLimitExceededEventHandler;
|
||||||
|
/**
|
||||||
|
* Method (in the form of middleware) to determine whether or not this request
|
||||||
|
* counts towards a client's quota.
|
||||||
|
*
|
||||||
|
* By default, skips no requests.
|
||||||
|
*/
|
||||||
|
skip: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* Method to determine whether or not the request counts as 'succesful'. Used
|
||||||
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
||||||
|
*
|
||||||
|
* By default, requests with a response status code less than 400 are considered
|
||||||
|
* successful.
|
||||||
|
*/
|
||||||
|
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
|
||||||
|
/**
|
||||||
|
* The `Store` to use to store the hit count for each client.
|
||||||
|
*
|
||||||
|
* By default, the built-in `MemoryStore` will be used.
|
||||||
|
*/
|
||||||
|
store: Store | LegacyStore;
|
||||||
|
/**
|
||||||
|
* The list of validation checks that should run.
|
||||||
|
*/
|
||||||
|
validate: boolean | EnabledValidations;
|
||||||
|
/**
|
||||||
|
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
|
||||||
|
* of requests.
|
||||||
|
*
|
||||||
|
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
|
||||||
|
*/
|
||||||
|
headers?: boolean;
|
||||||
|
/**
|
||||||
|
* The maximum number of connections to allow during the `window` before
|
||||||
|
* rate limiting the client.
|
||||||
|
*
|
||||||
|
* Can be the limit itself as a number or express middleware that parses
|
||||||
|
* the request and then figures out the limit.
|
||||||
|
*
|
||||||
|
* @deprecated 7.x - This option was renamed to `limit`. However, it will not
|
||||||
|
* be removed from the library in the foreseeable future.
|
||||||
|
*/
|
||||||
|
max?: number | ValueDeterminingMiddleware<number>;
|
||||||
|
/**
|
||||||
|
* If the Store generates an error, allow the request to pass.
|
||||||
|
*/
|
||||||
|
passOnStoreError: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The extended request object that includes information about the client's
|
||||||
|
* rate limit.
|
||||||
|
*/
|
||||||
|
export type AugmentedRequest = Request & {
|
||||||
|
[key: string]: RateLimitInfo;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The rate limit related information for each client included in the
|
||||||
|
* Express request object.
|
||||||
|
*/
|
||||||
|
export type RateLimitInfo = {
|
||||||
|
limit: number;
|
||||||
|
used: number;
|
||||||
|
remaining: number;
|
||||||
|
resetTime: Date | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create an instance of IP rate-limiting middleware for Express.
|
||||||
|
*
|
||||||
|
* @param passedOptions {Options} - Options to configure the rate limiter.
|
||||||
|
*
|
||||||
|
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
|
||||||
|
/**
|
||||||
|
* The record that stores information about a client - namely, how many times
|
||||||
|
* they have hit the endpoint, and when their hit count resets.
|
||||||
|
*
|
||||||
|
* Similar to `ClientRateLimitInfo`, except `resetTime` is a compulsory field.
|
||||||
|
*/
|
||||||
|
export type Client = {
|
||||||
|
totalHits: number;
|
||||||
|
resetTime: Date;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A `Store` that stores the hit count for each client in memory.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export declare class MemoryStore implements Store {
|
||||||
|
/**
|
||||||
|
* The duration of time before which all hit counts are reset (in milliseconds).
|
||||||
|
*/
|
||||||
|
windowMs: number;
|
||||||
|
/**
|
||||||
|
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||||
|
* addresses or API keys).
|
||||||
|
*
|
||||||
|
* They are split into two to avoid having to iterate through the entire set to
|
||||||
|
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||||
|
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||||
|
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||||
|
* known to be expired and can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
previous: Map<string, Client>;
|
||||||
|
current: Map<string, Client>;
|
||||||
|
/**
|
||||||
|
* A reference to the active timer.
|
||||||
|
*/
|
||||||
|
interval?: NodeJS.Timeout;
|
||||||
|
/**
|
||||||
|
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||||
|
* cannot affect other instances.
|
||||||
|
*/
|
||||||
|
localKeys: boolean;
|
||||||
|
/**
|
||||||
|
* Method that initializes the store.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init(options: Options): void;
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get(key: string): Promise<ClientRateLimitInfo | undefined>;
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
increment(key: string): Promise<ClientRateLimitInfo>;
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
decrement(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetKey(key: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
resetAll(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Method to stop the timer (if currently running) and prevent any memory
|
||||||
|
* leaks.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shutdown(): void;
|
||||||
|
/**
|
||||||
|
* Recycles a client by setting its hit count to zero, and reset time to
|
||||||
|
* `windowMs` milliseconds from now.
|
||||||
|
*
|
||||||
|
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||||
|
* `current` and `previous` maps.
|
||||||
|
*
|
||||||
|
* @param client {Client} - The client to recycle.
|
||||||
|
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||||
|
*
|
||||||
|
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||||
|
*/
|
||||||
|
private resetClient;
|
||||||
|
/**
|
||||||
|
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||||
|
* returned is in the `current` map.
|
||||||
|
*
|
||||||
|
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||||
|
*
|
||||||
|
* @returns {Client} - The requested client.
|
||||||
|
*/
|
||||||
|
private getClient;
|
||||||
|
/**
|
||||||
|
* Move current clients to previous, create a new map for current.
|
||||||
|
*
|
||||||
|
* This function is called every `windowMs`.
|
||||||
|
*/
|
||||||
|
private clearExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
rateLimit as default,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
809
node_modules/express-rate-limit/dist/index.mjs
generated
vendored
Normal file
809
node_modules/express-rate-limit/dist/index.mjs
generated
vendored
Normal file
@ -0,0 +1,809 @@
|
|||||||
|
// source/headers.ts
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
|
||||||
|
var getResetSeconds = (resetTime, windowMs) => {
|
||||||
|
let resetSeconds = void 0;
|
||||||
|
if (resetTime) {
|
||||||
|
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
||||||
|
resetSeconds = Math.max(0, deltaSeconds);
|
||||||
|
} else if (windowMs) {
|
||||||
|
resetSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
}
|
||||||
|
return resetSeconds;
|
||||||
|
};
|
||||||
|
var getPartitionKey = (key) => {
|
||||||
|
const hash = createHash("sha256");
|
||||||
|
hash.update(key);
|
||||||
|
const partitionKey = hash.digest("hex").slice(0, 12);
|
||||||
|
return Buffer.from(partitionKey).toString("base64");
|
||||||
|
};
|
||||||
|
var setLegacyHeaders = (response, info) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
response.setHeader("X-RateLimit-Limit", info.limit.toString());
|
||||||
|
response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
|
||||||
|
if (info.resetTime instanceof Date) {
|
||||||
|
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
||||||
|
response.setHeader(
|
||||||
|
"X-RateLimit-Reset",
|
||||||
|
Math.ceil(info.resetTime.getTime() / 1e3).toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var setDraft6Headers = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime);
|
||||||
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
||||||
|
response.setHeader("RateLimit-Limit", info.limit.toString());
|
||||||
|
response.setHeader("RateLimit-Remaining", info.remaining.toString());
|
||||||
|
if (resetSeconds)
|
||||||
|
response.setHeader("RateLimit-Reset", resetSeconds.toString());
|
||||||
|
};
|
||||||
|
var setDraft7Headers = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
|
||||||
|
response.setHeader(
|
||||||
|
"RateLimit",
|
||||||
|
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
var setDraft8Headers = (response, info, windowMs, name, key) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
const partitionKey = getPartitionKey(key);
|
||||||
|
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
|
||||||
|
const header = `r=${info.remaining}; t=${resetSeconds}`;
|
||||||
|
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
|
||||||
|
response.append("RateLimit", `"${name}"; ${header}`);
|
||||||
|
};
|
||||||
|
var setRetryAfterHeader = (response, info, windowMs) => {
|
||||||
|
if (response.headersSent)
|
||||||
|
return;
|
||||||
|
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||||
|
response.setHeader("Retry-After", resetSeconds.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/validations.ts
|
||||||
|
import { isIP } from "net";
|
||||||
|
var ValidationError = class extends Error {
|
||||||
|
/**
|
||||||
|
* The code must be a string, in snake case and all capital, that starts with
|
||||||
|
* the substring `ERR_ERL_`.
|
||||||
|
*
|
||||||
|
* The message must be a string, starting with an uppercase character,
|
||||||
|
* describing the issue in detail.
|
||||||
|
*/
|
||||||
|
constructor(code, message) {
|
||||||
|
const url = `https://express-rate-limit.github.io/${code}/`;
|
||||||
|
super(`${message} See ${url} for more information.`);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.code = code;
|
||||||
|
this.help = url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ChangeWarning = class extends ValidationError {
|
||||||
|
};
|
||||||
|
var usedStores = /* @__PURE__ */ new Set();
|
||||||
|
var singleCountKeys = /* @__PURE__ */ new WeakMap();
|
||||||
|
var validations = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
enabled: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// Should be EnabledValidations type, but that's a circular reference
|
||||||
|
disable() {
|
||||||
|
for (const k of Object.keys(this.enabled))
|
||||||
|
this.enabled[k] = false;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks whether the IP address is valid, and that it does not have a port
|
||||||
|
* number in it.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
|
||||||
|
*
|
||||||
|
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
ip(ip) {
|
||||||
|
if (ip === void 0) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNDEFINED_IP_ADDRESS",
|
||||||
|
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!isIP(ip)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_INVALID_IP_ADDRESS",
|
||||||
|
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is not set to `true`.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
trustProxy(request) {
|
||||||
|
if (request.app.get("trust proxy") === true) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
|
||||||
|
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
|
||||||
|
* header is present.
|
||||||
|
*
|
||||||
|
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
xForwardedForHeader(request) {
|
||||||
|
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
|
||||||
|
`The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures totalHits value from store is a positive integer.
|
||||||
|
*
|
||||||
|
* @param hits {any} - The `totalHits` returned by the store.
|
||||||
|
*/
|
||||||
|
positiveHits(hits) {
|
||||||
|
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_INVALID_HITS",
|
||||||
|
`The totalHits value returned from the store must be a positive integer, got ${hits}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||||
|
*/
|
||||||
|
unsharedStore(store) {
|
||||||
|
if (usedStores.has(store)) {
|
||||||
|
const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_STORE_REUSE",
|
||||||
|
`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
usedStores.add(store);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ensures a given key is incremented only once per request.
|
||||||
|
*
|
||||||
|
* @param request {Request} - The Express request object.
|
||||||
|
* @param store {Store} - The store class.
|
||||||
|
* @param key {string} - The key used to store the client's hit count.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
singleCount(request, store, key) {
|
||||||
|
let storeKeys = singleCountKeys.get(request);
|
||||||
|
if (!storeKeys) {
|
||||||
|
storeKeys = /* @__PURE__ */ new Map();
|
||||||
|
singleCountKeys.set(request, storeKeys);
|
||||||
|
}
|
||||||
|
const storeKey = store.localKeys ? store : store.constructor.name;
|
||||||
|
let keys = storeKeys.get(storeKey);
|
||||||
|
if (!keys) {
|
||||||
|
keys = [];
|
||||||
|
storeKeys.set(storeKey, keys);
|
||||||
|
}
|
||||||
|
const prefixedKey = `${store.prefix ?? ""}${key}`;
|
||||||
|
if (keys.includes(prefixedKey)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_DOUBLE_COUNT",
|
||||||
|
`The hit count for ${key} was incremented more than once for a single request.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
keys.push(prefixedKey);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the behaviour for `max: 0` / `limit: 0` is
|
||||||
|
* changing in the next major release.
|
||||||
|
*
|
||||||
|
* @param limit {number} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
limit(limit) {
|
||||||
|
if (limit === 0) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_MAX_ZERO",
|
||||||
|
`Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
|
||||||
|
* and will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
draftPolliHeaders(draft_polli_ratelimit_headers) {
|
||||||
|
if (draft_polli_ratelimit_headers) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
|
||||||
|
`The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user that the `onLimitReached` option is deprecated and
|
||||||
|
* will be removed in the next major release.
|
||||||
|
*
|
||||||
|
* @param onLimitReached {any | undefined} - The maximum number of hits per client.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onLimitReached(onLimitReached) {
|
||||||
|
if (onLimitReached) {
|
||||||
|
throw new ChangeWarning(
|
||||||
|
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
||||||
|
`The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||||
|
*
|
||||||
|
* @param version {any | undefined} - The version passed by the user.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersDraftVersion(version) {
|
||||||
|
if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
|
||||||
|
const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
|
||||||
|
`standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Warns the user when the selected headers option requires a reset time but
|
||||||
|
* the store does not provide one.
|
||||||
|
*
|
||||||
|
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
headersResetTime(resetTime) {
|
||||||
|
if (!resetTime) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_HEADERS_NO_RESET",
|
||||||
|
`standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks the options.validate setting to ensure that only recognized
|
||||||
|
* validations are enabled or disabled.
|
||||||
|
*
|
||||||
|
* If any unrecognized values are found, an error is logged that
|
||||||
|
* includes the list of supported vaidations.
|
||||||
|
*/
|
||||||
|
validationsConfig() {
|
||||||
|
const supportedValidations = Object.keys(this).filter(
|
||||||
|
(k) => !["enabled", "disable"].includes(k)
|
||||||
|
);
|
||||||
|
supportedValidations.push("default");
|
||||||
|
for (const key of Object.keys(this.enabled)) {
|
||||||
|
if (!supportedValidations.includes(key)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_UNKNOWN_VALIDATION",
|
||||||
|
`options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
|
||||||
|
", "
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks to see if the instance was created inside of a request handler,
|
||||||
|
* which would prevent it from working correctly, with the default memory
|
||||||
|
* store (or any other store with localKeys.)
|
||||||
|
*/
|
||||||
|
creationStack(store) {
|
||||||
|
const { stack } = new Error(
|
||||||
|
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
|
||||||
|
);
|
||||||
|
if (stack?.includes("Layer.handle [as handle_request]")) {
|
||||||
|
if (!store.localKeys) {
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||||
|
"express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new ValidationError(
|
||||||
|
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||||
|
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var getValidations = (_enabled) => {
|
||||||
|
let enabled;
|
||||||
|
if (typeof _enabled === "boolean") {
|
||||||
|
enabled = {
|
||||||
|
default: _enabled
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
enabled = {
|
||||||
|
default: true,
|
||||||
|
..._enabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const wrappedValidations = {
|
||||||
|
enabled
|
||||||
|
};
|
||||||
|
for (const [name, validation] of Object.entries(validations)) {
|
||||||
|
if (typeof validation === "function")
|
||||||
|
wrappedValidations[name] = (...args) => {
|
||||||
|
if (!(enabled[name] ?? enabled.default)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
;
|
||||||
|
validation.apply(
|
||||||
|
wrappedValidations,
|
||||||
|
args
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ChangeWarning)
|
||||||
|
console.warn(error);
|
||||||
|
else
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return wrappedValidations;
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/memory-store.ts
|
||||||
|
var MemoryStore = class {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||||
|
* addresses or API keys).
|
||||||
|
*
|
||||||
|
* They are split into two to avoid having to iterate through the entire set to
|
||||||
|
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||||
|
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||||
|
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||||
|
* known to be expired and can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
this.previous = /* @__PURE__ */ new Map();
|
||||||
|
this.current = /* @__PURE__ */ new Map();
|
||||||
|
/**
|
||||||
|
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||||
|
* cannot affect other instances.
|
||||||
|
*/
|
||||||
|
this.localKeys = true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method that initializes the store.
|
||||||
|
*
|
||||||
|
* @param options {Options} - The options used to setup the middleware.
|
||||||
|
*/
|
||||||
|
init(options) {
|
||||||
|
this.windowMs = options.windowMs;
|
||||||
|
if (this.interval)
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.clearExpired();
|
||||||
|
}, this.windowMs);
|
||||||
|
if (this.interval.unref)
|
||||||
|
this.interval.unref();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to fetch a client's hit count and reset time.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async get(key) {
|
||||||
|
return this.current.get(key) ?? this.previous.get(key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to increment a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async increment(key) {
|
||||||
|
const client = this.getClient(key);
|
||||||
|
const now = Date.now();
|
||||||
|
if (client.resetTime.getTime() <= now) {
|
||||||
|
this.resetClient(client, now);
|
||||||
|
}
|
||||||
|
client.totalHits++;
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to decrement a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async decrement(key) {
|
||||||
|
const client = this.getClient(key);
|
||||||
|
if (client.totalHits > 0)
|
||||||
|
client.totalHits--;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to reset a client's hit counter.
|
||||||
|
*
|
||||||
|
* @param key {string} - The identifier for a client.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async resetKey(key) {
|
||||||
|
this.current.delete(key);
|
||||||
|
this.previous.delete(key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to reset everyone's hit counter.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
async resetAll() {
|
||||||
|
this.current.clear();
|
||||||
|
this.previous.clear();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method to stop the timer (if currently running) and prevent any memory
|
||||||
|
* leaks.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shutdown() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
void this.resetAll();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Recycles a client by setting its hit count to zero, and reset time to
|
||||||
|
* `windowMs` milliseconds from now.
|
||||||
|
*
|
||||||
|
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||||
|
* `current` and `previous` maps.
|
||||||
|
*
|
||||||
|
* @param client {Client} - The client to recycle.
|
||||||
|
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||||
|
*
|
||||||
|
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||||
|
*/
|
||||||
|
resetClient(client, now = Date.now()) {
|
||||||
|
client.totalHits = 0;
|
||||||
|
client.resetTime.setTime(now + this.windowMs);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||||
|
* returned is in the `current` map.
|
||||||
|
*
|
||||||
|
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||||
|
*
|
||||||
|
* @returns {Client} - The requested client.
|
||||||
|
*/
|
||||||
|
getClient(key) {
|
||||||
|
if (this.current.has(key))
|
||||||
|
return this.current.get(key);
|
||||||
|
let client;
|
||||||
|
if (this.previous.has(key)) {
|
||||||
|
client = this.previous.get(key);
|
||||||
|
this.previous.delete(key);
|
||||||
|
} else {
|
||||||
|
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
|
||||||
|
this.resetClient(client);
|
||||||
|
}
|
||||||
|
this.current.set(key, client);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Move current clients to previous, create a new map for current.
|
||||||
|
*
|
||||||
|
* This function is called every `windowMs`.
|
||||||
|
*/
|
||||||
|
clearExpired() {
|
||||||
|
this.previous = this.current;
|
||||||
|
this.current = /* @__PURE__ */ new Map();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// source/lib.ts
|
||||||
|
var isLegacyStore = (store) => (
|
||||||
|
// Check that `incr` exists but `increment` does not - store authors might want
|
||||||
|
// to keep both around for backwards compatibility.
|
||||||
|
typeof store.incr === "function" && typeof store.increment !== "function"
|
||||||
|
);
|
||||||
|
var promisifyStore = (passedStore) => {
|
||||||
|
if (!isLegacyStore(passedStore)) {
|
||||||
|
return passedStore;
|
||||||
|
}
|
||||||
|
const legacyStore = passedStore;
|
||||||
|
class PromisifiedStore {
|
||||||
|
async increment(key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
legacyStore.incr(
|
||||||
|
key,
|
||||||
|
(error, totalHits, resetTime) => {
|
||||||
|
if (error)
|
||||||
|
reject(error);
|
||||||
|
resolve({ totalHits, resetTime });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async decrement(key) {
|
||||||
|
return legacyStore.decrement(key);
|
||||||
|
}
|
||||||
|
async resetKey(key) {
|
||||||
|
return legacyStore.resetKey(key);
|
||||||
|
}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
async resetAll() {
|
||||||
|
if (typeof legacyStore.resetAll === "function")
|
||||||
|
return legacyStore.resetAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new PromisifiedStore();
|
||||||
|
};
|
||||||
|
var getOptionsFromConfig = (config) => {
|
||||||
|
const { validations: validations2, ...directlyPassableEntries } = config;
|
||||||
|
return {
|
||||||
|
...directlyPassableEntries,
|
||||||
|
validate: validations2.enabled
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var omitUndefinedOptions = (passedOptions) => {
|
||||||
|
const omittedOptions = {};
|
||||||
|
for (const k of Object.keys(passedOptions)) {
|
||||||
|
const key = k;
|
||||||
|
if (passedOptions[key] !== void 0) {
|
||||||
|
omittedOptions[key] = passedOptions[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return omittedOptions;
|
||||||
|
};
|
||||||
|
var parseOptions = (passedOptions) => {
|
||||||
|
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
||||||
|
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
|
||||||
|
validations2.validationsConfig();
|
||||||
|
validations2.draftPolliHeaders(
|
||||||
|
// @ts-expect-error see the note above.
|
||||||
|
notUndefinedOptions.draft_polli_ratelimit_headers
|
||||||
|
);
|
||||||
|
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
|
||||||
|
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
|
||||||
|
if (standardHeaders === true)
|
||||||
|
standardHeaders = "draft-6";
|
||||||
|
const config = {
|
||||||
|
windowMs: 60 * 1e3,
|
||||||
|
limit: passedOptions.max ?? 5,
|
||||||
|
// `max` is deprecated, but support it anyways.
|
||||||
|
message: "Too many requests, please try again later.",
|
||||||
|
statusCode: 429,
|
||||||
|
legacyHeaders: passedOptions.headers ?? true,
|
||||||
|
identifier(request, _response) {
|
||||||
|
let duration = "";
|
||||||
|
const property = config.requestPropertyName;
|
||||||
|
const { limit } = request[property];
|
||||||
|
const seconds = config.windowMs / 1e3;
|
||||||
|
const minutes = config.windowMs / (1e3 * 60);
|
||||||
|
const hours = config.windowMs / (1e3 * 60 * 60);
|
||||||
|
const days = config.windowMs / (1e3 * 60 * 60 * 24);
|
||||||
|
if (seconds < 60)
|
||||||
|
duration = `${seconds}sec`;
|
||||||
|
else if (minutes < 60)
|
||||||
|
duration = `${minutes}min`;
|
||||||
|
else if (hours < 24)
|
||||||
|
duration = `${hours}hr${hours > 1 ? "s" : ""}`;
|
||||||
|
else
|
||||||
|
duration = `${days}day${days > 1 ? "s" : ""}`;
|
||||||
|
return `${limit}-in-${duration}`;
|
||||||
|
},
|
||||||
|
requestPropertyName: "rateLimit",
|
||||||
|
skipFailedRequests: false,
|
||||||
|
skipSuccessfulRequests: false,
|
||||||
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
||||||
|
skip: (_request, _response) => false,
|
||||||
|
keyGenerator(request, _response) {
|
||||||
|
validations2.ip(request.ip);
|
||||||
|
validations2.trustProxy(request);
|
||||||
|
validations2.xForwardedForHeader(request);
|
||||||
|
return request.ip;
|
||||||
|
},
|
||||||
|
async handler(request, response, _next, _optionsUsed) {
|
||||||
|
response.status(config.statusCode);
|
||||||
|
const message = typeof config.message === "function" ? await config.message(
|
||||||
|
request,
|
||||||
|
response
|
||||||
|
) : config.message;
|
||||||
|
if (!response.writableEnded) {
|
||||||
|
response.send(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
passOnStoreError: false,
|
||||||
|
// Allow the default options to be overriden by the passed options.
|
||||||
|
...notUndefinedOptions,
|
||||||
|
// `standardHeaders` is resolved into a draft version above, use that.
|
||||||
|
standardHeaders,
|
||||||
|
// Note that this field is declared after the user's options are spread in,
|
||||||
|
// so that this field doesn't get overriden with an un-promisified store!
|
||||||
|
store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
|
||||||
|
// Print an error to the console if a few known misconfigurations are detected.
|
||||||
|
validations: validations2
|
||||||
|
};
|
||||||
|
if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
|
||||||
|
throw new TypeError(
|
||||||
|
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
var handleAsyncErrors = (fn) => async (request, response, next) => {
|
||||||
|
try {
|
||||||
|
await Promise.resolve(fn(request, response, next)).catch(next);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var rateLimit = (passedOptions) => {
|
||||||
|
const config = parseOptions(passedOptions ?? {});
|
||||||
|
const options = getOptionsFromConfig(config);
|
||||||
|
config.validations.creationStack(config.store);
|
||||||
|
config.validations.unsharedStore(config.store);
|
||||||
|
if (typeof config.store.init === "function")
|
||||||
|
config.store.init(options);
|
||||||
|
const middleware = handleAsyncErrors(
|
||||||
|
async (request, response, next) => {
|
||||||
|
const skip = await config.skip(request, response);
|
||||||
|
if (skip) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const augmentedRequest = request;
|
||||||
|
const key = await config.keyGenerator(request, response);
|
||||||
|
let totalHits = 0;
|
||||||
|
let resetTime;
|
||||||
|
try {
|
||||||
|
const incrementResult = await config.store.increment(key);
|
||||||
|
totalHits = incrementResult.totalHits;
|
||||||
|
resetTime = incrementResult.resetTime;
|
||||||
|
} catch (error) {
|
||||||
|
if (config.passOnStoreError) {
|
||||||
|
console.error(
|
||||||
|
"express-rate-limit: error from store, allowing request without rate-limiting.",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
config.validations.positiveHits(totalHits);
|
||||||
|
config.validations.singleCount(request, config.store, key);
|
||||||
|
const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
|
||||||
|
const limit = await retrieveLimit;
|
||||||
|
config.validations.limit(limit);
|
||||||
|
const info = {
|
||||||
|
limit,
|
||||||
|
used: totalHits,
|
||||||
|
remaining: Math.max(limit - totalHits, 0),
|
||||||
|
resetTime
|
||||||
|
};
|
||||||
|
Object.defineProperty(info, "current", {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
value: totalHits
|
||||||
|
});
|
||||||
|
augmentedRequest[config.requestPropertyName] = info;
|
||||||
|
if (config.legacyHeaders && !response.headersSent) {
|
||||||
|
setLegacyHeaders(response, info);
|
||||||
|
}
|
||||||
|
if (config.standardHeaders && !response.headersSent) {
|
||||||
|
switch (config.standardHeaders) {
|
||||||
|
case "draft-6": {
|
||||||
|
setDraft6Headers(response, info, config.windowMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "draft-7": {
|
||||||
|
config.validations.headersResetTime(info.resetTime);
|
||||||
|
setDraft7Headers(response, info, config.windowMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "draft-8": {
|
||||||
|
const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
|
||||||
|
const name = await retrieveName;
|
||||||
|
config.validations.headersResetTime(info.resetTime);
|
||||||
|
setDraft8Headers(response, info, config.windowMs, name, key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
config.validations.headersDraftVersion(config.standardHeaders);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
|
||||||
|
let decremented = false;
|
||||||
|
const decrementKey = async () => {
|
||||||
|
if (!decremented) {
|
||||||
|
await config.store.decrement(key);
|
||||||
|
decremented = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (config.skipFailedRequests) {
|
||||||
|
response.on("finish", async () => {
|
||||||
|
if (!await config.requestWasSuccessful(request, response))
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
response.on("close", async () => {
|
||||||
|
if (!response.writableEnded)
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
response.on("error", async () => {
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.skipSuccessfulRequests) {
|
||||||
|
response.on("finish", async () => {
|
||||||
|
if (await config.requestWasSuccessful(request, response))
|
||||||
|
await decrementKey();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.validations.disable();
|
||||||
|
if (totalHits > limit) {
|
||||||
|
if (config.legacyHeaders || config.standardHeaders) {
|
||||||
|
setRetryAfterHeader(response, info, config.windowMs);
|
||||||
|
}
|
||||||
|
config.handler(request, response, next, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const getThrowFn = () => {
|
||||||
|
throw new Error("The current store does not support the get/getKey method");
|
||||||
|
};
|
||||||
|
middleware.resetKey = config.store.resetKey.bind(config.store);
|
||||||
|
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
|
||||||
|
return middleware;
|
||||||
|
};
|
||||||
|
var lib_default = rateLimit;
|
||||||
|
export {
|
||||||
|
MemoryStore,
|
||||||
|
lib_default as default,
|
||||||
|
lib_default as rateLimit
|
||||||
|
};
|
133
node_modules/express-rate-limit/package.json
generated
vendored
Normal file
133
node_modules/express-rate-limit/package.json
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
{
|
||||||
|
"name": "express-rate-limit",
|
||||||
|
"version": "7.5.0",
|
||||||
|
"description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
|
||||||
|
"author": {
|
||||||
|
"name": "Nathan Friedly",
|
||||||
|
"url": "http://nfriedly.com/"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/express-rate-limit/express-rate-limit",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/express-rate-limit/express-rate-limit.git"
|
||||||
|
},
|
||||||
|
"funding": "https://github.com/sponsors/express-rate-limit",
|
||||||
|
"keywords": [
|
||||||
|
"express-rate-limit",
|
||||||
|
"express",
|
||||||
|
"rate",
|
||||||
|
"limit",
|
||||||
|
"ratelimit",
|
||||||
|
"rate-limit",
|
||||||
|
"middleware",
|
||||||
|
"ip",
|
||||||
|
"auth",
|
||||||
|
"authorization",
|
||||||
|
"security",
|
||||||
|
"brute",
|
||||||
|
"force",
|
||||||
|
"bruteforce",
|
||||||
|
"brute-force",
|
||||||
|
"attack"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/index.d.mts",
|
||||||
|
"default": "./dist/index.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/index.d.cts",
|
||||||
|
"default": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/",
|
||||||
|
"tsconfig.json"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
|
||||||
|
"build:cjs": "esbuild --platform=node --bundle --target=es2022 --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
|
||||||
|
"build:esm": "esbuild --platform=node --bundle --target=es2022 --format=esm --outfile=dist/index.mjs source/index.ts",
|
||||||
|
"build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts && cp dist/index.d.ts dist/index.d.cts && cp dist/index.d.ts dist/index.d.mts",
|
||||||
|
"compile": "run-s clean build:*",
|
||||||
|
"docs": "cd docs && mintlify dev",
|
||||||
|
"lint:code": "xo",
|
||||||
|
"lint:rest": "prettier --check .",
|
||||||
|
"lint": "run-s lint:*",
|
||||||
|
"format:code": "xo --fix",
|
||||||
|
"format:rest": "prettier --write .",
|
||||||
|
"format": "run-s format:*",
|
||||||
|
"test:lib": "jest",
|
||||||
|
"test:ext": "cd test/external/ && bash run-all-tests",
|
||||||
|
"test": "run-s lint test:lib",
|
||||||
|
"pre-commit": "lint-staged",
|
||||||
|
"prepare": "run-s compile && husky install config/husky"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@express-rate-limit/prettier": "1.1.1",
|
||||||
|
"@express-rate-limit/tsconfig": "1.0.2",
|
||||||
|
"@jest/globals": "29.7.0",
|
||||||
|
"@types/express": "4.17.20",
|
||||||
|
"@types/jest": "29.5.6",
|
||||||
|
"@types/node": "20.8.7",
|
||||||
|
"@types/supertest": "2.0.15",
|
||||||
|
"del-cli": "5.1.0",
|
||||||
|
"dts-bundle-generator": "8.0.1",
|
||||||
|
"esbuild": "0.19.5",
|
||||||
|
"express": "4.21.1",
|
||||||
|
"husky": "8.0.3",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"lint-staged": "15.0.2",
|
||||||
|
"mintlify": "4.0.63",
|
||||||
|
"npm-run-all": "4.1.5",
|
||||||
|
"ratelimit-header-parser": "0.1.0",
|
||||||
|
"supertest": "6.3.3",
|
||||||
|
"ts-jest": "29.1.1",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"typescript": "5.2.2",
|
||||||
|
"xo": "0.56.0"
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"prettier": true,
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-empty-function": 0,
|
||||||
|
"@typescript-eslint/no-dynamic-delete": 0,
|
||||||
|
"@typescript-eslint/no-confusing-void-expression": 0,
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": [
|
||||||
|
"error",
|
||||||
|
"index-signature"
|
||||||
|
],
|
||||||
|
"n/no-unsupported-features/es-syntax": 0
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "test/library/*.ts",
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-argument": 0,
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"test/external"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"prettier": "@express-rate-limit/prettier",
|
||||||
|
"lint-staged": {
|
||||||
|
"{source,test}/**/*.ts": "xo --fix",
|
||||||
|
"**/*.{json,yaml,md}": "prettier --write "
|
||||||
|
}
|
||||||
|
}
|
8
node_modules/express-rate-limit/tsconfig.json
generated
vendored
Normal file
8
node_modules/express-rate-limit/tsconfig.json
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"include": ["source/"],
|
||||||
|
"exclude": ["node_modules/"],
|
||||||
|
"extends": "@express-rate-limit/tsconfig",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020"
|
||||||
|
}
|
||||||
|
}
|
693
node_modules/express-session/index.js
generated
vendored
Normal file
693
node_modules/express-session/index.js
generated
vendored
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
/*!
|
||||||
|
* express-session
|
||||||
|
* Copyright(c) 2010 Sencha Inc.
|
||||||
|
* Copyright(c) 2011 TJ Holowaychuk
|
||||||
|
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Buffer = require('safe-buffer').Buffer
|
||||||
|
var cookie = require('cookie');
|
||||||
|
var crypto = require('crypto')
|
||||||
|
var debug = require('debug')('express-session');
|
||||||
|
var deprecate = require('depd')('express-session');
|
||||||
|
var onHeaders = require('on-headers')
|
||||||
|
var parseUrl = require('parseurl');
|
||||||
|
var signature = require('cookie-signature')
|
||||||
|
var uid = require('uid-safe').sync
|
||||||
|
|
||||||
|
var Cookie = require('./session/cookie')
|
||||||
|
var MemoryStore = require('./session/memory')
|
||||||
|
var Session = require('./session/session')
|
||||||
|
var Store = require('./session/store')
|
||||||
|
|
||||||
|
// environment
|
||||||
|
|
||||||
|
var env = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose the middleware.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports = module.exports = session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose constructors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.Store = Store;
|
||||||
|
exports.Cookie = Cookie;
|
||||||
|
exports.Session = Session;
|
||||||
|
exports.MemoryStore = MemoryStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning message for `MemoryStore` usage in production.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var warning = 'Warning: connect.session() MemoryStore is not\n'
|
||||||
|
+ 'designed for a production environment, as it will leak\n'
|
||||||
|
+ 'memory, and will not scale past a single process.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node.js 0.8+ async implementation.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
var defer = typeof setImmediate === 'function'
|
||||||
|
? setImmediate
|
||||||
|
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup session store with the given `options`.
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Object} [options.cookie] Options for cookie
|
||||||
|
* @param {Function} [options.genid]
|
||||||
|
* @param {String} [options.name=connect.sid] Session ID cookie name
|
||||||
|
* @param {Boolean} [options.proxy]
|
||||||
|
* @param {Boolean} [options.resave] Resave unmodified sessions back to the store
|
||||||
|
* @param {Boolean} [options.rolling] Enable/disable rolling session expiration
|
||||||
|
* @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
|
||||||
|
* @param {String|Array} [options.secret] Secret for signing session ID
|
||||||
|
* @param {Object} [options.store=MemoryStore] Session store
|
||||||
|
* @param {String} [options.unset]
|
||||||
|
* @return {Function} middleware
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function session(options) {
|
||||||
|
var opts = options || {}
|
||||||
|
|
||||||
|
// get the cookie options
|
||||||
|
var cookieOptions = opts.cookie || {}
|
||||||
|
|
||||||
|
// get the session id generate function
|
||||||
|
var generateId = opts.genid || generateSessionId
|
||||||
|
|
||||||
|
// get the session cookie name
|
||||||
|
var name = opts.name || opts.key || 'connect.sid'
|
||||||
|
|
||||||
|
// get the session store
|
||||||
|
var store = opts.store || new MemoryStore()
|
||||||
|
|
||||||
|
// get the trust proxy setting
|
||||||
|
var trustProxy = opts.proxy
|
||||||
|
|
||||||
|
// get the resave session option
|
||||||
|
var resaveSession = opts.resave;
|
||||||
|
|
||||||
|
// get the rolling session option
|
||||||
|
var rollingSessions = Boolean(opts.rolling)
|
||||||
|
|
||||||
|
// get the save uninitialized session option
|
||||||
|
var saveUninitializedSession = opts.saveUninitialized
|
||||||
|
|
||||||
|
// get the cookie signing secret
|
||||||
|
var secret = opts.secret
|
||||||
|
|
||||||
|
if (typeof generateId !== 'function') {
|
||||||
|
throw new TypeError('genid option must be a function');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resaveSession === undefined) {
|
||||||
|
deprecate('undefined resave option; provide resave option');
|
||||||
|
resaveSession = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveUninitializedSession === undefined) {
|
||||||
|
deprecate('undefined saveUninitialized option; provide saveUninitialized option');
|
||||||
|
saveUninitializedSession = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.unset && opts.unset !== 'destroy' && opts.unset !== 'keep') {
|
||||||
|
throw new TypeError('unset option must be "destroy" or "keep"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: switch to "destroy" on next major
|
||||||
|
var unsetDestroy = opts.unset === 'destroy'
|
||||||
|
|
||||||
|
if (Array.isArray(secret) && secret.length === 0) {
|
||||||
|
throw new TypeError('secret option array must contain one or more strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secret && !Array.isArray(secret)) {
|
||||||
|
secret = [secret];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secret) {
|
||||||
|
deprecate('req.secret; provide secret option');
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify user that this store is not
|
||||||
|
// meant for a production environment
|
||||||
|
/* istanbul ignore next: not tested */
|
||||||
|
if (env === 'production' && store instanceof MemoryStore) {
|
||||||
|
console.warn(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generates the new session
|
||||||
|
store.generate = function(req){
|
||||||
|
req.sessionID = generateId(req);
|
||||||
|
req.session = new Session(req);
|
||||||
|
req.session.cookie = new Cookie(cookieOptions);
|
||||||
|
|
||||||
|
if (cookieOptions.secure === 'auto') {
|
||||||
|
req.session.cookie.secure = issecure(req, trustProxy);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var storeImplementsTouch = typeof store.touch === 'function';
|
||||||
|
|
||||||
|
// register event listeners for the store to track readiness
|
||||||
|
var storeReady = true
|
||||||
|
store.on('disconnect', function ondisconnect() {
|
||||||
|
storeReady = false
|
||||||
|
})
|
||||||
|
store.on('connect', function onconnect() {
|
||||||
|
storeReady = true
|
||||||
|
})
|
||||||
|
|
||||||
|
return function session(req, res, next) {
|
||||||
|
// self-awareness
|
||||||
|
if (req.session) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connection as if there is no session if
|
||||||
|
// the store has temporarily disconnected etc
|
||||||
|
if (!storeReady) {
|
||||||
|
debug('store is disconnected')
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathname mismatch
|
||||||
|
var originalPath = parseUrl.original(req).pathname || '/'
|
||||||
|
if (originalPath.indexOf(cookieOptions.path || '/') !== 0) {
|
||||||
|
debug('pathname mismatch')
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure a secret is available or bail
|
||||||
|
if (!secret && !req.secret) {
|
||||||
|
next(new Error('secret option required for sessions'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// backwards compatibility for signed cookies
|
||||||
|
// req.secret is passed from the cookie parser middleware
|
||||||
|
var secrets = secret || [req.secret];
|
||||||
|
|
||||||
|
var originalHash;
|
||||||
|
var originalId;
|
||||||
|
var savedHash;
|
||||||
|
var touched = false
|
||||||
|
|
||||||
|
// expose store
|
||||||
|
req.sessionStore = store;
|
||||||
|
|
||||||
|
// get the session ID from the cookie
|
||||||
|
var cookieId = req.sessionID = getcookie(req, name, secrets);
|
||||||
|
|
||||||
|
// set-cookie
|
||||||
|
onHeaders(res, function(){
|
||||||
|
if (!req.session) {
|
||||||
|
debug('no session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldSetCookie(req)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only send secure cookies via https
|
||||||
|
if (req.session.cookie.secure && !issecure(req, trustProxy)) {
|
||||||
|
debug('not secured');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!touched) {
|
||||||
|
// touch session
|
||||||
|
req.session.touch()
|
||||||
|
touched = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie
|
||||||
|
try {
|
||||||
|
setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data)
|
||||||
|
} catch (err) {
|
||||||
|
defer(next, err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// proxy end() to commit the session
|
||||||
|
var _end = res.end;
|
||||||
|
var _write = res.write;
|
||||||
|
var ended = false;
|
||||||
|
res.end = function end(chunk, encoding) {
|
||||||
|
if (ended) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ended = true;
|
||||||
|
|
||||||
|
var ret;
|
||||||
|
var sync = true;
|
||||||
|
|
||||||
|
function writeend() {
|
||||||
|
if (sync) {
|
||||||
|
ret = _end.call(res, chunk, encoding);
|
||||||
|
sync = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_end.call(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writetop() {
|
||||||
|
if (!sync) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res._header) {
|
||||||
|
res._implicitHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk == null) {
|
||||||
|
ret = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentLength = Number(res.getHeader('Content-Length'));
|
||||||
|
|
||||||
|
if (!isNaN(contentLength) && contentLength > 0) {
|
||||||
|
// measure chunk
|
||||||
|
chunk = !Buffer.isBuffer(chunk)
|
||||||
|
? Buffer.from(chunk, encoding)
|
||||||
|
: chunk;
|
||||||
|
encoding = undefined;
|
||||||
|
|
||||||
|
if (chunk.length !== 0) {
|
||||||
|
debug('split response');
|
||||||
|
ret = _write.call(res, chunk.slice(0, chunk.length - 1));
|
||||||
|
chunk = chunk.slice(chunk.length - 1, chunk.length);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = _write.call(res, chunk, encoding);
|
||||||
|
sync = false;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDestroy(req)) {
|
||||||
|
// destroy session
|
||||||
|
debug('destroying');
|
||||||
|
store.destroy(req.sessionID, function ondestroy(err) {
|
||||||
|
if (err) {
|
||||||
|
defer(next, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('destroyed');
|
||||||
|
writeend();
|
||||||
|
});
|
||||||
|
|
||||||
|
return writetop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// no session to save
|
||||||
|
if (!req.session) {
|
||||||
|
debug('no session');
|
||||||
|
return _end.call(res, chunk, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!touched) {
|
||||||
|
// touch session
|
||||||
|
req.session.touch()
|
||||||
|
touched = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSave(req)) {
|
||||||
|
req.session.save(function onsave(err) {
|
||||||
|
if (err) {
|
||||||
|
defer(next, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeend();
|
||||||
|
});
|
||||||
|
|
||||||
|
return writetop();
|
||||||
|
} else if (storeImplementsTouch && shouldTouch(req)) {
|
||||||
|
// store implements touch method
|
||||||
|
debug('touching');
|
||||||
|
store.touch(req.sessionID, req.session, function ontouch(err) {
|
||||||
|
if (err) {
|
||||||
|
defer(next, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('touched');
|
||||||
|
writeend();
|
||||||
|
});
|
||||||
|
|
||||||
|
return writetop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _end.call(res, chunk, encoding);
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate the session
|
||||||
|
function generate() {
|
||||||
|
store.generate(req);
|
||||||
|
originalId = req.sessionID;
|
||||||
|
originalHash = hash(req.session);
|
||||||
|
wrapmethods(req.session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inflate the session
|
||||||
|
function inflate (req, sess) {
|
||||||
|
store.createSession(req, sess)
|
||||||
|
originalId = req.sessionID
|
||||||
|
originalHash = hash(sess)
|
||||||
|
|
||||||
|
if (!resaveSession) {
|
||||||
|
savedHash = originalHash
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapmethods(req.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewrapmethods (sess, callback) {
|
||||||
|
return function () {
|
||||||
|
if (req.session !== sess) {
|
||||||
|
wrapmethods(req.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap session methods
|
||||||
|
function wrapmethods(sess) {
|
||||||
|
var _reload = sess.reload
|
||||||
|
var _save = sess.save;
|
||||||
|
|
||||||
|
function reload(callback) {
|
||||||
|
debug('reloading %s', this.id)
|
||||||
|
_reload.call(this, rewrapmethods(this, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
debug('saving %s', this.id);
|
||||||
|
savedHash = hash(this);
|
||||||
|
_save.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(sess, 'reload', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: reload,
|
||||||
|
writable: true
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.defineProperty(sess, 'save', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: save,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if session has been modified
|
||||||
|
function isModified(sess) {
|
||||||
|
return originalId !== sess.id || originalHash !== hash(sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if session has been saved
|
||||||
|
function isSaved(sess) {
|
||||||
|
return originalId === sess.id && savedHash === hash(sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if session should be destroyed
|
||||||
|
function shouldDestroy(req) {
|
||||||
|
return req.sessionID && unsetDestroy && req.session == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if session should be saved to store
|
||||||
|
function shouldSave(req) {
|
||||||
|
// cannot set cookie without a session ID
|
||||||
|
if (typeof req.sessionID !== 'string') {
|
||||||
|
debug('session ignored because of bogus req.sessionID %o', req.sessionID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !saveUninitializedSession && !savedHash && cookieId !== req.sessionID
|
||||||
|
? isModified(req.session)
|
||||||
|
: !isSaved(req.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if session should be touched
|
||||||
|
function shouldTouch(req) {
|
||||||
|
// cannot set cookie without a session ID
|
||||||
|
if (typeof req.sessionID !== 'string') {
|
||||||
|
debug('session ignored because of bogus req.sessionID %o', req.sessionID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookieId === req.sessionID && !shouldSave(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if cookie should be set on response
|
||||||
|
function shouldSetCookie(req) {
|
||||||
|
// cannot set cookie without a session ID
|
||||||
|
if (typeof req.sessionID !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookieId !== req.sessionID
|
||||||
|
? saveUninitializedSession || isModified(req.session)
|
||||||
|
: rollingSessions || req.session.cookie.expires != null && isModified(req.session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a session if the browser doesn't send a sessionID
|
||||||
|
if (!req.sessionID) {
|
||||||
|
debug('no SID sent, generating session');
|
||||||
|
generate();
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the session object
|
||||||
|
debug('fetching %s', req.sessionID);
|
||||||
|
store.get(req.sessionID, function(err, sess){
|
||||||
|
// error handling
|
||||||
|
if (err && err.code !== 'ENOENT') {
|
||||||
|
debug('error %j', err);
|
||||||
|
next(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (err || !sess) {
|
||||||
|
debug('no session found')
|
||||||
|
generate()
|
||||||
|
} else {
|
||||||
|
debug('session found')
|
||||||
|
inflate(req, sess)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a session ID for a new session.
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateSessionId(sess) {
|
||||||
|
return uid(24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the session ID cookie from request.
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getcookie(req, name, secrets) {
|
||||||
|
var header = req.headers.cookie;
|
||||||
|
var raw;
|
||||||
|
var val;
|
||||||
|
|
||||||
|
// read from cookie header
|
||||||
|
if (header) {
|
||||||
|
var cookies = cookie.parse(header);
|
||||||
|
|
||||||
|
raw = cookies[name];
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
if (raw.substr(0, 2) === 's:') {
|
||||||
|
val = unsigncookie(raw.slice(2), secrets);
|
||||||
|
|
||||||
|
if (val === false) {
|
||||||
|
debug('cookie signature invalid');
|
||||||
|
val = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('cookie unsigned')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// back-compat read from cookieParser() signedCookies data
|
||||||
|
if (!val && req.signedCookies) {
|
||||||
|
val = req.signedCookies[name];
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
deprecate('cookie should be available in req.headers.cookie');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// back-compat read from cookieParser() cookies data
|
||||||
|
if (!val && req.cookies) {
|
||||||
|
raw = req.cookies[name];
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
if (raw.substr(0, 2) === 's:') {
|
||||||
|
val = unsigncookie(raw.slice(2), secrets);
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
deprecate('cookie should be available in req.headers.cookie');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === false) {
|
||||||
|
debug('cookie signature invalid');
|
||||||
|
val = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('cookie unsigned')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the given `sess` object omitting changes to `.cookie`.
|
||||||
|
*
|
||||||
|
* @param {Object} sess
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function hash(sess) {
|
||||||
|
// serialize
|
||||||
|
var str = JSON.stringify(sess, function (key, val) {
|
||||||
|
// ignore sess.cookie property
|
||||||
|
if (this === sess && key === 'cookie') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
|
||||||
|
// hash
|
||||||
|
return crypto
|
||||||
|
.createHash('sha1')
|
||||||
|
.update(str, 'utf8')
|
||||||
|
.digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if request is secure.
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Boolean} [trustProxy]
|
||||||
|
* @return {Boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function issecure(req, trustProxy) {
|
||||||
|
// socket is https server
|
||||||
|
if (req.connection && req.connection.encrypted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not trust proxy
|
||||||
|
if (trustProxy === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no explicit trust; try req.secure from express
|
||||||
|
if (trustProxy !== true) {
|
||||||
|
return req.secure === true
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the proto from x-forwarded-proto header
|
||||||
|
var header = req.headers['x-forwarded-proto'] || '';
|
||||||
|
var index = header.indexOf(',');
|
||||||
|
var proto = index !== -1
|
||||||
|
? header.substr(0, index).toLowerCase().trim()
|
||||||
|
: header.toLowerCase().trim()
|
||||||
|
|
||||||
|
return proto === 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cookie on response.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setcookie(res, name, val, secret, options) {
|
||||||
|
var signed = 's:' + signature.sign(val, secret);
|
||||||
|
var data = cookie.serialize(name, signed, options);
|
||||||
|
|
||||||
|
debug('set-cookie %s', data);
|
||||||
|
|
||||||
|
var prev = res.getHeader('Set-Cookie') || []
|
||||||
|
var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
|
||||||
|
|
||||||
|
res.setHeader('Set-Cookie', header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify and decode the given `val` with `secrets`.
|
||||||
|
*
|
||||||
|
* @param {String} val
|
||||||
|
* @param {Array} secrets
|
||||||
|
* @returns {String|Boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function unsigncookie(val, secrets) {
|
||||||
|
for (var i = 0; i < secrets.length; i++) {
|
||||||
|
var result = signature.unsign(val, secrets[i]);
|
||||||
|
|
||||||
|
if (result !== false) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
51
node_modules/express-session/node_modules/cookie-signature/index.js
generated
vendored
Normal file
51
node_modules/express-session/node_modules/cookie-signature/index.js
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the given `val` with `secret`.
|
||||||
|
*
|
||||||
|
* @param {String} val
|
||||||
|
* @param {String|NodeJS.ArrayBufferView|crypto.KeyObject} secret
|
||||||
|
* @return {String}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.sign = function(val, secret){
|
||||||
|
if ('string' !== typeof val) throw new TypeError("Cookie value must be provided as a string.");
|
||||||
|
if (null == secret) throw new TypeError("Secret key must be provided.");
|
||||||
|
return val + '.' + crypto
|
||||||
|
.createHmac('sha256', secret)
|
||||||
|
.update(val)
|
||||||
|
.digest('base64')
|
||||||
|
.replace(/\=+$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsign and decode the given `val` with `secret`,
|
||||||
|
* returning `false` if the signature is invalid.
|
||||||
|
*
|
||||||
|
* @param {String} val
|
||||||
|
* @param {String|NodeJS.ArrayBufferView|crypto.KeyObject} secret
|
||||||
|
* @return {String|Boolean}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.unsign = function(val, secret){
|
||||||
|
if ('string' !== typeof val) throw new TypeError("Signed cookie string must be provided.");
|
||||||
|
if (null == secret) throw new TypeError("Secret key must be provided.");
|
||||||
|
var str = val.slice(0, val.lastIndexOf('.'))
|
||||||
|
, mac = exports.sign(str, secret);
|
||||||
|
|
||||||
|
return sha1(mac) == sha1(val) ? str : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function sha1(str){
|
||||||
|
return crypto.createHash('sha1').update(str).digest('hex');
|
||||||
|
}
|
18
node_modules/express-session/node_modules/cookie-signature/package.json
generated
vendored
Normal file
18
node_modules/express-session/node_modules/cookie-signature/package.json
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "cookie-signature",
|
||||||
|
"version": "1.0.7",
|
||||||
|
"description": "Sign and unsign cookies",
|
||||||
|
"keywords": ["cookie", "sign", "unsign"],
|
||||||
|
"author": "TJ Holowaychuk <tj@learnboost.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": { "type": "git", "url": "https://github.com/visionmedia/node-cookie-signature.git"},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "*",
|
||||||
|
"should": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --require should --reporter spec"
|
||||||
|
},
|
||||||
|
"main": "index"
|
||||||
|
}
|
335
node_modules/express-session/node_modules/cookie/index.js
generated
vendored
Normal file
335
node_modules/express-session/node_modules/cookie/index.js
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
/*!
|
||||||
|
* cookie
|
||||||
|
* Copyright(c) 2012-2014 Roman Shtylman
|
||||||
|
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.parse = parse;
|
||||||
|
exports.serialize = serialize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module variables.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var __toString = Object.prototype.toString
|
||||||
|
var __hasOwnProperty = Object.prototype.hasOwnProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match cookie-name in RFC 6265 sec 4.1.1
|
||||||
|
* This refers out to the obsoleted definition of token in RFC 2616 sec 2.2
|
||||||
|
* which has been replaced by the token definition in RFC 7230 appendix B.
|
||||||
|
*
|
||||||
|
* cookie-name = token
|
||||||
|
* token = 1*tchar
|
||||||
|
* tchar = "!" / "#" / "$" / "%" / "&" / "'" /
|
||||||
|
* "*" / "+" / "-" / "." / "^" / "_" /
|
||||||
|
* "`" / "|" / "~" / DIGIT / ALPHA
|
||||||
|
*/
|
||||||
|
|
||||||
|
var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match cookie-value in RFC 6265 sec 4.1.1
|
||||||
|
*
|
||||||
|
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||||
|
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||||
|
* ; US-ASCII characters excluding CTLs,
|
||||||
|
* ; whitespace DQUOTE, comma, semicolon,
|
||||||
|
* ; and backslash
|
||||||
|
*/
|
||||||
|
|
||||||
|
var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match domain-value in RFC 6265 sec 4.1.1
|
||||||
|
*
|
||||||
|
* domain-value = <subdomain>
|
||||||
|
* ; defined in [RFC1034], Section 3.5, as
|
||||||
|
* ; enhanced by [RFC1123], Section 2.1
|
||||||
|
* <subdomain> = <label> | <subdomain> "." <label>
|
||||||
|
* <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
||||||
|
* Labels must be 63 characters or less.
|
||||||
|
* 'let-dig' not 'letter' in the first char, per RFC1123
|
||||||
|
* <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
||||||
|
* <let-dig-hyp> = <let-dig> | "-"
|
||||||
|
* <let-dig> = <letter> | <digit>
|
||||||
|
* <letter> = any one of the 52 alphabetic characters A through Z in
|
||||||
|
* upper case and a through z in lower case
|
||||||
|
* <digit> = any one of the ten digits 0 through 9
|
||||||
|
*
|
||||||
|
* Keep support for leading dot: https://github.com/jshttp/cookie/issues/173
|
||||||
|
*
|
||||||
|
* > (Note that a leading %x2E ("."), if present, is ignored even though that
|
||||||
|
* character is not permitted, but a trailing %x2E ("."), if present, will
|
||||||
|
* cause the user agent to ignore the attribute.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
var domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegExp to match path-value in RFC 6265 sec 4.1.1
|
||||||
|
*
|
||||||
|
* path-value = <any CHAR except CTLs or ";">
|
||||||
|
* CHAR = %x01-7F
|
||||||
|
* ; defined in RFC 5234 appendix B.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
var pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a cookie header.
|
||||||
|
*
|
||||||
|
* Parse the given cookie header string into an object
|
||||||
|
* The object has the various cookies as keys(names) => values
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @param {object} [opt]
|
||||||
|
* @return {object}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse(str, opt) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new TypeError('argument str must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = {};
|
||||||
|
var len = str.length;
|
||||||
|
// RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
|
||||||
|
if (len < 2) return obj;
|
||||||
|
|
||||||
|
var dec = (opt && opt.decode) || decode;
|
||||||
|
var index = 0;
|
||||||
|
var eqIdx = 0;
|
||||||
|
var endIdx = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
eqIdx = str.indexOf('=', index);
|
||||||
|
if (eqIdx === -1) break; // No more cookie pairs.
|
||||||
|
|
||||||
|
endIdx = str.indexOf(';', index);
|
||||||
|
|
||||||
|
if (endIdx === -1) {
|
||||||
|
endIdx = len;
|
||||||
|
} else if (eqIdx > endIdx) {
|
||||||
|
// backtrack on prior semicolon
|
||||||
|
index = str.lastIndexOf(';', eqIdx - 1) + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyStartIdx = startIndex(str, index, eqIdx);
|
||||||
|
var keyEndIdx = endIndex(str, eqIdx, keyStartIdx);
|
||||||
|
var key = str.slice(keyStartIdx, keyEndIdx);
|
||||||
|
|
||||||
|
// only assign once
|
||||||
|
if (!__hasOwnProperty.call(obj, key)) {
|
||||||
|
var valStartIdx = startIndex(str, eqIdx + 1, endIdx);
|
||||||
|
var valEndIdx = endIndex(str, endIdx, valStartIdx);
|
||||||
|
|
||||||
|
if (str.charCodeAt(valStartIdx) === 0x22 /* " */ && str.charCodeAt(valEndIdx - 1) === 0x22 /* " */) {
|
||||||
|
valStartIdx++;
|
||||||
|
valEndIdx--;
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = str.slice(valStartIdx, valEndIdx);
|
||||||
|
obj[key] = tryDecode(val, dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
index = endIdx + 1
|
||||||
|
} while (index < len);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startIndex(str, index, max) {
|
||||||
|
do {
|
||||||
|
var code = str.charCodeAt(index);
|
||||||
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index;
|
||||||
|
} while (++index < max);
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
function endIndex(str, index, min) {
|
||||||
|
while (index > min) {
|
||||||
|
var code = str.charCodeAt(--index);
|
||||||
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1;
|
||||||
|
}
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize data into a cookie header.
|
||||||
|
*
|
||||||
|
* Serialize a name value pair into a cookie string suitable for
|
||||||
|
* http headers. An optional options object specifies cookie parameters.
|
||||||
|
*
|
||||||
|
* serialize('foo', 'bar', { httpOnly: true })
|
||||||
|
* => "foo=bar; httpOnly"
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} val
|
||||||
|
* @param {object} [opt]
|
||||||
|
* @return {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function serialize(name, val, opt) {
|
||||||
|
var enc = (opt && opt.encode) || encodeURIComponent;
|
||||||
|
|
||||||
|
if (typeof enc !== 'function') {
|
||||||
|
throw new TypeError('option encode is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cookieNameRegExp.test(name)) {
|
||||||
|
throw new TypeError('argument name is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = enc(val);
|
||||||
|
|
||||||
|
if (!cookieValueRegExp.test(value)) {
|
||||||
|
throw new TypeError('argument val is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
var str = name + '=' + value;
|
||||||
|
if (!opt) return str;
|
||||||
|
|
||||||
|
if (null != opt.maxAge) {
|
||||||
|
var maxAge = Math.floor(opt.maxAge);
|
||||||
|
|
||||||
|
if (!isFinite(maxAge)) {
|
||||||
|
throw new TypeError('option maxAge is invalid')
|
||||||
|
}
|
||||||
|
|
||||||
|
str += '; Max-Age=' + maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.domain) {
|
||||||
|
if (!domainValueRegExp.test(opt.domain)) {
|
||||||
|
throw new TypeError('option domain is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
str += '; Domain=' + opt.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.path) {
|
||||||
|
if (!pathValueRegExp.test(opt.path)) {
|
||||||
|
throw new TypeError('option path is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
str += '; Path=' + opt.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.expires) {
|
||||||
|
var expires = opt.expires
|
||||||
|
|
||||||
|
if (!isDate(expires) || isNaN(expires.valueOf())) {
|
||||||
|
throw new TypeError('option expires is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
str += '; Expires=' + expires.toUTCString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.httpOnly) {
|
||||||
|
str += '; HttpOnly';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.secure) {
|
||||||
|
str += '; Secure';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.partitioned) {
|
||||||
|
str += '; Partitioned'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.priority) {
|
||||||
|
var priority = typeof opt.priority === 'string'
|
||||||
|
? opt.priority.toLowerCase() : opt.priority;
|
||||||
|
|
||||||
|
switch (priority) {
|
||||||
|
case 'low':
|
||||||
|
str += '; Priority=Low'
|
||||||
|
break
|
||||||
|
case 'medium':
|
||||||
|
str += '; Priority=Medium'
|
||||||
|
break
|
||||||
|
case 'high':
|
||||||
|
str += '; Priority=High'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new TypeError('option priority is invalid')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.sameSite) {
|
||||||
|
var sameSite = typeof opt.sameSite === 'string'
|
||||||
|
? opt.sameSite.toLowerCase() : opt.sameSite;
|
||||||
|
|
||||||
|
switch (sameSite) {
|
||||||
|
case true:
|
||||||
|
str += '; SameSite=Strict';
|
||||||
|
break;
|
||||||
|
case 'lax':
|
||||||
|
str += '; SameSite=Lax';
|
||||||
|
break;
|
||||||
|
case 'strict':
|
||||||
|
str += '; SameSite=Strict';
|
||||||
|
break;
|
||||||
|
case 'none':
|
||||||
|
str += '; SameSite=None';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError('option sameSite is invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL-decode string value. Optimized to skip native call when no %.
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decode (str) {
|
||||||
|
return str.indexOf('%') !== -1
|
||||||
|
? decodeURIComponent(str)
|
||||||
|
: str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if value is a Date.
|
||||||
|
*
|
||||||
|
* @param {*} val
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isDate (val) {
|
||||||
|
return __toString.call(val) === '[object Date]';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try decoding a string using a decoding function.
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @param {function} decode
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function tryDecode(str, decode) {
|
||||||
|
try {
|
||||||
|
return decode(str);
|
||||||
|
} catch (e) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
44
node_modules/express-session/node_modules/cookie/package.json
generated
vendored
Normal file
44
node_modules/express-session/node_modules/cookie/package.json
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "cookie",
|
||||||
|
"description": "HTTP server cookie parsing and serialization",
|
||||||
|
"version": "0.7.2",
|
||||||
|
"author": "Roman Shtylman <shtylman@gmail.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"cookie",
|
||||||
|
"cookies"
|
||||||
|
],
|
||||||
|
"repository": "jshttp/cookie",
|
||||||
|
"devDependencies": {
|
||||||
|
"beautify-benchmark": "0.2.4",
|
||||||
|
"benchmark": "2.1.4",
|
||||||
|
"eslint": "8.53.0",
|
||||||
|
"eslint-plugin-markdown": "3.0.1",
|
||||||
|
"mocha": "10.2.0",
|
||||||
|
"nyc": "15.1.0",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"top-sites": "1.1.194"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"HISTORY.md",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
"SECURITY.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"bench": "node benchmark/index.js",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --reporter spec --bail --check-leaks test/",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc --reporter=html --reporter=text npm test",
|
||||||
|
"update-bench": "node scripts/update-benchmark.js"
|
||||||
|
}
|
||||||
|
}
|
47
node_modules/express-session/package.json
generated
vendored
Normal file
47
node_modules/express-session/package.json
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "express-session",
|
||||||
|
"version": "1.18.1",
|
||||||
|
"description": "Simple session middleware for Express",
|
||||||
|
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"Joe Wagner <njwjs722@gmail.com>"
|
||||||
|
],
|
||||||
|
"repository": "expressjs/session",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.7",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.0.2",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"uid-safe": "~2.1.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"after": "0.8.2",
|
||||||
|
"cookie-parser": "1.4.6",
|
||||||
|
"eslint": "8.56.0",
|
||||||
|
"eslint-plugin-markdown": "3.0.1",
|
||||||
|
"express": "4.17.3",
|
||||||
|
"mocha": "10.2.0",
|
||||||
|
"nyc": "15.1.0",
|
||||||
|
"supertest": "6.3.4"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"session/",
|
||||||
|
"HISTORY.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint . && node ./scripts/lint-readme.js",
|
||||||
|
"test": "mocha --require test/support/env --check-leaks --bail --no-exit --reporter spec test/",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc npm test",
|
||||||
|
"version": "node scripts/version-history.js && git add HISTORY.md"
|
||||||
|
}
|
||||||
|
}
|
152
node_modules/express-session/session/cookie.js
generated
vendored
Normal file
152
node_modules/express-session/session/cookie.js
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*!
|
||||||
|
* Connect - session - Cookie
|
||||||
|
* Copyright(c) 2010 Sencha Inc.
|
||||||
|
* Copyright(c) 2011 TJ Holowaychuk
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var cookie = require('cookie')
|
||||||
|
var deprecate = require('depd')('express-session')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new `Cookie` with the given `options`.
|
||||||
|
*
|
||||||
|
* @param {IncomingMessage} req
|
||||||
|
* @param {Object} options
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Cookie = module.exports = function Cookie(options) {
|
||||||
|
this.path = '/';
|
||||||
|
this.maxAge = null;
|
||||||
|
this.httpOnly = true;
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if (typeof options !== 'object') {
|
||||||
|
throw new TypeError('argument options must be a object')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in options) {
|
||||||
|
if (key !== 'data') {
|
||||||
|
this[key] = options[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.originalMaxAge === undefined || this.originalMaxAge === null) {
|
||||||
|
this.originalMaxAge = this.maxAge
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Prototype.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Cookie.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set expires `date`.
|
||||||
|
*
|
||||||
|
* @param {Date} date
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
set expires(date) {
|
||||||
|
this._expires = date;
|
||||||
|
this.originalMaxAge = this.maxAge;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get expires `date`.
|
||||||
|
*
|
||||||
|
* @return {Date}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
get expires() {
|
||||||
|
return this._expires;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set expires via max-age in `ms`.
|
||||||
|
*
|
||||||
|
* @param {Number} ms
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
set maxAge(ms) {
|
||||||
|
if (ms && typeof ms !== 'number' && !(ms instanceof Date)) {
|
||||||
|
throw new TypeError('maxAge must be a number or Date')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ms instanceof Date) {
|
||||||
|
deprecate('maxAge as Date; pass number of milliseconds instead')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.expires = typeof ms === 'number'
|
||||||
|
? new Date(Date.now() + ms)
|
||||||
|
: ms;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get expires max-age in `ms`.
|
||||||
|
*
|
||||||
|
* @return {Number}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
get maxAge() {
|
||||||
|
return this.expires instanceof Date
|
||||||
|
? this.expires.valueOf() - Date.now()
|
||||||
|
: this.expires;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return cookie data object.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return {
|
||||||
|
originalMaxAge: this.originalMaxAge,
|
||||||
|
partitioned: this.partitioned,
|
||||||
|
priority: this.priority
|
||||||
|
, expires: this._expires
|
||||||
|
, secure: this.secure
|
||||||
|
, httpOnly: this.httpOnly
|
||||||
|
, domain: this.domain
|
||||||
|
, path: this.path
|
||||||
|
, sameSite: this.sameSite
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a serialized cookie string.
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
serialize: function(name, val){
|
||||||
|
return cookie.serialize(name, val, this.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return JSON representation of this cookie.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
toJSON: function(){
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
};
|
187
node_modules/express-session/session/memory.js
generated
vendored
Normal file
187
node_modules/express-session/session/memory.js
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/*!
|
||||||
|
* express-session
|
||||||
|
* Copyright(c) 2010 Sencha Inc.
|
||||||
|
* Copyright(c) 2011 TJ Holowaychuk
|
||||||
|
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Store = require('./store')
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shim setImmediate for node.js < 0.10
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
var defer = typeof setImmediate === 'function'
|
||||||
|
? setImmediate
|
||||||
|
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = MemoryStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A session store in memory.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function MemoryStore() {
|
||||||
|
Store.call(this)
|
||||||
|
this.sessions = Object.create(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from Store.
|
||||||
|
*/
|
||||||
|
|
||||||
|
util.inherits(MemoryStore, Store)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all active sessions.
|
||||||
|
*
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.all = function all(callback) {
|
||||||
|
var sessionIds = Object.keys(this.sessions)
|
||||||
|
var sessions = Object.create(null)
|
||||||
|
|
||||||
|
for (var i = 0; i < sessionIds.length; i++) {
|
||||||
|
var sessionId = sessionIds[i]
|
||||||
|
var session = getSession.call(this, sessionId)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
sessions[sessionId] = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback && defer(callback, null, sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all sessions.
|
||||||
|
*
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.clear = function clear(callback) {
|
||||||
|
this.sessions = Object.create(null)
|
||||||
|
callback && defer(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the session associated with the given session ID.
|
||||||
|
*
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.destroy = function destroy(sessionId, callback) {
|
||||||
|
delete this.sessions[sessionId]
|
||||||
|
callback && defer(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch session by the given session ID.
|
||||||
|
*
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.get = function get(sessionId, callback) {
|
||||||
|
defer(callback, null, getSession.call(this, sessionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the given session associated with the given sessionId to the store.
|
||||||
|
*
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @param {object} session
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.set = function set(sessionId, session, callback) {
|
||||||
|
this.sessions[sessionId] = JSON.stringify(session)
|
||||||
|
callback && defer(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of active sessions.
|
||||||
|
*
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.length = function length(callback) {
|
||||||
|
this.all(function (err, sessions) {
|
||||||
|
if (err) return callback(err)
|
||||||
|
callback(null, Object.keys(sessions).length)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Touch the given session object associated with the given session ID.
|
||||||
|
*
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @param {object} session
|
||||||
|
* @param {function} callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStore.prototype.touch = function touch(sessionId, session, callback) {
|
||||||
|
var currentSession = getSession.call(this, sessionId)
|
||||||
|
|
||||||
|
if (currentSession) {
|
||||||
|
// update expiration
|
||||||
|
currentSession.cookie = session.cookie
|
||||||
|
this.sessions[sessionId] = JSON.stringify(currentSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback && defer(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get session from the store.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getSession(sessionId) {
|
||||||
|
var sess = this.sessions[sessionId]
|
||||||
|
|
||||||
|
if (!sess) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse
|
||||||
|
sess = JSON.parse(sess)
|
||||||
|
|
||||||
|
if (sess.cookie) {
|
||||||
|
var expires = typeof sess.cookie.expires === 'string'
|
||||||
|
? new Date(sess.cookie.expires)
|
||||||
|
: sess.cookie.expires
|
||||||
|
|
||||||
|
// destroy expired session
|
||||||
|
if (expires && expires <= Date.now()) {
|
||||||
|
delete this.sessions[sessionId]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess
|
||||||
|
}
|
143
node_modules/express-session/session/session.js
generated
vendored
Normal file
143
node_modules/express-session/session/session.js
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*!
|
||||||
|
* Connect - session - Session
|
||||||
|
* Copyright(c) 2010 Sencha Inc.
|
||||||
|
* Copyright(c) 2011 TJ Holowaychuk
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose Session.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new `Session` with the given request and `data`.
|
||||||
|
*
|
||||||
|
* @param {IncomingRequest} req
|
||||||
|
* @param {Object} data
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Session(req, data) {
|
||||||
|
Object.defineProperty(this, 'req', { value: req });
|
||||||
|
Object.defineProperty(this, 'id', { value: req.sessionID });
|
||||||
|
|
||||||
|
if (typeof data === 'object' && data !== null) {
|
||||||
|
// merge data into this, ignoring prototype properties
|
||||||
|
for (var prop in data) {
|
||||||
|
if (!(prop in this)) {
|
||||||
|
this[prop] = data[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update reset `.cookie.maxAge` to prevent
|
||||||
|
* the cookie from expiring when the
|
||||||
|
* session is still active.
|
||||||
|
*
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'touch', function touch() {
|
||||||
|
return this.resetMaxAge();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset `.maxAge` to `.originalMaxAge`.
|
||||||
|
*
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'resetMaxAge', function resetMaxAge() {
|
||||||
|
this.cookie.maxAge = this.cookie.originalMaxAge;
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the session data with optional callback `fn(err)`.
|
||||||
|
*
|
||||||
|
* @param {Function} fn
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'save', function save(fn) {
|
||||||
|
this.req.sessionStore.set(this.id, this, fn || function(){});
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-loads the session data _without_ altering
|
||||||
|
* the maxAge properties. Invokes the callback `fn(err)`,
|
||||||
|
* after which time if no exception has occurred the
|
||||||
|
* `req.session` property will be a new `Session` object,
|
||||||
|
* although representing the same session.
|
||||||
|
*
|
||||||
|
* @param {Function} fn
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'reload', function reload(fn) {
|
||||||
|
var req = this.req
|
||||||
|
var store = this.req.sessionStore
|
||||||
|
|
||||||
|
store.get(this.id, function(err, sess){
|
||||||
|
if (err) return fn(err);
|
||||||
|
if (!sess) return fn(new Error('failed to load session'));
|
||||||
|
store.createSession(req, sess);
|
||||||
|
fn();
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy `this` session.
|
||||||
|
*
|
||||||
|
* @param {Function} fn
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'destroy', function destroy(fn) {
|
||||||
|
delete this.req.session;
|
||||||
|
this.req.sessionStore.destroy(this.id, fn);
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerate this request's session.
|
||||||
|
*
|
||||||
|
* @param {Function} fn
|
||||||
|
* @return {Session} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
defineMethod(Session.prototype, 'regenerate', function regenerate(fn) {
|
||||||
|
this.req.sessionStore.regenerate(this.req, fn);
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for creating a method on a prototype.
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} name
|
||||||
|
* @param {Function} fn
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function defineMethod(obj, name, fn) {
|
||||||
|
Object.defineProperty(obj, name, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: fn,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
};
|
102
node_modules/express-session/session/store.js
generated
vendored
Normal file
102
node_modules/express-session/session/store.js
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*!
|
||||||
|
* Connect - session - Store
|
||||||
|
* Copyright(c) 2010 Sencha Inc.
|
||||||
|
* Copyright(c) 2011 TJ Holowaychuk
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Cookie = require('./cookie')
|
||||||
|
var EventEmitter = require('events').EventEmitter
|
||||||
|
var Session = require('./session')
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = Store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for session stores.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Store () {
|
||||||
|
EventEmitter.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from EventEmitter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
util.inherits(Store, EventEmitter)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-generate the given requests's session.
|
||||||
|
*
|
||||||
|
* @param {IncomingRequest} req
|
||||||
|
* @return {Function} fn
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Store.prototype.regenerate = function(req, fn){
|
||||||
|
var self = this;
|
||||||
|
this.destroy(req.sessionID, function(err){
|
||||||
|
self.generate(req);
|
||||||
|
fn(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a `Session` instance via the given `sid`
|
||||||
|
* and invoke the callback `fn(err, sess)`.
|
||||||
|
*
|
||||||
|
* @param {String} sid
|
||||||
|
* @param {Function} fn
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Store.prototype.load = function(sid, fn){
|
||||||
|
var self = this;
|
||||||
|
this.get(sid, function(err, sess){
|
||||||
|
if (err) return fn(err);
|
||||||
|
if (!sess) return fn();
|
||||||
|
var req = { sessionID: sid, sessionStore: self };
|
||||||
|
fn(null, self.createSession(req, sess))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create session from JSON `sess` data.
|
||||||
|
*
|
||||||
|
* @param {IncomingRequest} req
|
||||||
|
* @param {Object} sess
|
||||||
|
* @return {Session}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
Store.prototype.createSession = function(req, sess){
|
||||||
|
var expires = sess.cookie.expires
|
||||||
|
var originalMaxAge = sess.cookie.originalMaxAge
|
||||||
|
|
||||||
|
sess.cookie = new Cookie(sess.cookie);
|
||||||
|
|
||||||
|
if (typeof expires === 'string') {
|
||||||
|
// convert expires to a Date object
|
||||||
|
sess.cookie.expires = new Date(expires)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep originalMaxAge intact
|
||||||
|
sess.cookie.originalMaxAge = originalMaxAge
|
||||||
|
|
||||||
|
req.session = new Session(req, sess);
|
||||||
|
return req.session;
|
||||||
|
};
|
132
node_modules/on-headers/index.js
generated
vendored
Normal file
132
node_modules/on-headers/index.js
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*!
|
||||||
|
* on-headers
|
||||||
|
* Copyright(c) 2014 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = onHeaders
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a replacement writeHead method.
|
||||||
|
*
|
||||||
|
* @param {function} prevWriteHead
|
||||||
|
* @param {function} listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createWriteHead (prevWriteHead, listener) {
|
||||||
|
var fired = false
|
||||||
|
|
||||||
|
// return function with core name and argument list
|
||||||
|
return function writeHead (statusCode) {
|
||||||
|
// set headers from arguments
|
||||||
|
var args = setWriteHeadHeaders.apply(this, arguments)
|
||||||
|
|
||||||
|
// fire listener
|
||||||
|
if (!fired) {
|
||||||
|
fired = true
|
||||||
|
listener.call(this)
|
||||||
|
|
||||||
|
// pass-along an updated status code
|
||||||
|
if (typeof args[0] === 'number' && this.statusCode !== args[0]) {
|
||||||
|
args[0] = this.statusCode
|
||||||
|
args.length = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevWriteHead.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a listener when a response is about to write headers.
|
||||||
|
*
|
||||||
|
* @param {object} res
|
||||||
|
* @return {function} listener
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onHeaders (res, listener) {
|
||||||
|
if (!res) {
|
||||||
|
throw new TypeError('argument res is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof listener !== 'function') {
|
||||||
|
throw new TypeError('argument listener must be a function')
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead = createWriteHead(res.writeHead, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers contained in array on the response object.
|
||||||
|
*
|
||||||
|
* @param {object} res
|
||||||
|
* @param {array} headers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setHeadersFromArray (res, headers) {
|
||||||
|
for (var i = 0; i < headers.length; i++) {
|
||||||
|
res.setHeader(headers[i][0], headers[i][1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers contained in object on the response object.
|
||||||
|
*
|
||||||
|
* @param {object} res
|
||||||
|
* @param {object} headers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setHeadersFromObject (res, headers) {
|
||||||
|
var keys = Object.keys(headers)
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var k = keys[i]
|
||||||
|
if (k) res.setHeader(k, headers[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers and other properties on the response object.
|
||||||
|
*
|
||||||
|
* @param {number} statusCode
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setWriteHeadHeaders (statusCode) {
|
||||||
|
var length = arguments.length
|
||||||
|
var headerIndex = length > 1 && typeof arguments[1] === 'string'
|
||||||
|
? 2
|
||||||
|
: 1
|
||||||
|
|
||||||
|
var headers = length >= headerIndex + 1
|
||||||
|
? arguments[headerIndex]
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
this.statusCode = statusCode
|
||||||
|
|
||||||
|
if (Array.isArray(headers)) {
|
||||||
|
// handle array case
|
||||||
|
setHeadersFromArray(this, headers)
|
||||||
|
} else if (headers) {
|
||||||
|
// handle object case
|
||||||
|
setHeadersFromObject(this, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy leading arguments
|
||||||
|
var args = new Array(Math.min(length, headerIndex))
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
args[i] = arguments[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
42
node_modules/on-headers/package.json
generated
vendored
Normal file
42
node_modules/on-headers/package.json
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "on-headers",
|
||||||
|
"description": "Execute a listener when a response is about to write headers",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"event",
|
||||||
|
"headers",
|
||||||
|
"http",
|
||||||
|
"onheaders"
|
||||||
|
],
|
||||||
|
"repository": "jshttp/on-headers",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "5.14.1",
|
||||||
|
"eslint-config-standard": "12.0.0",
|
||||||
|
"eslint-plugin-import": "2.16.0",
|
||||||
|
"eslint-plugin-markdown": "1.0.0",
|
||||||
|
"eslint-plugin-node": "8.0.1",
|
||||||
|
"eslint-plugin-promise": "4.0.1",
|
||||||
|
"eslint-plugin-standard": "4.0.0",
|
||||||
|
"istanbul": "0.4.5",
|
||||||
|
"mocha": "6.0.1",
|
||||||
|
"supertest": "3.4.2"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"README.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint --plugin markdown --ext js,md .",
|
||||||
|
"test": "mocha --reporter spec --bail --check-leaks test/",
|
||||||
|
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
|
||||||
|
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/",
|
||||||
|
"version": "node scripts/version-history.js && git add HISTORY.md"
|
||||||
|
}
|
||||||
|
}
|
101
node_modules/random-bytes/index.js
generated
vendored
Normal file
101
node_modules/random-bytes/index.js
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*!
|
||||||
|
* random-bytes
|
||||||
|
* Copyright(c) 2016 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var crypto = require('crypto')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module variables.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var generateAttempts = crypto.randomBytes === crypto.pseudoRandomBytes ? 1 : 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = randomBytes
|
||||||
|
module.exports.sync = randomBytesSync
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates strong pseudo-random bytes.
|
||||||
|
*
|
||||||
|
* @param {number} size
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @return {Promise}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function randomBytes(size, callback) {
|
||||||
|
// validate callback is a function, if provided
|
||||||
|
if (callback !== undefined && typeof callback !== 'function') {
|
||||||
|
throw new TypeError('argument callback must be a function')
|
||||||
|
}
|
||||||
|
|
||||||
|
// require the callback without promises
|
||||||
|
if (!callback && !global.Promise) {
|
||||||
|
throw new TypeError('argument callback is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
// classic callback style
|
||||||
|
return generateRandomBytes(size, generateAttempts, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(function executor(resolve, reject) {
|
||||||
|
generateRandomBytes(size, generateAttempts, function onRandomBytes(err, str) {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(str)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates strong pseudo-random bytes sync.
|
||||||
|
*
|
||||||
|
* @param {number} size
|
||||||
|
* @return {Buffer}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function randomBytesSync(size) {
|
||||||
|
var err = null
|
||||||
|
|
||||||
|
for (var i = 0; i < generateAttempts; i++) {
|
||||||
|
try {
|
||||||
|
return crypto.randomBytes(size)
|
||||||
|
} catch (e) {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates strong pseudo-random bytes.
|
||||||
|
*
|
||||||
|
* @param {number} size
|
||||||
|
* @param {number} attempts
|
||||||
|
* @param {function} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateRandomBytes(size, attempts, callback) {
|
||||||
|
crypto.randomBytes(size, function onRandomBytes(err, buf) {
|
||||||
|
if (!err) return callback(null, buf)
|
||||||
|
if (!--attempts) return callback(err)
|
||||||
|
setTimeout(generateRandomBytes.bind(null, size, attempts, callback), 10)
|
||||||
|
})
|
||||||
|
}
|
36
node_modules/random-bytes/package.json
generated
vendored
Normal file
36
node_modules/random-bytes/package.json
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "random-bytes",
|
||||||
|
"description": "URL and cookie safe UIDs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "crypto-utils/random-bytes",
|
||||||
|
"devDependencies": {
|
||||||
|
"bluebird": "3.1.1",
|
||||||
|
"istanbul": "0.4.2",
|
||||||
|
"mocha": "2.3.4",
|
||||||
|
"proxyquire": "1.2.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"README.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --trace-deprecation --reporter spec --bail --check-leaks test/",
|
||||||
|
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --trace-deprecation --reporter dot --check-leaks test/",
|
||||||
|
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --trace-deprecation --reporter spec --check-leaks test/"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"bytes",
|
||||||
|
"generator",
|
||||||
|
"random",
|
||||||
|
"safe"
|
||||||
|
]
|
||||||
|
}
|
107
node_modules/uid-safe/index.js
generated
vendored
Normal file
107
node_modules/uid-safe/index.js
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*!
|
||||||
|
* uid-safe
|
||||||
|
* Copyright(c) 2014 Jonathan Ong
|
||||||
|
* Copyright(c) 2015-2017 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var randomBytes = require('random-bytes')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module variables.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var EQUAL_END_REGEXP = /=+$/
|
||||||
|
var PLUS_GLOBAL_REGEXP = /\+/g
|
||||||
|
var SLASH_GLOBAL_REGEXP = /\//g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = uid
|
||||||
|
module.exports.sync = uidSync
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a unique ID.
|
||||||
|
*
|
||||||
|
* @param {number} length
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @return {Promise}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function uid (length, callback) {
|
||||||
|
// validate callback is a function, if provided
|
||||||
|
if (callback !== undefined && typeof callback !== 'function') {
|
||||||
|
throw new TypeError('argument callback must be a function')
|
||||||
|
}
|
||||||
|
|
||||||
|
// require the callback without promises
|
||||||
|
if (!callback && !global.Promise) {
|
||||||
|
throw new TypeError('argument callback is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
// classic callback style
|
||||||
|
return generateUid(length, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(function executor (resolve, reject) {
|
||||||
|
generateUid(length, function onUid (err, str) {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(str)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a unique ID sync.
|
||||||
|
*
|
||||||
|
* @param {number} length
|
||||||
|
* @return {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function uidSync (length) {
|
||||||
|
return toString(randomBytes.sync(length))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique ID string.
|
||||||
|
*
|
||||||
|
* @param {number} length
|
||||||
|
* @param {function} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateUid (length, callback) {
|
||||||
|
randomBytes(length, function (err, buf) {
|
||||||
|
if (err) return callback(err)
|
||||||
|
callback(null, toString(buf))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change a Buffer into a string.
|
||||||
|
*
|
||||||
|
* @param {Buffer} buf
|
||||||
|
* @return {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function toString (buf) {
|
||||||
|
return buf.toString('base64')
|
||||||
|
.replace(EQUAL_END_REGEXP, '')
|
||||||
|
.replace(PLUS_GLOBAL_REGEXP, '-')
|
||||||
|
.replace(SLASH_GLOBAL_REGEXP, '_')
|
||||||
|
}
|
46
node_modules/uid-safe/package.json
generated
vendored
Normal file
46
node_modules/uid-safe/package.json
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "uid-safe",
|
||||||
|
"description": "URL and cookie safe UIDs",
|
||||||
|
"version": "2.1.5",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "crypto-utils/uid-safe",
|
||||||
|
"dependencies": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bluebird": "3.5.0",
|
||||||
|
"eslint": "3.19.0",
|
||||||
|
"eslint-config-standard": "10.2.1",
|
||||||
|
"eslint-plugin-import": "2.7.0",
|
||||||
|
"eslint-plugin-node": "5.1.1",
|
||||||
|
"eslint-plugin-promise": "3.5.0",
|
||||||
|
"eslint-plugin-standard": "3.0.1",
|
||||||
|
"istanbul": "0.4.5",
|
||||||
|
"mocha": "2.5.3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"README.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --trace-deprecation --reporter spec --bail --check-leaks test/",
|
||||||
|
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --trace-deprecation --reporter dot --check-leaks test/",
|
||||||
|
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --trace-deprecation --reporter spec --check-leaks test/"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"random",
|
||||||
|
"generator",
|
||||||
|
"uid",
|
||||||
|
"safe"
|
||||||
|
]
|
||||||
|
}
|
118
package-lock.json
generated
118
package-lock.json
generated
@ -9,8 +9,11 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"call-of-duty-api": "^4.1.0",
|
||||||
"csso": "^5.0.5",
|
"csso": "^5.0.5",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
@ -82,6 +85,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@ -528,6 +540,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-of-duty-api": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-of-duty-api/-/call-of-duty-api-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-f5DJ6gQru6f406QVBZkkXOv0gUzFu0hykdkyKRa2Am6iWwGRVzcBK7rq+xHpNI6oTq3EFfw0T70kb38rZaezNA==",
|
||||||
|
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"undici": "^5.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Lierrmm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-of-duty-api/node_modules/undici": {
|
||||||
|
"version": "5.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||||
|
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/camel-case": {
|
"node_modules/camel-case": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
||||||
@ -1007,6 +1046,55 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-rate-limit": {
|
||||||
|
"version": "7.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
|
||||||
|
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/express-rate-limit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.7",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.0.2",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"uid-safe": "~2.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/body-parser": {
|
"node_modules/express/node_modules/body-parser": {
|
||||||
"version": "1.20.3",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
@ -1889,6 +1977,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/on-headers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -2151,6 +2248,15 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/random-bytes": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@ -2884,6 +2990,18 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uid-safe": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "7.6.0",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.6.0.tgz",
|
||||||
|
@ -20,8 +20,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"call-of-duty-api": "^4.1.0",
|
||||||
"csso": "^5.0.5",
|
"csso": "^5.0.5",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
|
@ -228,6 +228,22 @@ button:hover {
|
|||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Demo mode styles */
|
||||||
|
.demo-mode {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #888;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-notice {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes octocat-wave {
|
@keyframes octocat-wave {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
381
src/js/backend-min.js
vendored
Normal file
381
src/js/backend-min.js
vendored
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
const a0_0x3e5ceb = a0_0x1776;
|
||||||
|
function a0_0x424e() {
|
||||||
|
const _0x308173 = [
|
||||||
|
'8HLbzTb',
|
||||||
|
'number',
|
||||||
|
'replace',
|
||||||
|
'endTime',
|
||||||
|
'timePlayed',
|
||||||
|
'uiAPI',
|
||||||
|
'GMT',
|
||||||
|
'forEach',
|
||||||
|
'[CLIENT]\x20Request\x20data:\x20',
|
||||||
|
'An\x20error\x20occurred',
|
||||||
|
'[CLIENT]\x20Response\x20received\x20at\x20',
|
||||||
|
'\x20Hours\x20',
|
||||||
|
'includes',
|
||||||
|
'\x20at\x20',
|
||||||
|
'getElementById',
|
||||||
|
'dateAdded',
|
||||||
|
'toISOString',
|
||||||
|
'textContent',
|
||||||
|
'Request\x20timed\x20out.\x20Please\x20try\x20again.',
|
||||||
|
'string',
|
||||||
|
'Fetch\x20error:',
|
||||||
|
'length',
|
||||||
|
'828318JvPrOY',
|
||||||
|
'time',
|
||||||
|
'replaceKeysOption',
|
||||||
|
'toString',
|
||||||
|
'substring',
|
||||||
|
'json',
|
||||||
|
'An\x20error\x20occurred\x20while\x20fetching\x20data.',
|
||||||
|
'keys',
|
||||||
|
'entries',
|
||||||
|
'274851lFAePH',
|
||||||
|
'displayError',
|
||||||
|
'toUTCString',
|
||||||
|
'checked',
|
||||||
|
'5mFBRcl',
|
||||||
|
'1809891DxJKoQ',
|
||||||
|
'DOMContentLoaded',
|
||||||
|
'7128aSefdV',
|
||||||
|
'Server\x20returned\x20non-JSON\x20response',
|
||||||
|
'none',
|
||||||
|
'log',
|
||||||
|
'stringify',
|
||||||
|
'currentData',
|
||||||
|
'object',
|
||||||
|
'displayResults',
|
||||||
|
'appState',
|
||||||
|
'headers',
|
||||||
|
'null',
|
||||||
|
'signal',
|
||||||
|
'\x20Days\x20',
|
||||||
|
'error',
|
||||||
|
'style',
|
||||||
|
'test',
|
||||||
|
'\x20Minutes\x20',
|
||||||
|
'utcStartSeconds',
|
||||||
|
'tutorialDismissed',
|
||||||
|
'isArray',
|
||||||
|
'trimStart',
|
||||||
|
'ssoToken',
|
||||||
|
'querySelectorAll',
|
||||||
|
'70rCMOkH',
|
||||||
|
'addEventListener',
|
||||||
|
'1218049tFLRcR',
|
||||||
|
'.tutorial',
|
||||||
|
'repeat',
|
||||||
|
'16176Hmtcab',
|
||||||
|
'message',
|
||||||
|
'loading',
|
||||||
|
'[CLIENT]\x20Response\x20status:\x20',
|
||||||
|
'map',
|
||||||
|
'POST',
|
||||||
|
'floor',
|
||||||
|
'70193EjRYyO',
|
||||||
|
'name',
|
||||||
|
'objTime',
|
||||||
|
'display',
|
||||||
|
'date',
|
||||||
|
'get',
|
||||||
|
'Error:\x20',
|
||||||
|
'content-type',
|
||||||
|
'application/json',
|
||||||
|
'status',
|
||||||
|
'UTC',
|
||||||
|
'boolean',
|
||||||
|
'2031464mgplJK',
|
||||||
|
'8ohsaRV',
|
||||||
|
];
|
||||||
|
a0_0x424e = function () {
|
||||||
|
return _0x308173;
|
||||||
|
};
|
||||||
|
return a0_0x424e();
|
||||||
|
}
|
||||||
|
(function (_0x26b91f, _0x29f7ca) {
|
||||||
|
const _0x4906c0 = a0_0x1776,
|
||||||
|
_0x243b93 = _0x26b91f();
|
||||||
|
while (!![]) {
|
||||||
|
try {
|
||||||
|
const _0x1e80bd =
|
||||||
|
(parseInt(_0x4906c0(0x11e)) / 0x1) *
|
||||||
|
(-parseInt(_0x4906c0(0x12b)) / 0x2) +
|
||||||
|
-parseInt(_0x4906c0(0x150)) / 0x3 +
|
||||||
|
parseInt(_0x4906c0(0x12a)) / 0x4 +
|
||||||
|
(-parseInt(_0x4906c0(0x14f)) / 0x5) *
|
||||||
|
(-parseInt(_0x4906c0(0x142)) / 0x6) +
|
||||||
|
(parseInt(_0x4906c0(0x114)) / 0x7) *
|
||||||
|
(parseInt(_0x4906c0(0x12c)) / 0x8) +
|
||||||
|
(parseInt(_0x4906c0(0x14b)) / 0x9) *
|
||||||
|
(-parseInt(_0x4906c0(0x112)) / 0xa) +
|
||||||
|
(-parseInt(_0x4906c0(0x152)) / 0xb) *
|
||||||
|
(-parseInt(_0x4906c0(0x117)) / 0xc);
|
||||||
|
if (_0x1e80bd === _0x29f7ca) break;
|
||||||
|
else _0x243b93['push'](_0x243b93['shift']());
|
||||||
|
} catch (_0x14bf47) {
|
||||||
|
_0x243b93['push'](_0x243b93['shift']());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(a0_0x424e, 0x91684),
|
||||||
|
(window['backendAPI'] = {
|
||||||
|
fetchData: fetchData,
|
||||||
|
jsonToYAML: jsonToYAML,
|
||||||
|
formatDuration: formatDuration,
|
||||||
|
formatEpochTime: formatEpochTime,
|
||||||
|
processTimestamps: processTimestamps,
|
||||||
|
}),
|
||||||
|
(window['appState'] = {
|
||||||
|
currentData: null,
|
||||||
|
outputFormat: a0_0x3e5ceb(0x147),
|
||||||
|
tutorialDismissed: ![],
|
||||||
|
}),
|
||||||
|
document[a0_0x3e5ceb(0x113)](a0_0x3e5ceb(0x151), function () {});
|
||||||
|
function jsonToYAML(_0x57e066) {
|
||||||
|
const _0x4e69bf = a0_0x3e5ceb,
|
||||||
|
_0x1e3ebf = 0x2;
|
||||||
|
function _0x58e6eb(_0x4d120a, _0x248fd6 = 0x0) {
|
||||||
|
const _0x3a284a = a0_0x1776,
|
||||||
|
_0x52d5f6 = '\x20'[_0x3a284a(0x116)](_0x248fd6);
|
||||||
|
if (_0x4d120a === null) return _0x3a284a(0x105);
|
||||||
|
if (_0x4d120a === undefined) return '';
|
||||||
|
if (typeof _0x4d120a === _0x3a284a(0x13f)) {
|
||||||
|
if (
|
||||||
|
/[:{}[\],&*#?|\-<>=!%@`]/[_0x3a284a(0x10a)](_0x4d120a) ||
|
||||||
|
_0x4d120a === '' ||
|
||||||
|
!isNaN(_0x4d120a)
|
||||||
|
)
|
||||||
|
return '\x22' + _0x4d120a[_0x3a284a(0x12e)](/"/g, '\x5c\x22') + '\x22';
|
||||||
|
return _0x4d120a;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof _0x4d120a === _0x3a284a(0x12d) ||
|
||||||
|
typeof _0x4d120a === _0x3a284a(0x129)
|
||||||
|
)
|
||||||
|
return _0x4d120a[_0x3a284a(0x145)]();
|
||||||
|
if (Array[_0x3a284a(0x10e)](_0x4d120a)) {
|
||||||
|
if (_0x4d120a[_0x3a284a(0x141)] === 0x0) return '[]';
|
||||||
|
let _0x53132f = '';
|
||||||
|
for (const _0x194c43 of _0x4d120a) {
|
||||||
|
_0x53132f +=
|
||||||
|
'\x0a' +
|
||||||
|
_0x52d5f6 +
|
||||||
|
'-\x20' +
|
||||||
|
_0x58e6eb(_0x194c43, _0x248fd6 + _0x1e3ebf)[_0x3a284a(0x10f)]();
|
||||||
|
}
|
||||||
|
return _0x53132f;
|
||||||
|
}
|
||||||
|
if (typeof _0x4d120a === 'object') {
|
||||||
|
if (Object[_0x3a284a(0x149)](_0x4d120a)[_0x3a284a(0x141)] === 0x0)
|
||||||
|
return '{}';
|
||||||
|
let _0x42a784 = '';
|
||||||
|
for (const [_0x1a8665, _0x32613d] of Object[_0x3a284a(0x14a)](
|
||||||
|
_0x4d120a
|
||||||
|
)) {
|
||||||
|
const _0x3a78cc = _0x58e6eb(_0x32613d, _0x248fd6 + _0x1e3ebf);
|
||||||
|
_0x3a78cc[_0x3a284a(0x138)]('\x0a') ?
|
||||||
|
(_0x42a784 += '\x0a' + _0x52d5f6 + _0x1a8665 + ':' + _0x3a78cc)
|
||||||
|
: (_0x42a784 += '\x0a' + _0x52d5f6 + _0x1a8665 + ':\x20' + _0x3a78cc);
|
||||||
|
}
|
||||||
|
return _0x42a784;
|
||||||
|
}
|
||||||
|
return String(_0x4d120a);
|
||||||
|
}
|
||||||
|
return _0x58e6eb(_0x57e066, 0x0)[_0x4e69bf(0x146)](0x1);
|
||||||
|
}
|
||||||
|
async function fetchData(_0x5d7e74, _0x3de528) {
|
||||||
|
const _0x5f0540 = a0_0x3e5ceb;
|
||||||
|
console['log'](
|
||||||
|
'[CLIENT]\x20Request\x20to\x20' +
|
||||||
|
_0x5d7e74 +
|
||||||
|
_0x5f0540(0x139) +
|
||||||
|
new Date()[_0x5f0540(0x13c)]()
|
||||||
|
),
|
||||||
|
console['log'](
|
||||||
|
_0x5f0540(0x134) +
|
||||||
|
JSON[_0x5f0540(0x156)]({
|
||||||
|
..._0x3de528,
|
||||||
|
ssoToken:
|
||||||
|
_0x3de528[_0x5f0540(0x110)] ?
|
||||||
|
_0x3de528['ssoToken'][_0x5f0540(0x146)](0x0, 0x5) + '...'
|
||||||
|
: _0x5f0540(0x154),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const _0x4d0c9e = document['getElementById']('error'),
|
||||||
|
_0x4a22b8 = document['getElementById'](_0x5f0540(0x119)),
|
||||||
|
_0x2f0994 = document[_0x5f0540(0x13a)]('results');
|
||||||
|
(_0x4d0c9e[_0x5f0540(0x13d)] = ''),
|
||||||
|
(_0x2f0994[_0x5f0540(0x109)][_0x5f0540(0x121)] = _0x5f0540(0x154)),
|
||||||
|
(_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = 'block');
|
||||||
|
!window[_0x5f0540(0x103)][_0x5f0540(0x10d)] &&
|
||||||
|
((window[_0x5f0540(0x103)][_0x5f0540(0x10d)] = !![]),
|
||||||
|
document[_0x5f0540(0x111)](_0x5f0540(0x115))[_0x5f0540(0x133)](
|
||||||
|
(_0x520561) => {
|
||||||
|
const _0x511e86 = _0x5f0540;
|
||||||
|
_0x520561[_0x511e86(0x109)][_0x511e86(0x121)] = _0x511e86(0x154);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
if (!_0x3de528[_0x5f0540(0x110)]) {
|
||||||
|
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](
|
||||||
|
'SSO\x20Token\x20is\x20required'
|
||||||
|
),
|
||||||
|
(_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = _0x5f0540(0x154));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const _0x4c0dfe = new AbortController(),
|
||||||
|
_0x5e0646 = setTimeout(() => _0x4c0dfe['abort'](), 0x7530),
|
||||||
|
_0x7ef5a8 = await fetch(_0x5d7e74, {
|
||||||
|
method: _0x5f0540(0x11c),
|
||||||
|
headers: { 'Content-Type': _0x5f0540(0x126) },
|
||||||
|
body: JSON[_0x5f0540(0x156)](_0x3de528),
|
||||||
|
signal: _0x4c0dfe[_0x5f0540(0x106)],
|
||||||
|
});
|
||||||
|
clearTimeout(_0x5e0646);
|
||||||
|
const _0x64ad7 = _0x7ef5a8[_0x5f0540(0x104)][_0x5f0540(0x123)](
|
||||||
|
_0x5f0540(0x125)
|
||||||
|
);
|
||||||
|
if (!_0x64ad7 || !_0x64ad7[_0x5f0540(0x138)](_0x5f0540(0x126)))
|
||||||
|
throw new Error(_0x5f0540(0x153));
|
||||||
|
const _0x572a77 = await _0x7ef5a8[_0x5f0540(0x147)]();
|
||||||
|
console['log'](_0x5f0540(0x136) + new Date()['toISOString']()),
|
||||||
|
console[_0x5f0540(0x155)](_0x5f0540(0x11a) + _0x7ef5a8['status']),
|
||||||
|
console[_0x5f0540(0x155)](
|
||||||
|
'[CLIENT]\x20Response\x20size:\x20~' +
|
||||||
|
JSON[_0x5f0540(0x156)](_0x572a77)[_0x5f0540(0x141)] / 0x400 +
|
||||||
|
'\x20KB'
|
||||||
|
);
|
||||||
|
if (!_0x7ef5a8['ok'])
|
||||||
|
throw new Error(
|
||||||
|
_0x572a77[_0x5f0540(0x108)] ||
|
||||||
|
_0x572a77[_0x5f0540(0x118)] ||
|
||||||
|
_0x5f0540(0x124) + _0x7ef5a8[_0x5f0540(0x127)]
|
||||||
|
);
|
||||||
|
if (_0x572a77['error'])
|
||||||
|
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](_0x572a77[_0x5f0540(0x108)]);
|
||||||
|
else
|
||||||
|
_0x572a77['status'] === _0x5f0540(0x108) ?
|
||||||
|
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](
|
||||||
|
_0x572a77['message'] || _0x5f0540(0x135)
|
||||||
|
)
|
||||||
|
: ((window[_0x5f0540(0x103)][_0x5f0540(0x100)] = _0x572a77),
|
||||||
|
window[_0x5f0540(0x131)][_0x5f0540(0x102)](_0x572a77));
|
||||||
|
} catch (_0x563951) {
|
||||||
|
_0x563951[_0x5f0540(0x11f)] === 'AbortError' ?
|
||||||
|
window[_0x5f0540(0x131)]['displayError'](_0x5f0540(0x13e))
|
||||||
|
: (window['uiAPI'][_0x5f0540(0x14c)](
|
||||||
|
_0x5f0540(0x124) + (_0x563951[_0x5f0540(0x118)] || _0x5f0540(0x148))
|
||||||
|
),
|
||||||
|
console['error'](_0x5f0540(0x140), _0x563951));
|
||||||
|
} finally {
|
||||||
|
_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatDuration(_0x58e5da) {
|
||||||
|
const _0x547bb4 = a0_0x3e5ceb;
|
||||||
|
if (!_0x58e5da || isNaN(_0x58e5da)) return _0x58e5da;
|
||||||
|
const _0x2efa31 = parseFloat(_0x58e5da),
|
||||||
|
_0x5a8e77 = Math[_0x547bb4(0x11d)](_0x2efa31 / 0x15180),
|
||||||
|
_0x31860f = Math[_0x547bb4(0x11d)]((_0x2efa31 % 0x15180) / 0xe10),
|
||||||
|
_0x40f2d9 = Math[_0x547bb4(0x11d)]((_0x2efa31 % 0xe10) / 0x3c),
|
||||||
|
_0x364d47 = Math[_0x547bb4(0x11d)](_0x2efa31 % 0x3c);
|
||||||
|
return (
|
||||||
|
_0x5a8e77 +
|
||||||
|
_0x547bb4(0x107) +
|
||||||
|
_0x31860f +
|
||||||
|
_0x547bb4(0x137) +
|
||||||
|
_0x40f2d9 +
|
||||||
|
_0x547bb4(0x10b) +
|
||||||
|
_0x364d47 +
|
||||||
|
'\x20Seconds'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function formatEpochTime(_0x2ad881, _0x376584) {
|
||||||
|
const _0x564df4 = a0_0x3e5ceb;
|
||||||
|
if (!_0x2ad881) return _0x2ad881;
|
||||||
|
const _0x2eb117 = parseInt(_0x2ad881);
|
||||||
|
if (isNaN(_0x2eb117)) return _0x2ad881;
|
||||||
|
const _0x403f03 =
|
||||||
|
_0x2eb117['toString']()['length'] <= 0xa ? _0x2eb117 * 0x3e8 : _0x2eb117;
|
||||||
|
let _0x56476d = 0x0;
|
||||||
|
if (_0x376584 !== _0x564df4(0x128)) {
|
||||||
|
const _0x34e2a2 = _0x376584['match'](/GMT([+-])(\d+)(?::(\d+))?/);
|
||||||
|
if (_0x34e2a2) {
|
||||||
|
const _0x27099d = _0x34e2a2[0x1] === '+' ? 0x1 : -0x1,
|
||||||
|
_0x16eec3 = parseInt(_0x34e2a2[0x2]),
|
||||||
|
_0x3bff6c = _0x34e2a2[0x3] ? parseInt(_0x34e2a2[0x3]) : 0x0;
|
||||||
|
_0x56476d = _0x27099d * (_0x16eec3 * 0x3c + _0x3bff6c) * 0x3c * 0x3e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const _0x56f46a = new Date(_0x403f03 + _0x56476d);
|
||||||
|
return _0x56f46a[_0x564df4(0x14d)]()[_0x564df4(0x12e)](
|
||||||
|
_0x564df4(0x132),
|
||||||
|
_0x376584
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function a0_0x1776(_0x51f135, _0x4e9ae9) {
|
||||||
|
const _0x424e92 = a0_0x424e();
|
||||||
|
return (
|
||||||
|
(a0_0x1776 = function (_0x177606, _0x203857) {
|
||||||
|
_0x177606 = _0x177606 - 0x100;
|
||||||
|
let _0x3fd5d4 = _0x424e92[_0x177606];
|
||||||
|
return _0x3fd5d4;
|
||||||
|
}),
|
||||||
|
a0_0x1776(_0x51f135, _0x4e9ae9)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function processTimestamps(
|
||||||
|
_0x13d227,
|
||||||
|
_0x1f087c,
|
||||||
|
_0x345fbc = [
|
||||||
|
a0_0x3e5ceb(0x122),
|
||||||
|
a0_0x3e5ceb(0x13b),
|
||||||
|
a0_0x3e5ceb(0x10c),
|
||||||
|
'utcEndSeconds',
|
||||||
|
'timestamp',
|
||||||
|
'startTime',
|
||||||
|
a0_0x3e5ceb(0x12f),
|
||||||
|
],
|
||||||
|
_0x3c9b02 = [
|
||||||
|
a0_0x3e5ceb(0x143),
|
||||||
|
'timePlayedTotal',
|
||||||
|
a0_0x3e5ceb(0x130),
|
||||||
|
'avgLifeTime',
|
||||||
|
'duration',
|
||||||
|
a0_0x3e5ceb(0x120),
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
const _0x4c2013 = a0_0x3e5ceb;
|
||||||
|
if (!_0x13d227 || typeof _0x13d227 !== _0x4c2013(0x101)) return _0x13d227;
|
||||||
|
if (Array[_0x4c2013(0x10e)](_0x13d227))
|
||||||
|
return _0x13d227[_0x4c2013(0x11b)]((_0x2088ab) =>
|
||||||
|
processTimestamps(_0x2088ab, _0x1f087c, _0x345fbc, _0x3c9b02)
|
||||||
|
);
|
||||||
|
const _0x134b47 = {};
|
||||||
|
for (const [_0x1a08ca, _0x13b85e] of Object[_0x4c2013(0x14a)](_0x13d227)) {
|
||||||
|
if (
|
||||||
|
_0x345fbc[_0x4c2013(0x138)](_0x1a08ca) &&
|
||||||
|
typeof _0x13b85e === _0x4c2013(0x12d)
|
||||||
|
)
|
||||||
|
_0x134b47[_0x1a08ca] = formatEpochTime(_0x13b85e, _0x1f087c);
|
||||||
|
else {
|
||||||
|
if (
|
||||||
|
_0x3c9b02[_0x4c2013(0x138)](_0x1a08ca) &&
|
||||||
|
typeof _0x13b85e === 'number' &&
|
||||||
|
document[_0x4c2013(0x13a)](_0x4c2013(0x144))[_0x4c2013(0x14e)]
|
||||||
|
)
|
||||||
|
_0x134b47[_0x1a08ca] = formatDuration(_0x13b85e);
|
||||||
|
else
|
||||||
|
typeof _0x13b85e === _0x4c2013(0x101) && _0x13b85e !== null ?
|
||||||
|
(_0x134b47[_0x1a08ca] = processTimestamps(
|
||||||
|
_0x13b85e,
|
||||||
|
_0x1f087c,
|
||||||
|
_0x345fbc,
|
||||||
|
_0x3c9b02
|
||||||
|
))
|
||||||
|
: (_0x134b47[_0x1a08ca] = _0x13b85e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _0x134b47;
|
||||||
|
}
|
@ -107,11 +107,11 @@ async function fetchData(endpoint, requestData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate request data
|
// Validate request data
|
||||||
if (!requestData.ssoToken) {
|
// if (!requestData.ssoToken) {
|
||||||
window.uiAPI.displayError('SSO Token is required');
|
// window.uiAPI.displayError('SSO Token is required');
|
||||||
loadingElement.style.display = 'none';
|
// loadingElement.style.display = 'none';
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Set up the request with a timeout
|
// Set up the request with a timeout
|
||||||
|
@ -67,8 +67,93 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
setupTimeOptions();
|
setupTimeOptions();
|
||||||
addSyncListeners();
|
addSyncListeners();
|
||||||
initializeSessionTracking();
|
initializeSessionTracking();
|
||||||
|
checkDemoMode();
|
||||||
|
// Call initially and then set up a refresh interval
|
||||||
|
updateRequestLimitInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setInterval(updateRequestLimitInfo, 60000); // Update every minute
|
||||||
|
|
||||||
|
// Function to check if we're in demo mode
|
||||||
|
function checkDemoMode() {
|
||||||
|
console.log('Checking for demo mode...');
|
||||||
|
fetch('/health')
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Demo mode status:', data.demoMode);
|
||||||
|
if (data.demoMode) {
|
||||||
|
enableDemoMode();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error checking demo mode:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to enable demo mode UI
|
||||||
|
function enableDemoMode() {
|
||||||
|
console.log('Enabling demo mode UI...');
|
||||||
|
|
||||||
|
// Get the SSO token input field
|
||||||
|
const ssoTokenInput = document.getElementById('ssoToken');
|
||||||
|
if (ssoTokenInput) {
|
||||||
|
// Disable the input
|
||||||
|
ssoTokenInput.disabled = true;
|
||||||
|
// Add a placeholder indicating demo mode
|
||||||
|
ssoTokenInput.placeholder = 'Demo Mode - No SSO Token Required';
|
||||||
|
// Gray it out with a CSS class
|
||||||
|
ssoTokenInput.classList.add('demo-mode');
|
||||||
|
|
||||||
|
// Add a demo mode indicator above the form
|
||||||
|
const formContainer = document.querySelector('.form-container');
|
||||||
|
if (formContainer) {
|
||||||
|
// Check if notice already exists to avoid duplicates
|
||||||
|
if (!document.querySelector('.demo-notice')) {
|
||||||
|
const demoNotice = document.createElement('div');
|
||||||
|
demoNotice.className = 'demo-notice';
|
||||||
|
demoNotice.innerHTML =
|
||||||
|
'<strong>🎮 Demo Mode Active</strong> - Using a pre-configured token. No need to enter your own SSO token.';
|
||||||
|
formContainer.insertBefore(demoNotice, formContainer.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure this is applied to window.appState
|
||||||
|
window.appState = window.appState || {};
|
||||||
|
window.appState.demoMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestLimitInfo() {
|
||||||
|
fetch('/health')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
const limitInfoElement = document.querySelector('.request-limit-info');
|
||||||
|
if (data.requestsRemaining !== null) {
|
||||||
|
const message =
|
||||||
|
data.requestsRemaining > 0 ?
|
||||||
|
`Demo mode requests remaining: <strong>${data.requestsRemaining}</strong> (Resets hourly)`
|
||||||
|
: `Request limit reached. Please try again later or use your own SSO token.`;
|
||||||
|
|
||||||
|
if (limitInfoElement) {
|
||||||
|
limitInfoElement.innerHTML = `<small>${message}</small>`;
|
||||||
|
} else {
|
||||||
|
const formContainer = document.querySelector('.form-container');
|
||||||
|
if (formContainer) {
|
||||||
|
const limitInfo = document.createElement('div');
|
||||||
|
limitInfo.className = 'request-limit-info';
|
||||||
|
limitInfo.innerHTML = `<small>${message}</small>`;
|
||||||
|
formContainer.appendChild(limitInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Tab switching logic
|
// Tab switching logic
|
||||||
function initTabSwitching() {
|
function initTabSwitching() {
|
||||||
document.querySelectorAll('.tab').forEach((tab) => {
|
document.querySelectorAll('.tab').forEach((tab) => {
|
||||||
|
5
src/js/index.d.ts
vendored
5
src/js/index.d.ts
vendored
@ -68,6 +68,11 @@ declare class MW {
|
|||||||
matchInfo: (matchId: string, platform: platforms) => Promise<unknown>;
|
matchInfo: (matchId: string, platform: platforms) => Promise<unknown>;
|
||||||
seasonloot: (gamertag: string, platform: platforms) => Promise<unknown>;
|
seasonloot: (gamertag: string, platform: platforms) => Promise<unknown>;
|
||||||
mapList: (platform: platforms) => Promise<unknown>;
|
mapList: (platform: platforms) => Promise<unknown>;
|
||||||
|
communityMapDataForMapMode: (
|
||||||
|
mapName: string,
|
||||||
|
gameMode: string,
|
||||||
|
platform: platforms
|
||||||
|
) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
declare class MW2 {
|
declare class MW2 {
|
||||||
fullData: (unoId: string) => Promise<unknown>;
|
fullData: (unoId: string) => Promise<unknown>;
|
||||||
|
@ -55,6 +55,7 @@ let basePostHeaders = {
|
|||||||
'user-agent': userAgent,
|
'user-agent': userAgent,
|
||||||
};
|
};
|
||||||
let baseUrl = 'https://profile.callofduty.com';
|
let baseUrl = 'https://profile.callofduty.com';
|
||||||
|
let custombaseUrl = 'https://www.callofduty.com';
|
||||||
let apiPath = '/api/papi-client';
|
let apiPath = '/api/papi-client';
|
||||||
let baseTelescopeUrl = 'https://telescope.callofduty.com';
|
let baseTelescopeUrl = 'https://telescope.callofduty.com';
|
||||||
let apiTelescopePath = '/api/ts-api';
|
let apiTelescopePath = '/api/ts-api';
|
||||||
@ -166,6 +167,32 @@ const sendRequest = (url) =>
|
|||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const sendRequestCustom = (url) =>
|
||||||
|
tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
if (!loggedIn) throw new Error('Not Logged In.');
|
||||||
|
let requestUrl = `${custombaseUrl}${apiPath}${url}`;
|
||||||
|
if (debugMode) console.log(`[DEBUG]`, `Request Uri: ${requestUrl}`);
|
||||||
|
if (debugMode) console.time('Round Trip');
|
||||||
|
const { body, statusCode } = yield (0, undici_1.request)(requestUrl, {
|
||||||
|
headers: baseHeaders,
|
||||||
|
});
|
||||||
|
if (debugMode) console.timeEnd('Round Trip');
|
||||||
|
if (statusCode >= 500)
|
||||||
|
throw new Error(
|
||||||
|
`Received status code: '${statusCode}'. Route may be down or not exist.`
|
||||||
|
);
|
||||||
|
let response = yield body.json();
|
||||||
|
if (debugMode)
|
||||||
|
console.log(
|
||||||
|
`[DEBUG]`,
|
||||||
|
`Body Size: ${JSON.stringify(response).length} bytes.`
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (exception) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
});
|
||||||
const sendPostRequest = (url, data) =>
|
const sendPostRequest = (url, data) =>
|
||||||
tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
||||||
try {
|
try {
|
||||||
@ -314,6 +341,8 @@ class Endpoints {
|
|||||||
`/codfriends/v1/${action}/${this.platform}/${this.lookupType}/${this.gamertag}`;
|
`/codfriends/v1/${action}/${this.platform}/${this.lookupType}/${this.gamertag}`;
|
||||||
this.search = () =>
|
this.search = () =>
|
||||||
`/crm/cod/v2/platform/${this.platform}/username/${this.gamertag}/search`;
|
`/crm/cod/v2/platform/${this.platform}/username/${this.gamertag}/search`;
|
||||||
|
this.communityMapDataForMapMode = (mapName, gameMode) =>
|
||||||
|
`/ce/v1/title/${this.game}/platform/${this.platform}/gameType/${this.mode}/map/${mapName}/mode/${gameMode}/communityMapData`;
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.gamertag = gamertag;
|
this.gamertag = gamertag;
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
@ -607,6 +636,26 @@ class MW {
|
|||||||
return yield sendRequest(endpoint.mapList());
|
return yield sendRequest(endpoint.mapList());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
this.communityMapDataForMapMode = (mapName, gameMode, platform) => {
|
||||||
|
var gamertag, platform, lookupType;
|
||||||
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
({
|
||||||
|
gamertag,
|
||||||
|
_platform: platform,
|
||||||
|
lookupType,
|
||||||
|
} = mapGamertagToPlatform('', platform));
|
||||||
|
const endpoint = new Endpoints(
|
||||||
|
games.ModernWarfare,
|
||||||
|
gamertag,
|
||||||
|
platform,
|
||||||
|
modes.Multiplayer,
|
||||||
|
lookupType
|
||||||
|
);
|
||||||
|
return yield sendRequestCustom(
|
||||||
|
endpoint.communityMapDataForMapMode(mapName, gameMode)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class MW2 {
|
class MW2 {
|
||||||
|
@ -46,6 +46,7 @@ let basePostHeaders: CustomHeaders = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let baseUrl: string = 'https://profile.callofduty.com';
|
let baseUrl: string = 'https://profile.callofduty.com';
|
||||||
|
let custombaseUrl = 'https://www.callofduty.com';
|
||||||
let apiPath: string = '/api/papi-client';
|
let apiPath: string = '/api/papi-client';
|
||||||
let baseTelescopeUrl: string = 'https://telescope.callofduty.com';
|
let baseTelescopeUrl: string = 'https://telescope.callofduty.com';
|
||||||
let apiTelescopePath: string = '/api/ts-api';
|
let apiTelescopePath: string = '/api/ts-api';
|
||||||
@ -183,6 +184,39 @@ const sendRequest = async (url: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendRequestCustom = async (url: string) => {
|
||||||
|
try {
|
||||||
|
if (!loggedIn) throw new Error('Not Logged In.');
|
||||||
|
let requestUrl = `${custombaseUrl}${apiPath}${url}`;
|
||||||
|
|
||||||
|
if (debugMode) console.log(`[DEBUG]`, `Request Uri: ${requestUrl}`);
|
||||||
|
if (debugMode) console.time('Round Trip');
|
||||||
|
|
||||||
|
const { body, statusCode } = await request(requestUrl, {
|
||||||
|
headers: baseHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (debugMode) console.timeEnd('Round Trip');
|
||||||
|
|
||||||
|
if (statusCode >= 500)
|
||||||
|
throw new Error(
|
||||||
|
`Received status code: '${statusCode}'. Route may be down or not exist.`
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = await body.json();
|
||||||
|
|
||||||
|
if (debugMode)
|
||||||
|
console.log(
|
||||||
|
`[DEBUG]`,
|
||||||
|
`Body Size: ${JSON.stringify(response).length} bytes.`
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (exception: unknown) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const sendPostRequest = async (url: string, data: string) => {
|
const sendPostRequest = async (url: string, data: string) => {
|
||||||
try {
|
try {
|
||||||
if (!loggedIn) throw new Error('Not Logged In.');
|
if (!loggedIn) throw new Error('Not Logged In.');
|
||||||
@ -367,6 +401,8 @@ class Endpoints {
|
|||||||
`/codfriends/v1/${action}/${this.platform}/${this.lookupType}/${this.gamertag}`;
|
`/codfriends/v1/${action}/${this.platform}/${this.lookupType}/${this.gamertag}`;
|
||||||
search = () =>
|
search = () =>
|
||||||
`/crm/cod/v2/platform/${this.platform}/username/${this.gamertag}/search`;
|
`/crm/cod/v2/platform/${this.platform}/username/${this.gamertag}/search`;
|
||||||
|
communityMapDataForMapMode = (mapName: string, gameMode: string) =>
|
||||||
|
`/ce/v1/title/${this.game}/platform/${this.platform}/gameType/${this.mode}/map/${mapName}/mode/${gameMode}/communityMapData`;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TelescopeEndpoints {
|
class TelescopeEndpoints {
|
||||||
@ -646,6 +682,28 @@ class MW {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
communityMapDataForMapMode = async (
|
||||||
|
mapName: string,
|
||||||
|
gameMode: string,
|
||||||
|
platform: platforms
|
||||||
|
) => {
|
||||||
|
var {
|
||||||
|
gamertag,
|
||||||
|
_platform: platform,
|
||||||
|
lookupType,
|
||||||
|
} = mapGamertagToPlatform('', platform);
|
||||||
|
const endpoint = new Endpoints(
|
||||||
|
games.ModernWarfare,
|
||||||
|
gamertag,
|
||||||
|
platform,
|
||||||
|
modes.Multiplayer,
|
||||||
|
lookupType
|
||||||
|
);
|
||||||
|
return await sendRequestCustom(
|
||||||
|
endpoint.communityMapDataForMapMode(mapName, gameMode)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
class MW2 {
|
class MW2 {
|
||||||
fullData = async (unoId: string) => {
|
fullData = async (unoId: string) => {
|
||||||
var { gamertag } = mapGamertagToPlatform(unoId, platforms.Uno, true);
|
var { gamertag } = mapGamertagToPlatform(unoId, platforms.Uno, true);
|
||||||
|
41
src/js/test.js
Normal file
41
src/js/test.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { login, ModernWarfare, platforms } from './index.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Login using the SSO token
|
||||||
|
const ssoToken =
|
||||||
|
'MTk1NjgyNzA6MTc0NDQ4OTcxNDE4MDpiOGE2MDEwMzY1ZWQ5OTM0NGM3ZjA0MWQxMTFjMTExNA';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await login(ssoToken); // Ensure login is successful before making requests
|
||||||
|
|
||||||
|
// // Fetch player data
|
||||||
|
// let gamertag = 'Ahrimdon';
|
||||||
|
// let platform = platforms.XBOX; // Use predefined constants for platforms
|
||||||
|
|
||||||
|
// let data = await ModernWarfare.fullData(gamertag, platform);
|
||||||
|
// console.log('Fetched data:', data);
|
||||||
|
|
||||||
|
// // Convert data to a JSON string with formatting
|
||||||
|
// const jsonData = JSON.stringify(data, null, 2);
|
||||||
|
|
||||||
|
// // Write the JSON data to a file
|
||||||
|
// fs.writeFileSync('playerData.json', jsonData);
|
||||||
|
|
||||||
|
// Fetch community map data for a specific map and mode
|
||||||
|
const mapData = await ModernWarfare.communityMapDataForMapMode(
|
||||||
|
'mp_m_king',
|
||||||
|
'gun',
|
||||||
|
platforms.XBOX
|
||||||
|
);
|
||||||
|
console.log('Map data:', mapData);
|
||||||
|
|
||||||
|
// Save to file if needed
|
||||||
|
fs.writeFileSync('mapData.json', JSON.stringify(mapData, null, 2));
|
||||||
|
console.log('Map data has been saved to mapData.json');
|
||||||
|
|
||||||
|
// console.log('Player data has been saved to playerData.json');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
}
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user