@openclaw/whatsapp 2026.8.1-beta.3 → 2026.8.2

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.
Files changed (263) hide show
  1. package/dist/{accounts-BX2fZUyR.js → accounts-CeKQbu_s.js} +9 -26
  2. package/dist/{action-runtime-pM-ExU0l.js → action-runtime-Bw0loEdy.js} +2 -2
  3. package/dist/action-runtime-api.js +1 -1
  4. package/dist/action-runtime.runtime.js +1 -1
  5. package/dist/{call-tool-api.js → agent-tools-api.js} +13 -6
  6. package/dist/{agent-tools-login-BjRpD-JQ.js → agent-tools-login-B-2qZPzc.js} +12 -3
  7. package/dist/api.js +10 -10
  8. package/dist/{approval-handler.runtime-CkMMvUjk.js → approval-handler.runtime-DeyPb57-.js} +3 -3
  9. package/dist/{approval-reactions-Jyr1JaAK.js → approval-reactions-DGDA6hQ7.js} +7 -5
  10. package/dist/{auth-store-CtpBqH5S.js → auth-store-DTj1H9pv.js} +5 -3
  11. package/dist/{channel-DRMYRfZ8.js → channel-DxMmDjqK.js} +26 -24
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel-react-action-CstrR0CT.js → channel-react-action-BUp7Wbfr.js} +3 -3
  15. package/dist/{channel.runtime-X3JC2gaq.js → channel.runtime-BwSnyXRX.js} +4 -4
  16. package/dist/{channel.setup-DhBBhbQv.js → channel.setup-aOFLSQyw.js} +1 -1
  17. package/dist/{config-schema-BQP5YCeK.js → config-schema-BYA6e_Ii.js} +6 -4
  18. package/dist/{connection-controller-CIYuBJrs.js → connection-controller-BvkxsbWi.js} +2 -2
  19. package/dist/{constants-HU41RHGI.js → constants-C7m81BNe.js} +1 -1
  20. package/dist/{directory-config-P7_3U73Z.js → directory-config-LgxYJLWY.js} +3 -3
  21. package/dist/directory-contract-api.js +1 -1
  22. package/dist/{group-session-key-DmFc_Cqi.js → group-session-key-KUguLmSZ.js} +4 -4
  23. package/dist/index.js +4 -4
  24. package/dist/light-runtime-api.js +2 -2
  25. package/dist/{login-BkpKpndm.js → login-Bk_jiXKD.js} +6 -5
  26. package/dist/{login-qr-1dpv6jjT.js → login-qr-B3m61NqA.js} +43 -20
  27. package/dist/login-qr-runtime.js +1 -1
  28. package/dist/{monitor-IzwQKizW.js → monitor-CTsyo-HE.js} +185 -162
  29. package/dist/{question-reactions-D3eAeB0i.js → question-reactions-B9D6MHXZ.js} +1 -1
  30. package/dist/runtime-api.js +9 -9
  31. package/dist/{send-CwvTU4RI.js → send-DiPjiygK.js} +54 -29
  32. package/dist/{send-api-obOSm2cC.js → send-api-kFcuhSiY.js} +43 -41
  33. package/dist/{setup-finalize-n4K2I2gJ.js → setup-finalize-CqH5wTK5.js} +7 -6
  34. package/dist/setup-plugin-api.js +1 -1
  35. package/dist/{setup-surface-Cwn3zyxK.js → setup-surface-Dm3T6S8t.js} +3 -3
  36. package/dist/{shared-wg42NKFB.js → shared-DDjonGys.js} +7 -6
  37. package/dist/{socket-close-2T0_jZTN.js → socket-close-CpkkJZCU.js} +9 -6
  38. package/dist/{targets-runtime-TMk0lGuy.js → targets-runtime-RBjx3pg_.js} +1 -2
  39. package/dist/{text-runtime-CaKLhowd.js → text-runtime-DyfmcATV.js} +1 -1
  40. package/node_modules/baileys/lib/Defaults/index.js +1 -1
  41. package/node_modules/baileys/lib/Socket/business.d.ts.map +1 -1
  42. package/node_modules/baileys/lib/Socket/chats.d.ts +1 -0
  43. package/node_modules/baileys/lib/Socket/chats.d.ts.map +1 -1
  44. package/node_modules/baileys/lib/Socket/chats.js +10 -5
  45. package/node_modules/baileys/lib/Socket/chats.js.map +1 -1
  46. package/node_modules/baileys/lib/Socket/communities.d.ts.map +1 -1
  47. package/node_modules/baileys/lib/Socket/groups.d.ts.map +1 -1
  48. package/node_modules/baileys/lib/Socket/index.d.ts.map +1 -1
  49. package/node_modules/baileys/lib/Socket/messages-recv.d.ts.map +1 -1
  50. package/node_modules/baileys/lib/Socket/messages-send.d.ts.map +1 -1
  51. package/node_modules/baileys/lib/Socket/newsletter.d.ts.map +1 -1
  52. package/node_modules/baileys/lib/Socket/socket.d.ts.map +1 -1
  53. package/node_modules/baileys/lib/Socket/socket.js +3 -0
  54. package/node_modules/baileys/lib/Socket/socket.js.map +1 -1
  55. package/node_modules/baileys/lib/Types/index.d.ts +1 -0
  56. package/node_modules/baileys/lib/Types/index.d.ts.map +1 -1
  57. package/node_modules/baileys/lib/Types/index.js.map +1 -1
  58. package/node_modules/baileys/lib/Utils/browser-utils.d.ts.map +1 -1
  59. package/node_modules/baileys/lib/Utils/browser-utils.js +1 -0
  60. package/node_modules/baileys/lib/Utils/browser-utils.js.map +1 -1
  61. package/node_modules/baileys/lib/Utils/generics.d.ts +1 -0
  62. package/node_modules/baileys/lib/Utils/generics.d.ts.map +1 -1
  63. package/node_modules/baileys/lib/Utils/generics.js +1 -1
  64. package/node_modules/baileys/lib/Utils/generics.js.map +1 -1
  65. package/node_modules/baileys/lib/Utils/tc-token-utils.d.ts.map +1 -1
  66. package/node_modules/baileys/lib/Utils/tc-token-utils.js +3 -2
  67. package/node_modules/baileys/lib/Utils/tc-token-utils.js.map +1 -1
  68. package/node_modules/baileys/lib/Utils/validate-connection.d.ts.map +1 -1
  69. package/node_modules/baileys/lib/Utils/validate-connection.js +9 -2
  70. package/node_modules/baileys/lib/Utils/validate-connection.js.map +1 -1
  71. package/node_modules/baileys/node_modules/content-type/README.md +2 -0
  72. package/node_modules/baileys/node_modules/content-type/dist/index.d.ts +20 -0
  73. package/node_modules/baileys/node_modules/content-type/dist/index.js +20 -14
  74. package/node_modules/baileys/node_modules/content-type/dist/index.js.map +1 -1
  75. package/node_modules/baileys/node_modules/content-type/package.json +1 -1
  76. package/node_modules/baileys/node_modules/file-type/package.json +1 -1
  77. package/node_modules/baileys/node_modules/file-type/readme.md +3 -0
  78. package/node_modules/baileys/node_modules/file-type/source/detectors/zip.js +6 -1
  79. package/node_modules/baileys/node_modules/file-type/source/index.d.ts +3 -0
  80. package/node_modules/baileys/node_modules/file-type/source/index.js +25 -13
  81. package/node_modules/baileys/node_modules/music-metadata/LICENSE.txt +9 -9
  82. package/node_modules/baileys/node_modules/music-metadata/README.md +878 -878
  83. package/node_modules/baileys/node_modules/music-metadata/lib/ParseError.d.ts +1 -1
  84. package/node_modules/baileys/node_modules/music-metadata/lib/ParseError.js +1 -1
  85. package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfObject.d.ts +1 -4
  86. package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfObject.js +66 -30
  87. package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfParser.d.ts +5 -1
  88. package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfParser.js +94 -25
  89. package/node_modules/baileys/node_modules/music-metadata/lib/dsf/DsfChunk.js +2 -2
  90. package/node_modules/baileys/node_modules/music-metadata/lib/dsf/DsfParser.js +38 -13
  91. package/node_modules/baileys/node_modules/music-metadata/lib/mp4/AtomToken.js +2 -1
  92. package/node_modules/baileys/node_modules/music-metadata/lib/mp4/MP4Parser.js +34 -7
  93. package/node_modules/baileys/node_modules/music-metadata/lib/ogg/speex/SpeexStream.d.ts +7 -0
  94. package/node_modules/baileys/node_modules/music-metadata/lib/ogg/speex/SpeexStream.js +13 -0
  95. package/node_modules/baileys/node_modules/music-metadata/lib/riff/RiffChunk.js +1 -1
  96. package/node_modules/baileys/node_modules/music-metadata/package.json +156 -156
  97. package/node_modules/baileys/node_modules/process-warning/.github/dependabot.yml +42 -2
  98. package/node_modules/baileys/node_modules/process-warning/.github/workflows/ci.yml +12 -1
  99. package/node_modules/baileys/node_modules/process-warning/.github/workflows/lock-threads.yml +19 -0
  100. package/node_modules/baileys/node_modules/process-warning/LICENSE +1 -1
  101. package/node_modules/baileys/node_modules/process-warning/README.md +41 -2
  102. package/node_modules/baileys/node_modules/process-warning/index.js +85 -17
  103. package/node_modules/baileys/node_modules/process-warning/package.json +6 -7
  104. package/node_modules/baileys/node_modules/process-warning/test/emit-once-only.test.js +3 -3
  105. package/node_modules/baileys/node_modules/process-warning/test/emit-reset.test.js +4 -4
  106. package/node_modules/baileys/node_modules/process-warning/test/emit-unlimited.test.js +2 -2
  107. package/node_modules/baileys/node_modules/process-warning/test/spy-warning.test.js +143 -0
  108. package/node_modules/baileys/node_modules/process-warning/types/index.d.ts +15 -1
  109. package/node_modules/baileys/node_modules/process-warning/types/index.tst.ts +48 -0
  110. package/node_modules/baileys/node_modules/protobufjs/README.md +61 -84
  111. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.js +66 -45
  112. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  113. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  114. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  115. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.js +47 -40
  116. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  117. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  118. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  119. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.js +92 -68
  120. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  121. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  122. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  123. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor.js +26 -10
  124. package/node_modules/baileys/node_modules/protobufjs/index.d.ts +3 -0
  125. package/node_modules/baileys/node_modules/protobufjs/package.json +1 -1
  126. package/node_modules/baileys/node_modules/protobufjs/src/converter.js +5 -1
  127. package/node_modules/baileys/node_modules/protobufjs/src/enum.js +5 -2
  128. package/node_modules/baileys/node_modules/protobufjs/src/field.js +6 -1
  129. package/node_modules/baileys/node_modules/protobufjs/src/parse.js +26 -23
  130. package/node_modules/baileys/node_modules/protobufjs/src/reader.js +36 -9
  131. package/node_modules/baileys/node_modules/protobufjs/src/util/codegen.js +3 -1
  132. package/node_modules/baileys/node_modules/protobufjs/src/util/utf8.js +2 -28
  133. package/node_modules/baileys/node_modules/protobufjs/src/writer.js +7 -1
  134. package/node_modules/baileys/node_modules/ws/lib/permessage-deflate.js +3 -1
  135. package/node_modules/baileys/node_modules/ws/package.json +1 -1
  136. package/node_modules/baileys/package.json +1 -1
  137. package/node_modules/typebox/build/format/date.d.mts +1 -1
  138. package/node_modules/typebox/build/format/date.mjs +1 -1
  139. package/node_modules/typebox/build/format/date_time.d.mts +2 -2
  140. package/node_modules/typebox/build/format/date_time.mjs +3 -3
  141. package/node_modules/typebox/build/format/email.d.mts +1 -1
  142. package/node_modules/typebox/build/format/email.mjs +2 -2
  143. package/node_modules/typebox/build/format/hostname.mjs +2 -10
  144. package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
  145. package/node_modules/typebox/build/format/idn_email.mjs +3 -3
  146. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
  147. package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
  148. package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
  149. package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
  150. package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
  151. package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
  152. package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
  153. package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
  154. package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
  155. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +41 -0
  156. package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
  157. package/node_modules/typebox/build/format/idna/index.mjs +2 -0
  158. package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
  159. package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
  160. package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
  161. package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
  162. package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
  163. package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
  164. package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
  165. package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
  166. package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
  167. package/node_modules/typebox/build/format/ipv4.mjs +2 -32
  168. package/node_modules/typebox/build/format/ipv6.mjs +2 -61
  169. package/node_modules/typebox/build/format/iri.d.mts +2 -2
  170. package/node_modules/typebox/build/format/iri.mjs +26 -8
  171. package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
  172. package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
  173. package/node_modules/typebox/build/format/regex.d.mts +1 -2
  174. package/node_modules/typebox/build/format/regex.mjs +2 -6
  175. package/node_modules/typebox/build/format/time.d.mts +2 -2
  176. package/node_modules/typebox/build/format/time.mjs +19 -14
  177. package/node_modules/typebox/build/format/uri.d.mts +1 -1
  178. package/node_modules/typebox/build/format/uri.mjs +3 -130
  179. package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
  180. package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
  181. package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
  182. package/node_modules/typebox/build/format/uri_template.mjs +4 -5
  183. package/node_modules/typebox/build/format/url.d.mts +3 -3
  184. package/node_modules/typebox/build/format/url.mjs +4 -5
  185. package/node_modules/typebox/build/guard/emit.d.mts +3 -1
  186. package/node_modules/typebox/build/guard/emit.mjs +12 -5
  187. package/node_modules/typebox/build/guard/guard.d.mts +8 -2
  188. package/node_modules/typebox/build/guard/guard.mjs +25 -4
  189. package/node_modules/typebox/build/guard/string.d.mts +2 -2
  190. package/node_modules/typebox/build/guard/string.mjs +7 -7
  191. package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
  192. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
  193. package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
  194. package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
  195. package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
  196. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
  197. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
  198. package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
  199. package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
  200. package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
  201. package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
  202. package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
  203. package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
  204. package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
  205. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
  206. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
  207. package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
  208. package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
  209. package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
  210. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
  211. package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
  212. package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
  213. package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
  214. package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
  215. package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
  216. package/node_modules/typebox/build/schema/types/index.mjs +0 -2
  217. package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
  218. package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
  219. package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
  220. package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
  221. package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
  222. package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
  223. package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
  224. package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
  225. package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
  226. package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
  227. package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
  228. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
  229. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
  230. package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
  231. package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
  232. package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
  233. package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
  234. package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
  235. package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
  236. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
  237. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
  238. package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
  239. package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
  240. package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
  241. package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
  242. package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
  243. package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
  244. package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
  245. package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
  246. package/node_modules/typebox/build/type/script/parser.mjs +27 -23
  247. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
  248. package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
  249. package/node_modules/typebox/package.json +27 -27
  250. package/node_modules/typebox/readme.md +79 -82
  251. package/openclaw.plugin.json +2 -1
  252. package/package.json +6 -6
  253. package/node_modules/baileys/node_modules/process-warning/types/index.test-d.ts +0 -36
  254. package/node_modules/typebox/build/format/_idna.d.mts +0 -2
  255. package/node_modules/typebox/build/format/_idna.mjs +0 -246
  256. package/node_modules/typebox/build/format/_puny.d.mts +0 -1
  257. package/node_modules/typebox/build/format/_puny.mjs +0 -77
  258. package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
  259. package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
  260. package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
  261. package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
  262. package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
  263. package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
