path.mjs 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { AnthropicError } from "../../core/error.mjs";
  2. /**
  3. * Percent-encode everything that isn't safe to have in a path without encoding safe chars.
  4. *
  5. * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
  6. * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  7. * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
  8. * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  9. */
  10. export function encodeURIPath(str) {
  11. return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
  12. }
  13. const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
  14. export const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
  15. // If there are no params, no processing is needed.
  16. if (statics.length === 1)
  17. return statics[0];
  18. let postPath = false;
  19. const invalidSegments = [];
  20. const path = statics.reduce((previousValue, currentValue, index) => {
  21. if (/[?#]/.test(currentValue)) {
  22. postPath = true;
  23. }
  24. const value = params[index];
  25. let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
  26. if (index !== params.length &&
  27. (value == null ||
  28. (typeof value === 'object' &&
  29. // handle values from other realms
  30. value.toString ===
  31. Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY) ?? EMPTY)
  32. ?.toString))) {
  33. encoded = value + '';
  34. invalidSegments.push({
  35. start: previousValue.length + currentValue.length,
  36. length: encoded.length,
  37. error: `Value of type ${Object.prototype.toString
  38. .call(value)
  39. .slice(8, -1)} is not a valid path parameter`,
  40. });
  41. }
  42. return previousValue + currentValue + (index === params.length ? '' : encoded);
  43. }, '');
  44. const pathOnly = path.split(/[?#]/, 1)[0];
  45. const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
  46. let match;
  47. // Find all invalid segments
  48. while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
  49. invalidSegments.push({
  50. start: match.index,
  51. length: match[0].length,
  52. error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
  53. });
  54. }
  55. invalidSegments.sort((a, b) => a.start - b.start);
  56. if (invalidSegments.length > 0) {
  57. let lastEnd = 0;
  58. const underline = invalidSegments.reduce((acc, segment) => {
  59. const spaces = ' '.repeat(segment.start - lastEnd);
  60. const arrows = '^'.repeat(segment.length);
  61. lastEnd = segment.start + segment.length;
  62. return acc + spaces + arrows;
  63. }, '');
  64. throw new AnthropicError(`Path parameters result in path with invalid segments:\n${invalidSegments
  65. .map((e) => e.error)
  66. .join('\n')}\n${path}\n${underline}`);
  67. }
  68. return path;
  69. };
  70. /**
  71. * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
  72. */
  73. export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
  74. //# sourceMappingURL=path.mjs.map