* Utility and miscellaneous functions.
*/
-const path = require('path');
-const crypto = require('crypto');
+const crypto = require('node:crypto');
const uuid = require('uuid');
const Enum = require('./enum');
-
-/**
- * @callback ScopeFn
- * @param {String} scope
- * @returns {String}
- */
-/**
- * Return a function which prefixes a provided scope with the most-
- * relevant part of the filename, for use in logging.
- * @param {String} filename
- * @returns {ScopeFn}
- */
-const fileScope = (filename) => {
- let fScope = path.basename(filename, '.js');
- if (fScope === 'index') {
- fScope = path.basename(path.dirname(filename));
- }
- return (scope) => `${fScope}:${scope}`;
-}
+const { fileScope } = require('@squeep/log-helper');
/**
* Simple ETag from data.
*/
const generateETag = (_filePath, fileStat, fileData) => {
const hash = crypto.createHash('sha256');
- if (fileStat && fileStat.mtimeMs) {
+ if (fileStat?.mtimeMs) {
hash.update(fileStat.mtimeMs.toString());
}
hash.update(fileData);
*/
const get = (obj, prop, def) => obj && prop && (prop in obj) ? obj[prop] : def;
-/**
- * @param {http.ClientRequest} req
- * @param {http.ServerResponse} res
- * @param {Object} ctx
- * @deprecated after v1.2.5 (integrated into logger module)
- */
-const handlerLogData = (req, res, ctx) => ({
- req: requestLogData(req),
- res: responseLogData(res),
- ctx,
-});
-
/**
* Determine whether a client has already requested a resource,
* based on If-Modified-Since and If-None-Match headers.
return picked;
};
-/**
- * Return a subset of a request object, suitable for logging.
- * Obscures sensitive header values.
- * @param {http.ClientRequest} req
- * @deprecated after v1.2.5 (integrated into logger module)
- */
-const requestLogData = (req) => {
- const data = pick(req, [
- 'method',
- 'url',
- 'httpVersion',
- 'headers',
- 'trailers',
- ]);
- scrubHeaderObject(data);
- return data;
-};
-
-
-/**
- * Remove sensitive header data.
- * @param {Object} data
- * @param {Object} data.headers
- * @deprecated after v1.2.5 (integrated into logger module)
- */
-const scrubHeaderObject = (data) => {
- if (data && data.headers && 'authorization' in data.headers) {
- data.headers = Object.assign({}, data.headers, {
- authorization: obscureAuthorizationHeader(data.headers['authorization']),
- });
- }
-}
-
-
-/**
- * Hide sensitive part of an Authorization header.
- * @param {String} authHeader
- * @returns {String}
- * @deprecated after v1.2.5 (integrated into logger module)
- */
-const obscureAuthorizationHeader = (authHeader) => {
- if (!authHeader) {
- return authHeader;
- }
- const space = authHeader.indexOf(' ');
- // This blurs entire string if no space found, because -1.
- return authHeader.slice(0, space + 1) + '*'.repeat(authHeader.length - (space + 1));
-}
-
-
-/**
- * Return a subset of a response object, suitable for logging.
- * @param {http.ServerResponse} res
- * @deprecated after v1.2.5 (integrated into logger module)
- */
-const responseLogData = (res) => {
- const response = pick(res, [
- 'statusCode',
- 'statusMessage',
- ]);
- response.headers = res.getHeaders();
- return response;
-};
-
-
/**
* Store all properties in defaultOptions on target from either options or defaultOptions.
* @param {Object} target
return uuid.v1();
};
-/**
- * Do nothing.
- */
-const nop = () => { /**/ };
-
-/**
- * A logger object which does nothing.
- */
-const nullLogger = {
- error: nop,
- warn: nop,
- info: nop,
- log: nop,
- debug: nop,
-};
-
-/**
- * Populates any absent logger level functions on a logger object.
- * @param {Object} logger
- * @returns {Object}
- */
-const ensureLoggerLevels = (logger = {}) => {
- for (const level in nullLogger) {
- if (! (level in logger)) {
- logger[level] = nullLogger[level];
- }
- }
- return logger;
-};
-
/**
* Merges folded header lines
* @param {String[]} lines
const unfoldHeaderLines = (lines) => {
const foldedLineRE = /^(\t| +)(.*)$/;
if (lines) {
- lines.reduceRight((_, line, idx) => {
+ lines.reduceRight((_, line, idx) => { // NOSONAR
const result = foldedLineRE.exec(line);
if (result && idx) {
const prevIdx = idx - 1;
return lines;
};
+/**
+ * Adds a new cookie.
+ * @param {http.ServerResponse} res
+ * @param {String} name
+ * @param {String} value
+ * @param {Object=} opt
+ * @param {String=} opt.domain
+ * @param {Date=} opt.expires
+ * @param {Boolean=} opt.httpOnly
+ * @param {Number=} opt.maxAge
+ * @param {String=} opt.path
+ * @param {String=} opt.sameSite
+ * @param {Boolean=} opt.secure
+ */
+function addCookie(res, name, value, opt = {}) {
+ const options = {
+ domain: undefined,
+ expires: undefined,
+ httpOnly: false,
+ maxAge: undefined,
+ path: undefined,
+ sameSite: undefined,
+ secure: false,
+ ...opt,
+ };
+ // TODO: validate name, value
+ const cookieParts = [
+ `${name}=${value}`,
+ ];
+ if (options.domain) {
+ cookieParts.push(`Domain=${options.domain}`);
+ }
+ if (options.expires) {
+ if (!(options.expires instanceof Date)) {
+ throw new TypeError('cookie expires must be Date');
+ }
+ cookieParts.push(`Expires=${options.expires.toUTCString()}`);
+ }
+ if (options.httpOnly) {
+ cookieParts.push('HttpOnly');
+ }
+ if (options.maxAge) {
+ cookieParts.push(`Max-Age=${options.maxAge}`);
+ }
+ if (options.path) {
+ cookieParts.push(`Path=${options.path}`);
+ }
+ if (options.sameSite) {
+ if (!(['Strict', 'Lax', 'None'].includes(options.sameSite))) {
+ throw new RangeError('cookie sameSite value not valid');
+ }
+ cookieParts.push(`SameSite=${options.sameSite}`);
+ }
+ if (options.secure) {
+ cookieParts.push('Secure');
+ }
+ res.appendHeader(Enum.Header.SetCookie, cookieParts.join('; '));
+}
+
+
module.exports = {
- ensureLoggerLevels,
+ addCookie,
fileScope,
generateETag,
get,
- handlerLogData,
httpStatusCodeClass,
isClientCached,
mergeDeep,
mergeEnum,
- nop,
- nullLogger,
- obscureAuthorizationHeader,
pick,
requestId,
- requestLogData,
- responseLogData,
- scrubHeaderObject,
setOptions,
splitFirst,
unfoldHeaderLines,