( path: Path, )
| 88 | * @category Utility |
| 89 | */ |
| 90 | export function stringToPath<const Path extends string>( |
| 91 | path: Path, |
| 92 | ): StringToPath<Path> { |
| 93 | const result: (string | number)[] = []; |
| 94 | |
| 95 | // There are four possible ways to define a path segment:: |
| 96 | // - `propName`: is used to parse dot-notation paths, e.g. 'foo.bar.baz', we |
| 97 | // allow multiple sequential dots because our type allows them, but they are |
| 98 | // semantically meaningless. Note that this would also allow strings starting |
| 99 | // with a dot, e.g. `.foo`, this is fine because the dot itself is just used |
| 100 | // as a separator and is ignored in the final path. |
| 101 | // - `quoted`, `doubleQuoted`: used within square bracket notation to prevent |
| 102 | // any recursive parsing of their contents. As for the type itself, we only |
| 103 | // allow quote symbols to appear immediately after the opening square bracket |
| 104 | // and immediately before the closing square bracket, this allows us to handle |
| 105 | // more gracefully cases where a quote symbol might appear within the quoted |
| 106 | // part. |
| 107 | // - `unquoted`: If the contents of the square brackets are not quoted they |
| 108 | // will match this group (this is why the order is important here!). Contents |
| 109 | // of this group get parsed *recursively*. |
| 110 | // |
| 111 | // NOTE: We limit all repeats to 4096 characters to avoid a possible attack |
| 112 | // vector when the input to this function is controlled by the user. This is |
| 113 | // due to a DoS timing attack because regex backtracking is non-linear. |
| 114 | // @see: https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos/ |
| 115 | const pathSegmentRe = |
| 116 | /\.{0,4096}(?<propName>[^.[\]]+)|\['(?<quoted>.{0,4096}?)'\]|\["(?<doubleQuoted>.{0,4096}?)"\]|\[(?<unquoted>.{0,4096}?)\]/uy; |
| 117 | |
| 118 | let match: RegExpExecArray | null; |
| 119 | while ((match = pathSegmentRe.exec(path)) !== null) { |
| 120 | const { propName, quoted, doubleQuoted, unquoted } = match.groups!; |
| 121 | |
| 122 | if (unquoted !== undefined) { |
| 123 | result.push(...stringToPath(unquoted)); |
| 124 | continue; |
| 125 | } |
| 126 | |
| 127 | result.push( |
| 128 | propName === undefined |
| 129 | ? (quoted ?? doubleQuoted!) |
| 130 | : // The only way to differentiate between array indices and properties |
| 131 | // is to check if the property is a non-negative integer. In those |
| 132 | // cases we perform the conversion (unlike Lodash). |
| 133 | NON_NEGATIVE_INTEGER_RE.test(propName) |
| 134 | ? Number(propName) |
| 135 | : propName, |
| 136 | ); |
| 137 | } |
| 138 | |
| 139 | return result; |
| 140 | } |
no outgoing calls
no test coverage detected
searching dependent graphs…