@coderline/alphatab 1.9.0-alpha.1804 → 1.9.0-alpha.1806

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.9.0-alpha.1804 (develop, build 1804)
2
+ * alphaTab v1.9.0-alpha.1806 (develop, build 1806)
3
3
  *
4
4
  * Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.9.0-alpha.1804';
207
- static date = '2026-05-16T03:54:50.685Z';
208
- static commit = '6e757c0cbec66598a7fa5327abc9d05616984486';
206
+ static version = '1.9.0-alpha.1806';
207
+ static date = '2026-05-18T04:36:35.809Z';
208
+ static commit = 'e926f2b6bcfbaa219a04140bf34fdd9431a6ca17';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -16001,6 +16001,68 @@ class AlphaTex1LanguageHandler {
16001
16001
  }
16002
16002
  }
16003
16003
 
16004
+ /**
16005
+ * Thrown whenever we hit the end of input data unexpectedly.
16006
+ * @public
16007
+ */
16008
+ class EndOfReaderError extends AlphaTabError {
16009
+ constructor() {
16010
+ super(AlphaTabErrorType.Format, 'Unexpected end of data within reader');
16011
+ }
16012
+ }
16013
+ /**
16014
+ * Thrown whenever an overflow in data or buffer sizes is detected.
16015
+ * @public
16016
+ */
16017
+ class OverflowError extends AlphaTabError {
16018
+ constructor(message) {
16019
+ super(AlphaTabErrorType.Format, message);
16020
+ }
16021
+ }
16022
+ /**
16023
+ * An {@see IReadable} implementation throwing when the end of stream is reached guarding against
16024
+ * corrupted or maliciously crafted files leading to endless reading
16025
+ * @internal
16026
+ */
16027
+ class ThrowingReadable {
16028
+ _readable;
16029
+ constructor(readable) {
16030
+ this._readable = readable;
16031
+ }
16032
+ get position() {
16033
+ return this._readable.position;
16034
+ }
16035
+ set position(value) {
16036
+ this._readable.position = value;
16037
+ }
16038
+ get length() {
16039
+ return this._readable.length;
16040
+ }
16041
+ reset() {
16042
+ this._readable.reset();
16043
+ }
16044
+ skip(offset) {
16045
+ this._readable.skip(offset);
16046
+ }
16047
+ _requireBytes(bytes) {
16048
+ const remaining = this.length - this.position;
16049
+ if (remaining < bytes) {
16050
+ throw new EndOfReaderError();
16051
+ }
16052
+ }
16053
+ readByte() {
16054
+ this._requireBytes(1);
16055
+ return this._readable.readByte();
16056
+ }
16057
+ read(buffer, offset, count) {
16058
+ this._requireBytes(count);
16059
+ return this._readable.read(buffer, offset, count);
16060
+ }
16061
+ readAll() {
16062
+ return this._readable.readAll();
16063
+ }
16064
+ }
16065
+
16004
16066
  /**
16005
16067
  * This is the base public class for creating new song importers which
16006
16068
  * enable reading scores from any binary datasource
@@ -16013,7 +16075,12 @@ class ScoreImporter {
16013
16075
  * Initializes the importer with the given data and settings.
16014
16076
  */
