Skip to content

Commit

Permalink
Clean up validate function. (#275)
Browse files Browse the repository at this point in the history
* Clean up validate function and add descriptions to validators.

* Fix type error.

* Add @types/node

* Fix validator implementation and add safe toString function.
  • Loading branch information
wjhsf committed Jul 17, 2023
1 parent 402563b commit e07f193
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 41 deletions.
25 changes: 16 additions & 9 deletions lib/cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import * as validators from './validators'
import { version } from './version'
import { permuteDomain } from './permuteDomain'
import { getCustomInspectSymbol } from './utilHelper'
import { ErrorCallback, safeToString } from './utils'

// From RFC6265 S4.1.1
// note that it excludes \x3B ";"
Expand Down Expand Up @@ -357,7 +358,7 @@ function parseDate(str: string | undefined | null): Date | undefined {
}

function formatDate(date: Date) {
validators.validate(validators.isDate(date), date)
validators.validate(validators.isDate(date), safeToString(date))
return date.toUTCString()
}

Expand Down Expand Up @@ -699,7 +700,7 @@ function parse(
* @returns boolean
*/
function isSecurePrefixConditionMet(cookie: Cookie) {
validators.validate(validators.isObject(cookie), cookie)
validators.validate(validators.isObject(cookie), safeToString(cookie))
const startsWithSecurePrefix =
typeof cookie.key === 'string' && cookie.key.startsWith('__Secure-')
return !startsWithSecurePrefix || cookie.secure
Expand Down Expand Up @@ -786,8 +787,8 @@ function fromJSON(str: string | SerializedCookie | null | undefined) {
*/

function cookieCompare(a: Cookie, b: Cookie) {
validators.validate(validators.isObject(a), a)
validators.validate(validators.isObject(b), b)
validators.validate(validators.isObject(a), safeToString(a))
validators.validate(validators.isObject(b), safeToString(b))
let cmp = 0

// descending for length: b CMP a
Expand Down Expand Up @@ -977,8 +978,11 @@ export class Cookie {
) {
return false
}
// @ts-ignore
if (this.maxAge != null && this.maxAge <= 0) {
if (
this.maxAge != null &&
this.maxAge !== 'Infinity' &&
(this.maxAge === '-Infinity' || this.maxAge <= 0)
) {
return false // "Max-Age=" non-zero-digit *DIGIT
}
if (this.path != null && !PATH_VALUE.test(this.path)) {
Expand Down Expand Up @@ -1343,7 +1347,11 @@ export class CookieJar {
const promiseCallback = createPromiseCallback<Cookie>(arguments)
const cb = promiseCallback.callback

validators.validate(validators.isNonEmptyString(url), callback, options)
validators.validate(
validators.isNonEmptyString(url),
callback,
safeToString(options),
)
let err

if (validators.isFunction(url)) {
Expand Down Expand Up @@ -1629,7 +1637,7 @@ export class CookieJar {
if (typeof options === 'function' || options === undefined) {
options = defaultGetCookieOptions
}
validators.validate(validators.isObject(options), cb, options)
validators.validate(validators.isObject(options), cb, safeToString(options))
validators.validate(validators.isFunction(cb), cb)

const host = canonicalDomain(context.hostname)
Expand Down Expand Up @@ -2247,4 +2255,3 @@ export type Callback<T> = (
error: Error | undefined,
result: T | undefined,
) => void
export type ErrorCallback = (error: Error) => void
19 changes: 19 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** Signature for a callback function that expects an error to be passed. */
export type ErrorCallback = (error: Error, result?: never) => void

/** Unbound `Object.prototype.toString`. */
const unboundToString = Object.prototype.toString

/** Wrapped `Object.prototype.toString`, so that you don't need to remember to use `.call()`. */
export const objectToString = (obj: unknown) => unboundToString.call(obj)

/** Safely converts any value to string, using the value's own `toString` when available. */
export const safeToString = (val: unknown) => {
// Ideally, we'd just use String() for everything, but it breaks if `toString` is missing (mostly
// values with no prototype), so we have to use Object#toString as a fallback.
if (val === undefined || val === null || typeof val.toString === 'function') {
return String(val)
} else {
return objectToString(val)
}
}
69 changes: 45 additions & 24 deletions lib/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,58 +25,79 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
************************************************************************************ */
'use strict'

const toString = Object.prototype.toString
import { ErrorCallback, objectToString, safeToString } from './utils'

/* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */
export function isFunction(data: unknown): boolean {

/** Determines whether the argument is a function. */
export function isFunction(data: unknown): data is Function {
return typeof data === 'function'
}

/** Determines whether the argument is a non-empty string. */
export function isNonEmptyString(data: unknown): boolean {
return isString(data) && data !== ''
}

/** Determines whether the argument is a *valid* Date. */
export function isDate(data: unknown): boolean {
if (data instanceof Date) {
return isInteger(data.getTime())
}
return false
return isInstanceStrict(data, Date) && isInteger(data.getTime())
}

/** Determines whether the argument is the empty string. */
export function isEmptyString(data: unknown): boolean {
return data === '' || (data instanceof String && data.toString() === '')
}

/** Determines whether the argument is a string. */
export function isString(data: unknown): boolean {
return typeof data === 'string' || data instanceof String
}

/** Determines whether the string representation of the argument is "[object Object]". */
export function isObject(data: unknown): boolean {
return toString.call(data) === '[object Object]'
return objectToString(data) === '[object Object]'
}

/** Determines whether the first argument is an instance of the second. */
export function isInstanceStrict<T extends Function>(
data: unknown,
Constructor: T,
): data is T['prototype'] {
try {
return data instanceof Constructor
} catch {
return false
}
}

/** Determines whether the argument is an integer. */
export function isInteger(data: unknown): boolean {
return typeof data === 'number' && data % 1 === 0
}
/* End validation functions */

export function validate(bool: boolean, cb?: unknown, options?: unknown): void {
if (!isFunction(cb)) {
options = cb
cb = null
}
if (!isObject(options)) options = { Error: 'Failed Check' }
if (!bool) {
if (typeof cb === 'function') {
// @ts-ignore
cb(new ParameterError(options))
} else {
// @ts-ignore
throw new ParameterError(options)
}
}
/* -- End validation functions -- */

/**
* When the first argument is false, an error is created with the given message. If a callback is
* provided, the error is passed to the callback, otherwise the error is thrown.
*/
export function validate(
bool: boolean,
cbOrMessage?: ErrorCallback | string,
message?: string,
): void {
if (bool) return // Validation passes
const cb = isFunction(cbOrMessage) ? cbOrMessage : null
let options = isFunction(cbOrMessage) ? message : cbOrMessage
// The default message prior to v5 was '[object Object]' due to a bug, and the message is kept
// for backwards compatibility.
if (!isObject(options)) options = '[object Object]'

const err = new ParameterError(safeToString(options))
if (cb) cb(err)
else throw err
}

export class ParameterError extends Error {}
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@
},
"devDependencies": {
"@types/jest": "^29",
"@types/node": "^16.18.23",
"@types/psl": "^1",
"@types/punycode": "^2",
"@types/url-parse": "^1.4.8",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"async": "2.6.4",
"eslint": "^8.36.0",
"@typescript-eslint/parser": "^5.57.0",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"genversion": "^3.1.1",
Expand Down

0 comments on commit e07f193

Please sign in to comment.