@tinycloud/share-sdk 1.0.0-beta.7 → 1.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  authorizationMethodForTarget: () => authorizationMethodForTarget,
46
46
  authorizeShare: () => authorizeShare,
47
47
  canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
48
+ canonicalShareFilename: () => canonicalShareFilename,
48
49
  canonicalize: () => canonicalize,
49
50
  checkBearerDelegation: () => checkBearerDelegation,
50
51
  claimShare: () => claimShare,
@@ -56,6 +57,7 @@ __export(index_exports, {
56
57
  createShareV2HolderBindingArtifact: () => createShareV2HolderBindingArtifact,
57
58
  deliverCredentialInvitation: () => deliverCredentialInvitation,
58
59
  fromBase64Url: () => fromBase64Url,
60
+ hasUnsafeFilenameCodePoint: () => hasUnsafeFilenameCodePoint,
59
61
  historyRecordForPublishedShare: () => historyRecordForPublishedShare,
60
62
  inspectShare: () => inspectShare,
61
63
  listShares: () => listShares,
@@ -85,6 +87,19 @@ __export(index_exports, {
85
87
  });
86
88
  module.exports = __toCommonJS(index_exports);
87
89
 
90
+ // src/filename-policy.ts
91
+ var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
92
+ function hasUnsafeFilenameCodePoint(value) {
93
+ return UNSAFE_FILENAME_CODE_POINT.test(value);
94
+ }
95
+ function canonicalShareFilename(value) {
96
+ const canonical = value.normalize("NFC");
97
+ if (canonical.length === 0 || canonical === "." || canonical === ".." || canonical.includes("/") || canonical.includes("\\") || hasUnsafeFilenameCodePoint(canonical)) {
98
+ throw new TypeError("share filename is unsafe");
99
+ }
100
+ return canonical;
101
+ }
102
+
88
103
  // ../share-envelope/dist/index.js
89
104
  var import_sha2 = require("@noble/hashes/sha2");
90
105
  var import_ed25519 = require("@noble/curves/ed25519");
@@ -6566,8 +6581,17 @@ function normalizeShareTarget(target) {
6566
6581
  function targetAuthorizationMethod(target) {
6567
6582
  return authorizationMethodForTarget(target);
6568
6583
  }
6584
+ function publishFilename(value) {
6585
+ try {
6586
+ return canonicalShareFilename(value);
6587
+ } catch {
6588
+ throw new SharePublishError("invalid-argument", hasUnsafeFilenameCodePoint(value) ? "filename contains control or invisible characters" : "filename must be one safe path segment");
6589
+ }
6590
+ }
6569
6591
  async function publishTargetShare(input) {
6570
6592
  const target = normalizeShareTarget(input.target);
6593
+ const filename = publishFilename(input.filename);
6594
+ const files = input.files?.map((file) => ({ ...file, filename: publishFilename(file.filename) }));
6571
6595
  if (input.targetAdapter === void 0) {
6572
6596
  if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
6573
6597
  return {
@@ -6593,9 +6617,6 @@ async function publishTargetShare(input) {
6593
6617
  return bytes3;
6594
6618
  })();
6595
6619
  if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
6596
- if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
6597
- throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
6598
- }
6599
6620
  if (target.kind === "bearer" && input.allowBinary !== true) {
6600
6621
  try {
6601
6622
  new TextDecoder("utf-8", { fatal: true }).decode(source);
@@ -6603,10 +6624,10 @@ async function publishTargetShare(input) {
6603
6624
  throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
6604
6625
  }
6605
6626
  }
6606
- const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
6627
+ const publishFiles = files === void 0 || files.length === 0 ? [{ bytes: source }] : files;
6607
6628
  const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
6608
6629
  let totalBytes = 0;
6609
- for (const file of files) {
6630
+ for (const file of publishFiles) {
6610
6631
  totalBytes += file.bytes.byteLength;
6611
6632
  if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
6612
6633
  throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
@@ -6617,8 +6638,8 @@ async function publishTargetShare(input) {
6617
6638
  if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
6618
6639
  return input.targetAdapter.publish({
6619
6640
  source,
6620
- filename: input.filename,
6621
- ...input.files === void 0 ? {} : { files: input.files },
6641
+ filename,
6642
+ ...files === void 0 ? {} : { files },
6622
6643
  ...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
6623
6644
  ...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
6624
6645
  ...input.actions === void 0 ? {} : { actions: input.actions },
@@ -6768,7 +6789,11 @@ function addressedCredentialRequirement(target) {
6768
6789
  return mailboxCredentialCommitment(normalized);
6769
6790
  }
6770
6791
  function prepareAddressedShare(request) {
6771
- if (request.filename.length === 0 || request.filename === "." || request.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(request.filename)) throw new TypeError("addressed filename is invalid");
6792
+ try {
6793
+ canonicalShareFilename(request.filename);
6794
+ } catch {
6795
+ throw new TypeError("addressed filename is invalid");
6796
+ }
6772
6797
  if (request.actions.length === 0 || request.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
6773
6798
  const target = normalizeShareTarget(request.target);
6774
6799
  if (target.kind === "bearer") throw new TypeError("addressed target is required");
@@ -6786,9 +6811,10 @@ function assertMailboxCommitment(commitment, target) {
6786
6811
  if (canonicalize(commitment.profile) !== canonicalize(expected.profile) || canonicalize(commitment.credentialType) !== canonicalize(expected.credentialType) || commitment.descriptorDigest !== expected.descriptorDigest || commitment.issuerDid !== expected.issuerDid || commitment.issuerKid !== expected.issuerKid) throw new TypeError(`${label} shares require the ${label} credential profile`);
6787
6812
  if (commitment.requirementDigest !== expected.requirementDigest) throw new TypeError(`credential requirement is not bound to the email ${target.kind === "email" ? "address" : "domain"}`);
6788
6813
  }
6789
- async function publishAddressedShare(options) {
6790
- assertSafeInput(options);
6791
- const { target } = prepareAddressedShare(options);
6814
+ async function publishAddressedShare(input) {
6815
+ assertSafeInput(input);
6816
+ const { target } = prepareAddressedShare(input);
6817
+ const options = { ...input, filename: canonicalShareFilename(input.filename) };
6792
6818
  if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
6793
6819
  const expiry = rfc3339Seconds(options.expiresAt);
6794
6820
  const matcher = targetMatcher(target);
@@ -8655,6 +8681,7 @@ async function openNativeShare(sharing, link) {
8655
8681
  authorizationMethodForTarget,
8656
8682
  authorizeShare,
8657
8683
  canonicalOwnerSharePolicy,
8684
+ canonicalShareFilename,
8658
8685
  canonicalize,
8659
8686
  checkBearerDelegation,
8660
8687
  claimShare,
@@ -8666,6 +8693,7 @@ async function openNativeShare(sharing, link) {
8666
8693
  createShareV2HolderBindingArtifact,
8667
8694
  deliverCredentialInvitation,
8668
8695
  fromBase64Url,
8696
+ hasUnsafeFilenameCodePoint,
8669
8697
  historyRecordForPublishedShare,
8670
8698
  inspectShare,
8671
8699
  listShares,