@@ -43,7 +43,7 @@ declare const UnexpectedFileContentError_base: {
43
43
  prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
44
44
  stackTraceLimit: number;
45
45
  };
46
- declare class UnexpectedFileContentError extends UnexpectedFileContentError_base {
46
+ export declare class UnexpectedFileContentError extends UnexpectedFileContentError_base {
47
47
  readonly fileType: string;
48
48
  constructor(fileType: string, message: string);
49
49
  toString(): string;
@@ -13,7 +13,7 @@ export class CouldNotDetermineFileTypeError extends makeParseError('CouldNotDete
13
13
  export class UnsupportedFileTypeError extends makeParseError('UnsupportedFileTypeError') {
14
14
  }
15
15
  // Concrete error class representing unexpected file content.
16
- class UnexpectedFileContentError extends makeParseError('UnexpectedFileContentError') {
16
+ export class UnexpectedFileContentError extends makeParseError('UnexpectedFileContentError') {
17
17
  constructor(fileType, message) {
18
18
  super(message);
19
19
  this.fileType = fileType;
@@ -81,9 +81,6 @@ export declare abstract class State<T> implements IGetToken<T> {
81
81
  abstract get(buf: Uint8Array, off: number): T;
82
82
  protected postProcessTag(tags: ITag[], name: string, valueType: number, data: AnyTagValue): void;
83
83
  }
84
- export declare class IgnoreObjectState extends State<unknown> {
85
- get(_buf: Uint8Array, _off: number): null;
86
- }
87
84
  /**
88
85
  * Interface for: 3.2: File Properties Object (mandatory, one only)
89
86
  *
@@ -238,7 +235,7 @@ export interface ICodecEntry {
238
235
  * 3.5: Read the Codec-List-Object, which provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
239
236
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
240
237
  */
241
- export declare function readCodecEntries(tokenizer: ITokenizer): Promise<ICodecEntry[]>;
238
+ export declare function readCodecEntries(tokenizer: ITokenizer, payloadSize: number): Promise<ICodecEntry[]>;
242
239
  /**
243
240
  * 3.10 Content Description Object (optional, one only)
244
241
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
@@ -43,11 +43,14 @@ export const DataType = {
43
43
  export const TopLevelHeaderObjectToken = {
44
44
  len: 30,
45
45
  get: (buf, off) => {
46
+ const base = HeaderObjectToken.get(buf, off);
47
+ if (base.objectSize < TopLevelHeaderObjectToken.len) {
48
+ throw new AsfContentParseError(`Invalid ASF top-level header object size: ${base.objectSize}`);
49
+ }
46
50
  return {
47
- objectId: AsfGuid.fromBin(buf, off),
48
- objectSize: Number(Token.UINT64_LE.get(buf, off + 16)),
51
+ ...base,
49
52
  numberOfHeaderObjects: Token.UINT32_LE.get(buf, off + 24)
50
- // Reserved: 2 bytes
53
+ // reserved: 2 bytes
51
54
  };
52
55
  }
53
56
  };
@@ -58,15 +61,20 @@ export const TopLevelHeaderObjectToken = {
58
61
  export const HeaderObjectToken = {
59
62
  len: 24,
60
63
  get: (buf, off) => {
61
- return {
64
+ const objectSize = Token.UINT64_LE.get(buf, off + 16);
65
+ if (objectSize < BigInt(HeaderObjectToken.len) || objectSize > BigInt(Number.MAX_SAFE_INTEGER)) {
66
+ throw new AsfContentParseError(`Invalid ASF header object size: ${objectSize}`);
67
+ }
68
+ const header = {
62
69
  objectId: AsfGuid.fromBin(buf, off),
63
- objectSize: Number(Token.UINT64_LE.get(buf, off + 16))
70
+ objectSize: Number(objectSize)
64
71
  };
72
+ return header;
65
73
  }
66
74
  };
67
75
  export class State {
68
76
  constructor(header) {
69
- this.len = Number(header.objectSize) - HeaderObjectToken.len;
77
+ this.len = header.objectSize - HeaderObjectToken.len;
70
78
  }
71
79
  postProcessTag(tags, name, valueType, data) {
72
80
  if (name === 'WM/Picture') {
@@ -81,12 +89,6 @@ export class State {
81
89
  }
82
90
  }
83
91
  }
84
- // ToDo: use ignore type
85
- export class IgnoreObjectState extends State {
86
- get(_buf, _off) {
87
- return null;
88
- }
89
- }
90
92
  /**
91
93
  * Token for: 3.2: File Properties Object (mandatory, one only)
92
94
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
@@ -140,7 +142,7 @@ export class HeaderExtensionObject {
140
142
  return {
141
143
  reserved1: AsfGuid.fromBin(buf, off),
142
144
  reserved2: view.getUint16(16, true),
143
- extensionDataSize: view.getUint16(18, true)
145
+ extensionDataSize: view.getUint32(18, true)
144
146
  };
145
147
  }
146
148
  }
@@ -158,42 +160,76 @@ const CodecListObjectHeader = {
158
160
  };
159
161
  }
160
162
  };
161
- async function readString(tokenizer) {
162
- const length = await tokenizer.readNumber(Token.UINT16_LE);
163
- return (await tokenizer.readToken(new Token.StringType(length * 2, 'utf-16le'))).replace('\0', '');
163
+ class BoundedReader {
164
+ constructor(tokenizer, size) {
165
+ this.tokenizer = tokenizer;
166
+ this.remaining = size;
167
+ }
168
+ async readToken(token) {
169
+ this.ensureAvailable(token.len);
170
+ const value = await this.tokenizer.readToken(token);
171
+ this.remaining -= token.len;
172
+ return value;
173
+ }
174
+ async readNumber(token) {
175
+ return this.readToken(token);
176
+ }
177
+ async readBuffer(length) {
178
+ this.ensureAvailable(length);
179
+ const buffer = new Uint8Array(length);
180
+ await this.tokenizer.readBuffer(buffer);
181
+ this.remaining -= length;
182
+ return buffer;
183
+ }
184
+ async ignoreRemaining() {
185
+ const ignored = await this.tokenizer.ignore(this.remaining);
186
+ if (ignored !== this.remaining) {
187
+ throw new AsfContentParseError(`Unexpected end of ASF Codec List Object; missing ${this.remaining - ignored} bytes`);
188
+ }
189
+ this.remaining = 0;
190
+ }
191
+ ensureAvailable(length) {
192
+ if (length > this.remaining) {
193
+ throw new AsfContentParseError(`ASF Codec List field size ${length} exceeds remaining object payload size ${this.remaining}`);
194
+ }
195
+ }
196
+ }
197
+ async function readString(reader) {
198
+ const length = await reader.readNumber(Token.UINT16_LE);
199
+ return (await reader.readToken(new Token.StringType(length * 2, 'utf-16le'))).replace('\0', '');
164
200
  }
165
201
  /**
166
202
  * 3.5: Read the Codec-List-Object, which provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
167
203
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
168
204
  */
169
- export async function readCodecEntries(tokenizer) {
170
- const codecHeader = await tokenizer.readToken(CodecListObjectHeader);
205
+ export async function readCodecEntries(tokenizer, payloadSize) {
206
+ const reader = new BoundedReader(tokenizer, payloadSize);
207
+ const codecHeader = await reader.readToken(CodecListObjectHeader);
171
208
  const entries = [];
172
209
  for (let i = 0; i < codecHeader.entryCount; ++i) {
173
- entries.push(await readCodecEntry(tokenizer));
210
+ entries.push(await readCodecEntry(reader));
174
211
  }
212
+ await reader.ignoreRemaining();
175
213
  return entries;
176
214
  }
177
- async function readInformation(tokenizer) {
178
- const length = await tokenizer.readNumber(Token.UINT16_LE);
179
- const buf = new Uint8Array(length);
180
- await tokenizer.readBuffer(buf);
181
- return buf;
215
+ async function readInformation(reader) {
216
+ const length = await reader.readNumber(Token.UINT16_LE);
217
+ return reader.readBuffer(length);
182
218
  }
183
219
  /**
184
220
  * Read Codec-Entries
185
- * @param tokenizer
221
+ * @param reader
186
222
  */
187
- async function readCodecEntry(tokenizer) {
188
- const type = await tokenizer.readNumber(Token.UINT16_LE);
223
+ async function readCodecEntry(reader) {
224
+ const type = await reader.readNumber(Token.UINT16_LE);
189
225
  return {
190
226
  type: {
191
227
  videoCodec: (type & 0x0001) === 0x0001,
192
228
  audioCodec: (type & 0x0002) === 0x0002
193
229
  },
194
- codecName: await readString(tokenizer),
195
- description: await readString(tokenizer),
196
- information: await readInformation(tokenizer)
230
+ codecName: await readString(reader),
231
+ description: await readString(reader),
232
+ information: await readInformation(reader)
197
233
  };
198
234
  }
199
235
  /**
@@ -11,7 +11,11 @@ import { BasicParser } from '../common/BasicParser.js';
11
11
  */
12
12
  export declare class AsfParser extends BasicParser {
13
13
  parse(): Promise<void>;
14
- private parseObjectHeader;
14
+ private parseObjectHeaders;
15
15
  private addTags;
16
+ private readObjectHeader;
17
+ private validatePayloadAvailability;
18
+ private validateAllocationSize;
19
+ private ignorePayload;
16
20
  private parseExtensionObject;
17
21
  }
@@ -1,4 +1,5 @@
1
1
  import initDebug from 'debug';
2
+ import { EndOfStreamError } from 'strtok3';
2
3
  import { TrackType } from '../type.js';
3
4
  import AsfGuid from './AsfGuid.js';
4
5
  import * as AsfObject from './AsfObject.js';
@@ -6,6 +7,7 @@ import { BasicParser } from '../common/BasicParser.js';
6
7
  import { AsfContentParseError } from './AsfObject.js';
7
8
  const debug = initDebug('music-metadata:parser:ASF');
8
9
  const headerType = 'asf';
10
+ const maxAsfMetadataObjectSize = 16 * 1024 * 1024;
9
11
  /**
10
12
  * Windows Media Metadata Usage Guidelines
11
13
  * - Ref: https://msdn.microsoft.com/en-us/library/ms867702.aspx
@@ -22,42 +24,64 @@ export class AsfParser extends BasicParser {
22
24
  if (!header.objectId.equals(AsfGuid.HeaderObject)) {
23
25
  throw new AsfContentParseError(`expected asf header; but was not found; got: ${header.objectId.str}`);
24
26
  }
25
- await this.parseObjectHeader(header.numberOfHeaderObjects);
27
+ if (header.numberOfHeaderObjects < 1 || header.numberOfHeaderObjects > 10000) {
28
+ throw new AsfContentParseError(`Unrealistic number of ASF header objects: ${header.numberOfHeaderObjects}`);
29
+ }
30
+ await this.parseObjectHeaders(header.numberOfHeaderObjects, header.objectSize - AsfObject.TopLevelHeaderObjectToken.len);
26
31
  }
27
- async parseObjectHeader(numberOfObjectHeaders) {
32
+ async parseObjectHeaders(numberOfObjectHeaders, remainingHeaderSize) {
28
33
  let tags;
29
- do {
30
- // Parse common header of the ASF Object (3.1)
31
- const header = await this.tokenizer.readToken(AsfObject.HeaderObjectToken);
34
+ for (let index = 0; index < numberOfObjectHeaders; ++index) {
35
+ const { header, payloadSize } = await this.readObjectHeader(remainingHeaderSize, 'header');
32
36
  // Parse data part of the ASF Object
33
37
  debug('header GUID=%s', header.objectId.str);
34
38
  switch (header.objectId.str) {
35
39
  case AsfObject.FilePropertiesObject.guid.str: { // 3.2
40
+ this.validateAllocationSize(payloadSize, 'File Properties Object');
36
41
  const fpo = await this.tokenizer.readToken(new AsfObject.FilePropertiesObject(header));
37
42
  this.metadata.setFormat('duration', Number(fpo.playDuration / BigInt(1000)) / 10000 - Number(fpo.preroll) / 1000);
38
43
  this.metadata.setFormat('bitrate', fpo.maximumBitrate);
39
44
  break;
40
45
  }
41
46
  case AsfObject.StreamPropertiesObject.guid.str: { // 3.3
47
+ this.validateAllocationSize(payloadSize, 'Stream Properties Object');
42
48
  const spo = await this.tokenizer.readToken(new AsfObject.StreamPropertiesObject(header));
43
49
  this.metadata.setFormat('container', `ASF/${spo.streamType}`);
44
50
  break;
45
51
  }
46
52
  case AsfObject.HeaderExtensionObject.guid.str: { // 3.4
47
- const extHeader = await this.tokenizer.readToken(new AsfObject.HeaderExtensionObject());
53
+ const extensionHeaderToken = new AsfObject.HeaderExtensionObject();
54
+ if (payloadSize < extensionHeaderToken.len) {
55
+ throw new AsfContentParseError(`ASF Header Extension Object payload is too small: ${payloadSize} bytes`);
56
+ }
57
+ const extHeader = await this.tokenizer.readToken(extensionHeaderToken);
58
+ const expectedExtensionSize = payloadSize - extensionHeaderToken.len;
59
+ if (extHeader.extensionDataSize !== expectedExtensionSize) {
60
+ throw new AsfContentParseError(`ASF extension data size ${extHeader.extensionDataSize} does not match enclosing payload size ${expectedExtensionSize}`);
61
+ }
48
62
  await this.parseExtensionObject(extHeader.extensionDataSize);
49
63
  break;
50
64
  }
51
65
  case AsfObject.ContentDescriptionObjectState.guid.str: // 3.10
66
+ this.validateAllocationSize(payloadSize, 'Content Description Object');
52
67
  tags = await this.tokenizer.readToken(new AsfObject.ContentDescriptionObjectState(header));
53
68
  await this.addTags(tags);
54
69
  break;
55
70
  case AsfObject.ExtendedContentDescriptionObjectState.guid.str: // 3.11
71
+ this.validateAllocationSize(payloadSize, 'Extended Content Description Object');
56
72
  tags = await this.tokenizer.readToken(new AsfObject.ExtendedContentDescriptionObjectState(header));
57
73
  await this.addTags(tags);
58
74
  break;
59
75
  case AsfGuid.CodecListObject.str: {
60
- const codecs = await AsfObject.readCodecEntries(this.tokenizer);
76
+ this.validateAllocationSize(payloadSize, 'Codec List Object');
77
+ let codecs;
78
+ try {
79
+ codecs = await AsfObject.readCodecEntries(this.tokenizer, payloadSize);
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ throw new AsfContentParseError(`Invalid ASF Codec List Object: ${message}`);
84
+ }
61
85
  codecs.forEach(codec => {
62
86
  this.metadata.addStreamInfo({
63
87
  type: codec.type.videoCodec ? TrackType.video : TrackType.audio,
@@ -70,65 +94,110 @@ export class AsfParser extends BasicParser {
70
94
  }
71
95
  case AsfGuid.StreamBitratePropertiesObject.str:
72
96
  // ToDo?
73
- await this.tokenizer.ignore(header.objectSize - AsfObject.HeaderObjectToken.len);
97
+ await this.ignorePayload(payloadSize, 'Stream Bitrate Properties Object');
74
98
  break;
75
99
  case AsfGuid.PaddingObject.str:
76
100
  // ToDo: register bytes pad
77
- debug('Padding: %s bytes', header.objectSize - AsfObject.HeaderObjectToken.len);
78
- await this.tokenizer.ignore(header.objectSize - AsfObject.HeaderObjectToken.len);
101
+ debug('Padding: %s bytes', payloadSize);
102
+ await this.ignorePayload(payloadSize, 'Padding Object');
79
103
  break;
80
104
  default:
81
105
  this.metadata.addWarning(`Ignore ASF-Object-GUID: ${header.objectId.str}`);
82
106
  debug('Ignore ASF-Object-GUID: %s', header.objectId.str);
83
- await this.tokenizer.readToken(new AsfObject.IgnoreObjectState(header));
107
+ await this.ignorePayload(payloadSize, `object ${header.objectId.str}`);
84
108
  }
85
- } while (--numberOfObjectHeaders);
86
- // done
109
+ remainingHeaderSize -= header.objectSize;
110
+ }
111
+ if (remainingHeaderSize !== 0) {
112
+ throw new AsfContentParseError(`ASF header child objects leave ${remainingHeaderSize} payload byte(s) unaccounted`);
113
+ }
87
114
  }
88
115
  async addTags(tags) {
89
116
  await Promise.all(tags.map(({ id, value }) => this.metadata.addTag(headerType, id, value)));
90
117
  }
91
- async parseExtensionObject(extensionSize) {
92
- do {
93
- // Parse common header of the ASF Object (3.1)
94
- const header = await this.tokenizer.readToken(AsfObject.HeaderObjectToken);
95
- const remaining = header.objectSize - AsfObject.HeaderObjectToken.len;
96
- if (remaining < 0) {
97
- throw new AsfContentParseError(`Invalid ASF header object size: ${header.objectSize}`);
118
+ async readObjectHeader(remainingSize, containerType) {
119
+ if (remainingSize < AsfObject.HeaderObjectToken.len) {
120
+ throw new AsfContentParseError(`Insufficient ASF ${containerType} data for an object header: ${remainingSize} bytes`);
121
+ }
122
+ const header = await this.tokenizer.readToken(AsfObject.HeaderObjectToken);
123
+ if (header.objectSize > remainingSize) {
124
+ throw new AsfContentParseError(`ASF ${containerType} object size ${header.objectSize} exceeds remaining ${containerType} payload size ${remainingSize}`);
125
+ }
126
+ return {
127
+ header,
128
+ payloadSize: this.validatePayloadAvailability(header, `${containerType} object`)
129
+ };
130
+ }
131
+ validatePayloadAvailability(header, objectType) {
132
+ const payloadSize = header.objectSize - AsfObject.HeaderObjectToken.len;
133
+ const fileSize = this.tokenizer.fileInfo.size;
134
+ if (fileSize !== undefined) {
135
+ const available = fileSize - this.tokenizer.position;
136
+ if (payloadSize > available) {
137
+ throw new AsfContentParseError(`ASF ${objectType} payload size ${payloadSize} exceeds available input size ${available}`);
138
+ }
139
+ }
140
+ return payloadSize;
141
+ }
142
+ validateAllocationSize(payloadSize, objectType) {
143
+ if (payloadSize > maxAsfMetadataObjectSize) {
144
+ throw new AsfContentParseError(`ASF ${objectType} payload size ${payloadSize} exceeds allocation limit ${maxAsfMetadataObjectSize}`);
145
+ }
146
+ }
147
+ async ignorePayload(payloadSize, objectType) {
148
+ let ignored;
149
+ try {
150
+ ignored = await this.tokenizer.ignore(payloadSize);
151
+ }
152
+ catch (error) {
153
+ if (error instanceof EndOfStreamError) {
154
+ throw new AsfContentParseError(`Unexpected end of ASF ${objectType}`);
98
155
  }
156
+ throw error;
157
+ }
158
+ if (ignored !== payloadSize) {
159
+ throw new AsfContentParseError(`Unexpected end of ASF ${objectType}; missing ${payloadSize - ignored} bytes`);
160
+ }
161
+ }
162
+ async parseExtensionObject(extensionSize) {
163
+ while (extensionSize > 0) {
164
+ const { header, payloadSize } = await this.readObjectHeader(extensionSize, 'extension');
99
165
  // Parse data part of the ASF Object
100
166
  switch (header.objectId.str) {
101
167
  case AsfObject.ExtendedStreamPropertiesObjectState.guid.str: // 4.1
102
168
  // ToDo: extended stream header properties are ignored
169
+ this.validateAllocationSize(payloadSize, 'Extended Stream Properties Object');
103
170
  await this.tokenizer.readToken(new AsfObject.ExtendedStreamPropertiesObjectState(header));
104
171
  break;
105
172
  case AsfObject.MetadataObjectState.guid.str: { // 4.7
173
+ this.validateAllocationSize(payloadSize, 'Metadata Object');
106
174
  const moTags = await this.tokenizer.readToken(new AsfObject.MetadataObjectState(header));
107
175
  await this.addTags(moTags);
108
176
  break;
109
177
  }
110
178
  case AsfObject.MetadataLibraryObjectState.guid.str: { // 4.8
179
+ this.validateAllocationSize(payloadSize, 'Metadata Library Object');
111
180
  const mlTags = await this.tokenizer.readToken(new AsfObject.MetadataLibraryObjectState(header));
112
181
  await this.addTags(mlTags);
113
182
  break;
114
183
  }
115
184
  case AsfGuid.PaddingObject.str:
116
185
  // ToDo: register bytes pad
117
- await this.tokenizer.ignore(remaining);
186
+ await this.ignorePayload(payloadSize, 'extension Padding Object');
118
187
  break;
119
188
  case AsfGuid.CompatibilityObject.str:
120
- await this.tokenizer.ignore(remaining);
189
+ await this.ignorePayload(payloadSize, 'Compatibility Object');
121
190
  break;
122
191
  case AsfGuid.ASF_Index_Placeholder_Object.str:
123
- await this.tokenizer.ignore(remaining);
192
+ await this.ignorePayload(payloadSize, 'Index Placeholder Object');
124
193
  break;
125
194
  default:
126
195
  this.metadata.addWarning(`Ignore ASF-Object-GUID: ${header.objectId.str}`);
127
196
  // console.log("Ignore ASF-Object-GUID: %s", header.objectId.str);
128
- await this.tokenizer.readToken(new AsfObject.IgnoreObjectState(header));
197
+ await this.ignorePayload(payloadSize, `extension object ${header.objectId.str}`);
129
198
  break;
130
199
  }
131
200
  extensionSize -= header.objectSize;
132
- } while (extensionSize > 0);
201
+ }
133
202
  }
134
203
  }
@@ -16,8 +16,8 @@ export const DsdChunk = {
16
16
  len: 16,
17
17
  get: (buf, off) => {
18
18
  return {
19
- fileSize: Token.INT64_LE.get(buf, off),
20
- metadataPointer: Token.INT64_LE.get(buf, off + 8)
19
+ fileSize: Token.UINT64_LE.get(buf, off),
20
+ metadataPointer: Token.UINT64_LE.get(buf, off + 8)
21
21
  };
22
22
  }
23
23
  };
@@ -1,8 +1,9 @@
1
1
  import initDebug from 'debug';
2
2
  import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
3
3
  import { ChunkHeader, DsdChunk, FormatChunk } from './DsfChunk.js';
4
- import { ID3v2Parser } from "../id3v2/ID3v2Parser.js";
4
+ import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
5
5
  import { makeUnexpectedFileContentError } from '../ParseError.js';
6
+ import { ID3v2Header } from '../id3v2/ID3v2Token.js';
6
7
  const debug = initDebug('music-metadata:parser:DSF');
7
8
  export class DsdContentParseError extends makeUnexpectedFileContentError('DSD') {
8
9
  }
@@ -14,41 +15,65 @@ export class DsfParser extends AbstractID3Parser {
14
15
  async postId3v2Parse() {
15
16
  const p0 = this.tokenizer.position; // mark start position, normally 0
16
17
  const chunkHeader = await this.tokenizer.readToken(ChunkHeader);
17
- if (chunkHeader.id !== 'DSD ')
18
+ if (chunkHeader.id !== 'DSD ') {
18
19
  throw new DsdContentParseError('Invalid chunk signature');
20
+ }
21
+ if (chunkHeader.size !== BigInt(ChunkHeader.len + DsdChunk.len)) {
22
+ throw new DsdContentParseError(`Invalid DSD chunk size: ${chunkHeader.size}`);
23
+ }
19
24
  this.metadata.setFormat('container', 'DSF');
20
25
  this.metadata.setFormat('lossless', true);
21
26
  this.metadata.setAudioOnly();
22
27
  const dsdChunk = await this.tokenizer.readToken(DsdChunk);
23
- if (dsdChunk.metadataPointer === BigInt(0)) {
24
- debug("No ID3v2 tag present");
28
+ if (dsdChunk.fileSize < chunkHeader.size) {
29
+ throw new DsdContentParseError(`Invalid DSF file size: ${dsdChunk.fileSize}`);
30
+ }
31
+ await this.parseChunks(dsdChunk.fileSize - chunkHeader.size);
32
+ if (dsdChunk.metadataPointer === 0n) {
33
+ debug('No ID3v2 tag present');
34
+ return;
25
35
  }
26
- else {
27
- debug(`expect ID3v2 at offset=${dsdChunk.metadataPointer}`);
28
- await this.parseChunks(dsdChunk.fileSize - chunkHeader.size);
29
- // Jump to ID3 header
30
- await this.tokenizer.ignore(Number(dsdChunk.metadataPointer) - this.tokenizer.position - p0);
31
- return new ID3v2Parser().parse(this.metadata, this.tokenizer, this.options);
36
+ debug(`expect ID3v2 at offset=${dsdChunk.metadataPointer}`);
37
+ const metadataOffset = dsdChunk.metadataPointer - BigInt(this.tokenizer.position - p0);
38
+ if (metadataOffset < 0n ||
39
+ metadataOffset > BigInt(Number.MAX_SAFE_INTEGER) ||
40
+ dsdChunk.metadataPointer + BigInt(ID3v2Header.len) > dsdChunk.fileSize) {
41
+ throw new DsdContentParseError(`Invalid metadata pointer: ${dsdChunk.metadataPointer}`);
32
42
  }
43
+ await this.tokenizer.ignore(Number(metadataOffset));
44
+ return new ID3v2Parser().parse(this.metadata, this.tokenizer, this.options);
33
45
  }
34
46
  async parseChunks(bytesRemaining) {
35
- while (bytesRemaining >= ChunkHeader.len) {
47
+ const chunkHeaderSize = BigInt(ChunkHeader.len);
48
+ while (bytesRemaining >= chunkHeaderSize) {
36
49
  const chunkHeader = await this.tokenizer.readToken(ChunkHeader);
37
50
  debug(`Parsing chunk name=${chunkHeader.id} size=${chunkHeader.size}`);
51
+ if (chunkHeader.size < chunkHeaderSize) {
52
+ throw new DsdContentParseError(`Invalid ${chunkHeader.id} chunk size: ${chunkHeader.size}`);
53
+ }
54
+ if (chunkHeader.size > bytesRemaining) {
55
+ throw new DsdContentParseError(`${chunkHeader.id} chunk exceeds remaining file size`);
56
+ }
57
+ const payloadSize = chunkHeader.size - chunkHeaderSize;
38
58
  switch (chunkHeader.id) {
39
59
  case 'fmt ': {
60
+ if (payloadSize < BigInt(FormatChunk.len)) {
61
+ throw new DsdContentParseError(`Invalid fmt chunk size: ${chunkHeader.size}`);
62
+ }
40
63
  const formatChunk = await this.tokenizer.readToken(FormatChunk);
41
64
  this.metadata.setFormat('numberOfChannels', formatChunk.channelNum);
42
65
  this.metadata.setFormat('sampleRate', formatChunk.samplingFrequency);
43
66
  this.metadata.setFormat('bitsPerSample', formatChunk.bitsPerSample);
44
67
  this.metadata.setFormat('numberOfSamples', formatChunk.sampleCount);
45
68
  this.metadata.setFormat('duration', Number(formatChunk.sampleCount) / formatChunk.samplingFrequency);
46
- const bitrate = formatChunk.bitsPerSample * formatChunk.samplingFrequency * formatChunk.channelNum;
69
+ const bitrate = formatChunk.bitsPerSample *
70
+ formatChunk.samplingFrequency *
71
+ formatChunk.channelNum;
47
72
  this.metadata.setFormat('bitrate', bitrate);
48
73
  return; // We got what we want, stop further processing of chunks
49
74
  }
50
75
  default:
51
- this.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len);
76
+ await this.tokenizer.ignore(Number(payloadSize));
52
77
  break;
53
78
  }
54
79
  bytesRemaining -= chunkHeader.size;
@@ -313,7 +313,8 @@ export class StsdAtom {
313
313
  const size = Token.UINT32_BE.get(buf, off); // Sample description size
314
314
  off += Token.UINT32_BE.len;
315
315
  table.push(new SampleDescriptionTable(size - Token.UINT32_BE.len).get(buf, off));
316
- off += size;
316
+ // A SampleEntry extends Box, so its size covers the size field already stepped over
317
+ off += size - Token.UINT32_BE.len;
317
318
  }
318
319
  return {
319
320
  header,
@@ -72,6 +72,26 @@ const encoderDict = {
72
72
  function distinct(value, index, self) {
73
73
  return self.indexOf(value) === index;
74
74
  }
75
+ /**
76
+ * Determine if a track carries audio.
77
+ *
78
+ * Ref: ISO/IEC 14496-12, 8.5.2: the sample entries in a sample description box are track-type specific,
79
+ * selected by the handler type of the enclosing track. Only an audio track holds an AudioSampleEntry, so
80
+ * only such a track may be described by a sound sample description; `isAudio()` treats both the 'soun'
81
+ * and 'audi' handler types as audio.
82
+ *
83
+ * Falls back to the sample description when no handler box was found, preserving the previous behavior.
84
+ */
85
+ function isAudioTrack(track) {
86
+ const [ssd] = track.soundSampleDescription ?? [];
87
+ if (!ssd) {
88
+ return false;
89
+ }
90
+ if (track.isAudio) {
91
+ return track.isAudio();
92
+ }
93
+ return !!ssd.description && ssd.description.numAudioChannels > 0;
94
+ }
75
95
  /*
76
96
  * Parser for the MP4 (MPEG-4 Part 14) container format
77
97
  * Standard: ISO/IEC 14496-14
@@ -209,6 +229,7 @@ export class MP4Parser extends BasicParser {
209
229
  const formatList = [];
210
230
  this.tracks.forEach(track => {
211
231
  const trackFormats = [];
232
+ const trackIsAudio = isAudioTrack(track);
212
233
  track.soundSampleDescription.forEach(ssd => {
213
234
  const streamInfo = {};
214
235
  const encoderInfo = encoderDict[ssd.dataFormat];
@@ -219,7 +240,7 @@ export class MP4Parser extends BasicParser {
219
240
  else {
220
241
  streamInfo.codecName = `<${ssd.dataFormat}>`;
221
242
  }
222
- if (ssd.description) {
243
+ if (trackIsAudio && ssd.description) {
223
244
  const { description } = ssd;
224
245
  if (description.sampleRate > 0) {
225
246
  streamInfo.type = TrackType.audio;
@@ -239,9 +260,7 @@ export class MP4Parser extends BasicParser {
239
260
  if (formatList.length > 0) {
240
261
  this.metadata.setFormat('codec', formatList.filter(distinct).join('+'));
241
262
  }
242
- const audioTracks = [...this.tracks.values()].filter(track => {
243
- return track.soundSampleDescription.length >= 1 && track.soundSampleDescription[0].description && track.soundSampleDescription[0].description.numAudioChannels > 0;
244
- });
263
+ const audioTracks = [...this.tracks.values()].filter(isAudioTrack);
245
264
  // Calculate duration and bitrate of audio tracks
246
265
  for (const audioTrack of audioTracks) {
247
266
  if (audioTrack.media.header && audioTrack.media.header.timeScale > 0) {
@@ -561,12 +580,20 @@ export class MP4Parser extends BasicParser {
561
580
  dataReferenceIndex: sampleDescription.dataReferenceIndex
562
581
  };
563
582
  let offset = 0;
564
- if (sampleDescription.description) {
565
- const version = AtomToken.SoundSampleDescriptionVersion.get(sampleDescription.description, offset);
583
+ const { description } = sampleDescription;
584
+ // Only an AudioSampleEntry carries a sound sample description; it is at least 20 bytes beyond the
585
+ // 16-byte SampleEntry base. A shorter description belongs to another sample entry class, or is truncated.
586
+ if (description && description.length >= AtomToken.SoundSampleDescriptionVersion.len) {
587
+ const version = AtomToken.SoundSampleDescriptionVersion.get(description, offset);
566
588
  offset += AtomToken.SoundSampleDescriptionVersion.len;
567
589
  if (version.version === 0 || version.version === 1) {
568
590
  // Sound Sample Description (Version 0)
569
- ssd.description = AtomToken.SoundSampleDescriptionV0.get(sampleDescription.description, offset);
591
+ if (description.length >= offset + AtomToken.SoundSampleDescriptionV0.len) {
592
+ ssd.description = AtomToken.SoundSampleDescriptionV0.get(description, offset);
593
+ }
594
+ else {
595
+ debug(`Warning: sound-sample-description too short: ${description.length} bytes`);
596
+ }
570
597
  }
571
598
  else {
572
599
  debug(`Warning: sound-sample-description ${version} not implemented`);
@@ -10,6 +10,7 @@ import type { INativeMetadataCollector } from '../../common/MetadataCollector.js
10
10
  * - https://tools.ietf.org/html/rfc5574
11
11
  */
12
12
  export declare class SpeexStream extends VorbisStream {
13
+ private commentHeaderParsed;
13
14
  constructor(metadata: INativeMetadataCollector, options: IOptions, _tokenizer: ITokenizer);
14
15
  /**
15
16
  * Parse first Speex Ogg page
@@ -17,4 +18,10 @@ export declare class SpeexStream extends VorbisStream {
17
18
  * @param {Uint8Array} pageData
18
19
  */
19
20
  protected parseFirstPage(_header: IPageHeader, pageData: Uint8Array): void;
21
+ /**
22
+ * The packet following the identification header is the comment header, in
23
+ * Vorbis comment format but without a packet type or magic signature.
24
+ * Ref: https://tools.ietf.org/html/rfc5574#section-3
25
+ */
26
+ protected parseFullPage(pageData: Uint8Array): Promise<void>;
20
27
  }