16015
16077
  init(data, settings) {
16016
- this.data = data;
16078
+ if (data instanceof ThrowingReadable) {
16079
+ this.data = data;
16080
+ }
16081
+ else {
16082
+ this.data = new ThrowingReadable(data);
16083
+ }
16017
16084
  this.settings = settings;
16018
16085
  // when beginning reading a new score we reset the IDs.
16019
16086
  Score.resetIds();
@@ -17682,8 +17749,10 @@ class ZipEntry {
17682
17749
  */
17683
17750
  class ZipReader {
17684
17751
  _readable;
17685
- constructor(readable) {
17752
+ _maxDecodingBufferSize;
17753
+ constructor(readable, maxDecodingBufferSize) {
17686
17754
  this._readable = readable;
17755
+ this._maxDecodingBufferSize = maxDecodingBufferSize;
17687
17756
  }
17688
17757
  read() {
17689
17758
  const entries = [];
@@ -17715,7 +17784,13 @@ class ZipReader {
17715
17784
  IOHelper.readInt32LE(readable); // crc-32
17716
17785
  IOHelper.readInt32LE(readable); // compressed size
17717
17786
  const uncompressedSize = IOHelper.readInt32LE(readable);
17787
+ if (uncompressedSize > this._maxDecodingBufferSize) {
17788
+ throw new OverflowError(`Zip contains files exceeding the configured maxDecodingBufferSize`);
17789
+ }
17718
17790
  const fileNameLength = IOHelper.readInt16LE(readable);
17791
+ if (fileNameLength > this._maxDecodingBufferSize) {
17792
+ throw new OverflowError(`Zip contains file names exceeding the configured maxDecodingBufferSize`);
17793
+ }
17719
17794
  const extraFieldLength = IOHelper.readInt16LE(readable);
17720
17795
  const fname = IOHelper.toString(IOHelper.readByteArray(readable, fileNameLength), 'utf-8');
17721
17796
  readable.skip(extraFieldLength);
@@ -17728,6 +17803,9 @@ class ZipReader {
17728
17803
  while (true) {
17729
17804
  const bytes = z.readBytes(buffer, 0, buffer.length);
17730
17805
  target.write(buffer, 0, bytes);
17806
+ if (target.length > this._maxDecodingBufferSize) {
17807
+ throw new OverflowError(`Zip entry "${fname}" contains data exceeding the configured maxDecodingBufferSize`);
17808
+ }
17731
17809
  if (bytes < buffer.length) {
17732
17810
  break;
17733
17811
  }
@@ -19676,7 +19754,7 @@ class CapellaImporter extends ScoreImporter {
19676
19754
  }
19677
19755
  readScore() {
19678
19756
  Logger.debug(this.name, 'Loading ZIP entries');
19679
- const fileSystem = new ZipReader(this.data);
19757
+ const fileSystem = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
19680
19758
  let entries;
19681
19759
  let xml = null;
19682
19760
  entries = fileSystem.read();
@@ -19806,7 +19884,7 @@ class Gp3To5Importer extends ScoreImporter {
19806
19884
  this._initialTempo = Automation.buildTempoAutomation(false, 0, 0, 0);
19807
19885
  if (this._versionNumber >= 500) {
19808
19886
  this.readPageSetup();
19809
- this._initialTempo.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
19887
+ this._initialTempo.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19810
19888
  }
19811
19889
  // tempo stuff
19812
19890
  this._initialTempo.value = IOHelper.readInt32LE(this.data);
@@ -19845,7 +19923,9 @@ class Gp3To5Importer extends ScoreImporter {
19845
19923
  }
19846
19924
  // contents
19847
19925
  this._barCount = IOHelper.readInt32LE(this.data);
19926
+ this._ensureLoopBoundary(this._barCount, Gp3To5Importer._maxBarCount, 'bar count');
19848
19927
  this._trackCount = IOHelper.readInt32LE(this.data);
19928
+ this._ensureLoopBoundary(this._trackCount, Gp3To5Importer._maxTrackCount, 'track count');
19849
19929
  this.readMasterBars();
19850
19930
  this.readTracks();
19851
19931
  this.readBars();
@@ -19893,35 +19973,54 @@ class Gp3To5Importer extends ScoreImporter {
19893
19973
  Logger.debug(this.name, `Guitar Pro version ${version} detected`);
19894
19974
  }
19895
19975
  readScoreInformation() {
19896
- this._score.title = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19897
- this._score.subTitle = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19898
- this._score.artist = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19899
- this._score.album = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19900
- this._score.words = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19976
+ this._score.title = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19977
+ this._score.subTitle = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19978
+ this._score.artist = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19979
+ this._score.album = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19980
+ this._score.words = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19901
19981
  this._score.music =
19902
19982
  this._versionNumber >= 500
19903
- ? GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding)
19983
+ ? GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize)
19904
19984
  : this._score.words;
19905
- this._score.copyright = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19906
- this._score.tab = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19907
- this._score.instructions = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19985
+ this._score.copyright = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19986
+ this._score.tab = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19987
+ this._score.instructions = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19908
19988
  const noticeLines = IOHelper.readInt32LE(this.data);
19989
+ this._ensureLoopBoundary(noticeLines, Gp3To5Importer._maxNoticeLines, 'notice line count');
19909
19990
  let notice = '';
19910
19991
  for (let i = 0; i < noticeLines; i++) {
19911
19992
  if (i > 0) {
19912
19993
  notice += '\r\n';
19913
19994
  }
19914
- notice += GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding)?.toString();
19995
+ notice += GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize)?.toString();
19915
19996
  }
19916
19997
  this._score.notices = notice;
19917
19998
  }
19999
+ // very generous thresholds for values which control loop boundaries
20000
+ // prevents DoS or resource exhaustion for corrupt files or files with malicious intent
20001
+ // not configurable, as realistically GP3-5 files will not exceed these values,
20002
+ // I don't hink anyone is that verbose in the small GP5 box where you can add notices
20003
+ static _maxNoticeLines = 1000;
20004
+ // I haven't encountered such a long song in the wild. beyond 1000 bars something is clearly off
20005
+ static _maxBarCount = 1000;
20006
+ // I think GP5 itself limits already to ~10. 100 tracks is just unrealistic, proof me wrong
20007
+ static _maxTrackCount = 100;
20008
+ // nobody reallistically writes that many beats in one bar either.
20009
+ static _maxBeatCount = 100;
20010
+ // I think GP5 already limits this to way less, very generous to allow 4 times more than likely the UI supports
20011
+ static _maxBendPointCount = BendPoint.MaxPosition * 4;
20012
+ _ensureLoopBoundary(value, maximumValue, label) {
20013
+ if (value > maximumValue) {
20014
+ throw new OverflowError(`'${label}' with value ${value} has exceeded the internal safety threshold of ${maximumValue}`);
20015
+ }
20016
+ }
19918
20017
  readLyrics() {
19919
20018
  this._lyrics = [];
19920
20019
  this._lyricsTrack = IOHelper.readInt32LE(this.data) - 1;
19921
20020
  for (let i = 0; i < 5; i++) {
19922
20021
  const lyrics = new Lyrics();
19923
20022
  lyrics.startBar = IOHelper.readInt32LE(this.data) - 1;
19924
- lyrics.text = GpBinaryHelpers.gpReadStringInt(this.data, this.settings.importer.encoding);
20023
+ lyrics.text = GpBinaryHelpers.gpReadStringInt(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19925
20024
  this._lyrics.push(lyrics);
19926
20025
  }
19927
20026
  }
@@ -19938,41 +20037,41 @@ class Gp3To5Importer extends ScoreImporter {
19938
20037
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Title).isVisible =
19939
20038
  (flags & (0x01 << 0)) !== 0;
19940
20039
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Title).template =
19941
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20040
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19942
20041
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.SubTitle).isVisible =
19943
20042
  (flags & (0x01 << 1)) !== 0;
19944
20043
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.SubTitle).template =
19945
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20044
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19946
20045
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Artist).isVisible =
19947
20046
  (flags & (0x01 << 2)) !== 0;
19948
20047
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Artist).template =
19949
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20048
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19950
20049
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Album).isVisible =
19951
20050
  (flags & (0x01 << 3)) !== 0;
19952
20051
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Album).template =
19953
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20052
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19954
20053
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Words).isVisible =
19955
20054
  (flags & (0x01 << 4)) !== 0;
19956
20055
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Words).template =
19957
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20056
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19958
20057
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Music).isVisible =
19959
20058
  (flags & (0x01 << 5)) !== 0;
19960
20059
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Music).template =
19961
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20060
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19962
20061
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.WordsAndMusic).isVisible =
19963
20062
  (flags & (0x01 << 6)) !== 0;
19964
20063
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.WordsAndMusic).template =
19965
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20064
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19966
20065
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Copyright).isVisible =
19967
20066
  (flags & (0x01 << 7)) !== 0;
19968
20067
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Copyright).template =
19969
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20068
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19970
20069
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.CopyrightSecondLine).isVisible =
19971
20070
  (flags & (0x01 << 7)) !== 0;
19972
20071
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.CopyrightSecondLine).template =
19973
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20072
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19974
20073
  // page number format
19975
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20074
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19976
20075
  }
19977
20076
  readPlaybackInfos() {
19978
20077
  this._playbackInfos = [];
@@ -20053,7 +20152,7 @@ class Gp3To5Importer extends ScoreImporter {
20053
20152
  // marker
20054
20153
  if ((flags & 0x20) !== 0) {
20055
20154
  const section = new Section();
20056
- section.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20155
+ section.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20057
20156
  section.marker = '';
20058
20157
  GpBinaryHelpers.gpReadColor(this.data, false);
20059
20158
  newMasterBar.section = section;
@@ -20220,9 +20319,9 @@ class Gp3To5Importer extends ScoreImporter {
20220
20319
  // 1 byte PRE
20221
20320
  this.data.skip(4);
20222
20321
  // RSE: effect name
20223
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20322
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20224
20323
  // RSE: effect category
20225
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20324
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20226
20325
  }
20227
20326
  }
20228
20327
  else {
@@ -20279,6 +20378,7 @@ class Gp3To5Importer extends ScoreImporter {
20279
20378
  }
20280
20379
  const newVoice = new Voice$1();
20281
20380
  bar.addVoice(newVoice);
20381
+ this._ensureLoopBoundary(beatCount, Gp3To5Importer._maxBeatCount, 'beat count');
20282
20382
  for (let i = 0; i < beatCount; i++) {
20283
20383
  this.readBeat(track, bar, newVoice);
20284
20384
  }
@@ -20357,7 +20457,7 @@ class Gp3To5Importer extends ScoreImporter {
20357
20457
  }
20358
20458
  const beatTextAsLyrics = this.settings.importer.beatTextAsLyrics && track.index !== this._lyricsTrack; // detect if not lyrics track
20359
20459
  if ((flags & 0x04) !== 0) {
20360
- const text = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
20460
+ const text = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20361
20461
  if (beatTextAsLyrics) {
20362
20462
  const lyrics = new Lyrics();
20363
20463
  lyrics.text = text.trim();
@@ -20532,7 +20632,7 @@ class Gp3To5Importer extends ScoreImporter {
20532
20632
  }
20533
20633
  else {
20534
20634
  const strings = this._versionNumber >= 406 ? 7 : 6;
20535
- chord.name = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20635
+ chord.name = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20536
20636
  chord.firstFret = IOHelper.readInt32LE(this.data);
20537
20637
  if (chord.firstFret > 0) {
20538
20638
  for (let i = 0; i < strings; i++) {
@@ -20643,6 +20743,7 @@ class Gp3To5Importer extends ScoreImporter {
20643
20743
  this.data.readByte(); // type
20644
20744
  IOHelper.readInt32LE(this.data); // value
20645
20745
  const pointCount = IOHelper.readInt32LE(this.data);
20746
+ this._ensureLoopBoundary(pointCount, Gp3To5Importer._maxBendPointCount, 'tremolo bar point count');
20646
20747
  if (pointCount > 0) {
20647
20748
  for (let i = 0; i < pointCount; i++) {
20648
20749
  const point = new BendPoint(0, 0);
@@ -20702,7 +20803,7 @@ class Gp3To5Importer extends ScoreImporter {
20702
20803
  const phaser = IOHelper.readSInt8(this.data);
20703
20804
  const tremolo = IOHelper.readSInt8(this.data);
20704
20805
  if (this._versionNumber >= 500) {
20705
- tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20806
+ tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20706
20807
  }
20707
20808
  tableChange.tempo = IOHelper.readInt32LE(this.data);
20708
20809
  // durations (in number of beats)
@@ -20746,8 +20847,8 @@ class Gp3To5Importer extends ScoreImporter {
20746
20847
  }
20747
20848
  // unknown
20748
20849
  if (this._versionNumber >= 510) {
20749
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20750
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20850
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20851
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20751
20852
  }
20752
20853
  if (tableChange.volume >= 0) {
20753
20854
  const volumeAutomation = new Automation();
@@ -20911,6 +21012,7 @@ class Gp3To5Importer extends ScoreImporter {
20911
21012
  this.data.readByte(); // type
20912
21013
  IOHelper.readInt32LE(this.data); // value
20913
21014
  const pointCount = IOHelper.readInt32LE(this.data);
21015
+ this._ensureLoopBoundary(pointCount, Gp3To5Importer._maxBendPointCount, 'note bend point count');
20914
21016
  if (pointCount > 0) {
20915
21017
  for (let i = 0; i < pointCount; i++) {
20916
21018
  const point = new BendPoint(0, 0);
@@ -21099,25 +21201,28 @@ class GpBinaryHelpers {
21099
21201
  * Skips an integer (4byte) and reads a string using
21100
21202
  * a bytesize
21101
21203
  */
21102
- static gpReadStringIntUnused(data, encoding) {
21204
+ static gpReadStringIntUnused(data, encoding, maxDecodingBufferSize) {
21103
21205
  data.skip(4);
21104
- return GpBinaryHelpers.gpReadString(data, data.readByte(), encoding);
21206
+ return GpBinaryHelpers.gpReadString(data, data.readByte(), encoding, maxDecodingBufferSize);
21105
21207
  }
21106
21208
  /**
21107
21209
  * Reads an integer as size, and then the string itself
21108
21210
  */
21109
- static gpReadStringInt(data, encoding) {
21110
- return GpBinaryHelpers.gpReadString(data, IOHelper.readInt32LE(data), encoding);
21211
+ static gpReadStringInt(data, encoding, maxDecodingBufferSize) {
21212
+ return GpBinaryHelpers.gpReadString(data, IOHelper.readInt32LE(data), encoding, maxDecodingBufferSize);
21111
21213
  }
21112
21214
  /**
21113
21215
  * Reads an integer as size, skips a byte and reads the string itself
21114
21216
  */
21115
- static gpReadStringIntByte(data, encoding) {
21217
+ static gpReadStringIntByte(data, encoding, maxDecodingBufferSize) {
21116
21218
  const length = IOHelper.readInt32LE(data) - 1;
21117
21219
  data.readByte();
21118
- return GpBinaryHelpers.gpReadString(data, length, encoding);
21220
+ return GpBinaryHelpers.gpReadString(data, length, encoding, maxDecodingBufferSize);
21119
21221
  }
21120
- static gpReadString(data, length, encoding) {
21222
+ static gpReadString(data, length, encoding, maxDecodingBufferSize) {
21223
+ if (length > maxDecodingBufferSize) {
21224
+ throw new OverflowError(`Detected string exceeding maxDecodingBufferSize at offset ${data.position}`);
21225
+ }
21121
21226
  const b = new Uint8Array(length);
21122
21227
  data.read(b, 0, b.length);
21123
21228
  return IOHelper.toString(b, encoding);
@@ -21251,17 +21356,17 @@ var DataType;
21251
21356
  class BinaryStylesheet {
21252
21357
  _types = new Map();
21253
21358
  raw = new Map();
21254
- constructor(data) {
21359
+ constructor(data, maxDecodingBufferSize = 0) {
21255
21360
  if (data) {
21256
- this._read(data);
21361
+ this._read(data, maxDecodingBufferSize);
21257
21362
  }
21258
21363
  }
21259
- _read(data) {
21364
+ _read(data, maxDecodingBufferSize) {
21260
21365
  // BinaryStylesheet apears to be big-endien
21261
21366
  const readable = ByteBuffer.fromBuffer(data);
21262
21367
  const entryCount = IOHelper.readInt32BE(readable);
21263
21368
  for (let i = 0; i < entryCount; i++) {
21264
- const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8');
21369
+ const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8', maxDecodingBufferSize);
21265
21370
  const type = readable.readByte();
21266
21371
  this._types.set(key, type);
21267
21372
  switch (type) {
@@ -21278,7 +21383,7 @@ class BinaryStylesheet {
21278
21383
  this.addValue(key, fvalue);
21279
21384
  break;
21280
21385
  case DataType.String:
21281
- const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8');
21386
+ const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8', maxDecodingBufferSize);
21282
21387
  this.addValue(key, s);
21283
21388
  break;
21284
21389
  case DataType.Point:
@@ -24655,7 +24760,7 @@ class Gp7To8Importer extends ScoreImporter {
24655
24760
  // at first we need to load the binary file system
24656
24761
  // from the GPX container
24657
24762
  Logger.debug(this.name, 'Loading ZIP entries');
24658
- const fileSystem = new ZipReader(this.data);
24763
+ const fileSystem = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
24659
24764
  let entries;
24660
24765
  try {
24661
24766
  entries = fileSystem.read();
@@ -24704,7 +24809,7 @@ class Gp7To8Importer extends ScoreImporter {
24704
24809
  const score = gpifParser.score;
24705
24810
  if (binaryStylesheetData) {
24706
24811
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
24707
- const stylesheet = new BinaryStylesheet(binaryStylesheetData);
24812
+ const stylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
24708
24813
  stylesheet.apply(score);
24709
24814
  Logger.debug(this.name, 'BinaryStylesheet parsed');
24710
24815
  }
@@ -24725,15 +24830,6 @@ class Gp7To8Importer extends ScoreImporter {
24725
24830
  }
24726
24831
  }
24727
24832
 
24728
- /**
24729
- * @internal
24730
- */
24731
- class EndOfReaderError extends AlphaTabError {
24732
- constructor() {
24733
- super(AlphaTabErrorType.Format, 'Unexpected end of data within reader');
24734
- }
24735
- }
24736
-
24737
24833
  /**
24738
24834
  * This utility public class allows bitwise reading of a stream
24739
24835
  * @internal
@@ -25079,7 +25175,7 @@ class GpxImporter extends ScoreImporter {
25079
25175
  const score = gpifParser.score;
25080
25176
  if (binaryStylesheetData) {
25081
25177
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
25082
- const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData);
25178
+ const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
25083
25179
  binaryStylesheet.apply(score);
25084
25180
  Logger.debug(this.name, 'BinaryStylesheet parsed');
25085
25181
  }
@@ -25456,7 +25552,7 @@ class MusicXmlImporter extends ScoreImporter {
25456
25552
  return this._score;
25457
25553
  }
25458
25554
  _extractMusicXml() {
25459
- const zip = new ZipReader(this.data);
25555
+ const zip = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
25460
25556
  let entries;
25461
25557
  try {
25462
25558
  entries = zip.read();
@@ -32821,6 +32917,17 @@ class ImporterSettings {
32821
32917
  * ![Disabled](https://alphatab.net/img/reference/property/beattextaslyrics-disabled.png)
32822
32918
  */
32823
32919
  beatTextAsLyrics = false;
32920
+ /**
32921
+ * This setting controls the escape hatch for handling potentially malicous or corrupt
32922
+ * input files. At selected spots in the codebase, we use this buffer size as maximum
32923
+ * allowed sizes. e.g. during unzipping or decoding strings.
32924
+ * This prevents resource exhaustion, especially when alphaTab is used on server side.
32925
+ * Increase this buffer size if you need to handle very big files.
32926
+ * @defaultValue `128000000`
32927
+ * @category Core
32928
+ * @since 1.9.0
32929
+ */
32930
+ maxDecodingBufferSize = 128000000;
32824
32931
  }
32825
32932
 
32826
32933
  /**
@@ -34146,6 +34253,7 @@ class ImporterSettingsSerializer {
34146
34253
  o.set("encoding", obj.encoding);
34147
34254
  o.set("mergepartgroupsinmusicxml", obj.mergePartGroupsInMusicXml);
34148
34255
  o.set("beattextaslyrics", obj.beatTextAsLyrics);
34256
+ o.set("maxdecodingbuffersize", obj.maxDecodingBufferSize);
34149
34257
  return o;
34150
34258
  }
34151
34259
  static setProperty(obj, property, v) {
@@ -34159,6 +34267,9 @@ class ImporterSettingsSerializer {
34159
34267
  case "beattextaslyrics":
34160
34268
  obj.beatTextAsLyrics = v;
34161
34269
  return true;
34270
+ case "maxdecodingbuffersize":
34271
+ obj.maxDecodingBufferSize = v;
34272
+ return true;
34162
34273
  }
34163
34274
  return false;
34164
34275
  }
@@ -34682,12 +34793,12 @@ class ScoreLoader {
34682
34793
  const importers = Environment.buildImporters();
34683
34794
  Logger.debug('ScoreLoader', `Loading score from ${data.length} bytes using ${importers.length} importers`);
34684
34795
  let score = null;
34685
- const bb = ByteBuffer.fromBuffer(data);
34796
+ const readable = new ThrowingReadable(ByteBuffer.fromBuffer(data));
34686
34797
  for (const importer of importers) {
34687
- bb.reset();
34798
+ readable.reset();
34688
34799
  try {
34689
34800
  Logger.debug('ScoreLoader', `Importing using importer ${importer.name}`);
34690
- importer.init(bb, settings);
34801
+ importer.init(readable, settings);
34691
34802
  score = importer.readScore();
34692
34803
  Logger.debug('ScoreLoader', `Score imported using ${importer.name}`);
34693
34804
  break;
@@ -7272,6 +7272,14 @@ declare class ElementStyle<TSubElements extends number> {
7272
7272
  colors: Map<TSubElements, Color | null>;
7273
7273
  }
7274
7274
 
7275
+ /**
7276
+ * Thrown whenever we hit the end of input data unexpectedly.
7277
+ * @public
7278
+ */
7279
+ declare class EndOfReaderError extends AlphaTabError {
7280
+ constructor();
7281
+ }
7282
+
7275
7283
  /**
7276
7284
  * Represents the end of the track indicating that no more events for this track follow.
7277
7285
  * @public
@@ -9645,6 +9653,17 @@ export declare class ImporterSettings {
9645
9653
  * ![Disabled](https://alphatab.net/img/reference/property/beattextaslyrics-disabled.png)
9646
9654
  */
9647
9655
  beatTextAsLyrics: boolean;
9656
+ /**
9657
+ * This setting controls the escape hatch for handling potentially malicous or corrupt
9658
+ * input files. At selected spots in the codebase, we use this buffer size as maximum
9659
+ * allowed sizes. e.g. during unzipping or decoding strings.
9660
+ * This prevents resource exhaustion, especially when alphaTab is used on server side.
9661
+ * Increase this buffer size if you need to handle very big files.
9662
+ * @defaultValue `128000000`
9663
+ * @category Core
9664
+ * @since 1.9.0
9665
+ */
9666
+ maxDecodingBufferSize: number;
9648
9667
  }
9649
9668
 
9650
9669
  /**
@@ -9711,6 +9730,17 @@ declare interface ImporterSettingsJson {
9711
9730
  * ![Disabled](https://alphatab.net/img/reference/property/beattextaslyrics-disabled.png)
9712
9731
  */
9713
9732
  beatTextAsLyrics?: boolean;
9733
+ /**
9734
+ * This setting controls the escape hatch for handling potentially malicous or corrupt
9735
+ * input files. At selected spots in the codebase, we use this buffer size as maximum
9736
+ * allowed sizes. e.g. during unzipping or decoding strings.
9737
+ * This prevents resource exhaustion, especially when alphaTab is used on server side.
9738
+ * Increase this buffer size if you need to handle very big files.
9739
+ * @defaultValue `128000000`
9740
+ * @category Core
9741
+ * @since 1.9.0
9742
+ */
9743
+ maxDecodingBufferSize?: number;
9714
9744
  }
9715
9745
 
9716
9746
  /**
@@ -9775,6 +9805,8 @@ export declare namespace io {
9775
9805
  export {
9776
9806
  IWriteable,
9777
9807
  IReadable,
9808
+ OverflowError,
9809
+ EndOfReaderError,
9778
9810
  ByteBuffer,
9779
9811
  IOHelper
9780
9812
  }
@@ -13346,6 +13378,14 @@ declare enum Ottavia {
13346
13378
  _15mb = 4
13347
13379
  }
13348
13380
 
13381
+ /**
13382
+ * Thrown whenever an overflow in data or buffer sizes is detected.
13383
+ * @public
13384
+ */
13385
+ declare class OverflowError extends AlphaTabError {
13386
+ constructor(message: string);
13387
+ }
13388
+
13349
13389
  /**
13350
13390
  * Lists all types of pick strokes.
13351
13391
  * @public