@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.
package/dist/alphaTab.js CHANGED
@@ -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
  *
@@ -209,9 +209,9 @@
209
209
  * @internal
210
210
  */
211
211
  class VersionInfo {
212
- static version = '1.9.0-alpha.1804';
213
- static date = '2026-05-16T03:54:50.685Z';
214
- static commit = '6e757c0cbec66598a7fa5327abc9d05616984486';
212
+ static version = '1.9.0-alpha.1806';
213
+ static date = '2026-05-18T04:36:35.809Z';
214
+ static commit = 'e926f2b6bcfbaa219a04140bf34fdd9431a6ca17';
215
215
  static print(print) {
216
216
  print(`alphaTab ${VersionInfo.version}`);
217
217
  print(`commit: ${VersionInfo.commit}`);
@@ -16007,6 +16007,68 @@
16007
16007
  }
16008
16008
  }
16009
16009
 
16010
+ /**
16011
+ * Thrown whenever we hit the end of input data unexpectedly.
16012
+ * @public
16013
+ */
16014
+ class EndOfReaderError extends AlphaTabError {
16015
+ constructor() {
16016
+ super(exports.AlphaTabErrorType.Format, 'Unexpected end of data within reader');
16017
+ }
16018
+ }
16019
+ /**
16020
+ * Thrown whenever an overflow in data or buffer sizes is detected.
16021
+ * @public
16022
+ */
16023
+ class OverflowError extends AlphaTabError {
16024
+ constructor(message) {
16025
+ super(exports.AlphaTabErrorType.Format, message);
16026
+ }
16027
+ }
16028
+ /**
16029
+ * An {@see IReadable} implementation throwing when the end of stream is reached guarding against
16030
+ * corrupted or maliciously crafted files leading to endless reading
16031
+ * @internal
16032
+ */
16033
+ class ThrowingReadable {
16034
+ _readable;
16035
+ constructor(readable) {
16036
+ this._readable = readable;
16037
+ }
16038
+ get position() {
16039
+ return this._readable.position;
16040
+ }
16041
+ set position(value) {
16042
+ this._readable.position = value;
16043
+ }
16044
+ get length() {
16045
+ return this._readable.length;
16046
+ }
16047
+ reset() {
16048
+ this._readable.reset();
16049
+ }
16050
+ skip(offset) {
16051
+ this._readable.skip(offset);
16052
+ }
16053
+ _requireBytes(bytes) {
16054
+ const remaining = this.length - this.position;
16055
+ if (remaining < bytes) {
16056
+ throw new EndOfReaderError();
16057
+ }
16058
+ }
16059
+ readByte() {
16060
+ this._requireBytes(1);
16061
+ return this._readable.readByte();
16062
+ }
16063
+ read(buffer, offset, count) {
16064
+ this._requireBytes(count);
16065
+ return this._readable.read(buffer, offset, count);
16066
+ }
16067
+ readAll() {
16068
+ return this._readable.readAll();
16069
+ }
16070
+ }
16071
+
16010
16072
  /**
16011
16073
  * This is the base public class for creating new song importers which
16012
16074
  * enable reading scores from any binary datasource
@@ -16019,7 +16081,12 @@
16019
16081
  * Initializes the importer with the given data and settings.
16020
16082
  */
16021
16083
  init(data, settings) {
16022
- this.data = data;
16084
+ if (data instanceof ThrowingReadable) {
16085
+ this.data = data;
16086
+ }
16087
+ else {
16088
+ this.data = new ThrowingReadable(data);
16089
+ }
16023
16090
  this.settings = settings;
16024
16091
  // when beginning reading a new score we reset the IDs.
16025
16092
  Score.resetIds();
@@ -17688,8 +17755,10 @@
17688
17755
  */
17689
17756
  class ZipReader {
17690
17757
  _readable;
17691
- constructor(readable) {
17758
+ _maxDecodingBufferSize;
17759
+ constructor(readable, maxDecodingBufferSize) {
17692
17760
  this._readable = readable;
17761
+ this._maxDecodingBufferSize = maxDecodingBufferSize;
17693
17762
  }
17694
17763
  read() {
17695
17764
  const entries = [];
@@ -17721,7 +17790,13 @@
17721
17790
  IOHelper.readInt32LE(readable); // crc-32
17722
17791
  IOHelper.readInt32LE(readable); // compressed size
17723
17792
  const uncompressedSize = IOHelper.readInt32LE(readable);
17793
+ if (uncompressedSize > this._maxDecodingBufferSize) {
17794
+ throw new OverflowError(`Zip contains files exceeding the configured maxDecodingBufferSize`);
17795
+ }
17724
17796
  const fileNameLength = IOHelper.readInt16LE(readable);
17797
+ if (fileNameLength > this._maxDecodingBufferSize) {
17798
+ throw new OverflowError(`Zip contains file names exceeding the configured maxDecodingBufferSize`);
17799
+ }
17725
17800
  const extraFieldLength = IOHelper.readInt16LE(readable);
17726
17801
  const fname = IOHelper.toString(IOHelper.readByteArray(readable, fileNameLength), 'utf-8');
17727
17802
  readable.skip(extraFieldLength);
@@ -17734,6 +17809,9 @@
17734
17809
  while (true) {
17735
17810
  const bytes = z.readBytes(buffer, 0, buffer.length);
17736
17811
  target.write(buffer, 0, bytes);
17812
+ if (target.length > this._maxDecodingBufferSize) {
17813
+ throw new OverflowError(`Zip entry "${fname}" contains data exceeding the configured maxDecodingBufferSize`);
17814
+ }
17737
17815
  if (bytes < buffer.length) {
17738
17816
  break;
17739
17817
  }
@@ -19682,7 +19760,7 @@
19682
19760
  }
19683
19761
  readScore() {
19684
19762
  Logger.debug(this.name, 'Loading ZIP entries');
19685
- const fileSystem = new ZipReader(this.data);
19763
+ const fileSystem = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
19686
19764
  let entries;
19687
19765
  let xml = null;
19688
19766
  entries = fileSystem.read();
@@ -19812,7 +19890,7 @@
19812
19890
  this._initialTempo = Automation.buildTempoAutomation(false, 0, 0, 0);
19813
19891
  if (this._versionNumber >= 500) {
19814
19892
  this.readPageSetup();
19815
- this._initialTempo.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
19893
+ this._initialTempo.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19816
19894
  }
19817
19895
  // tempo stuff
19818
19896
  this._initialTempo.value = IOHelper.readInt32LE(this.data);
@@ -19851,7 +19929,9 @@
19851
19929
  }
19852
19930
  // contents
19853
19931
  this._barCount = IOHelper.readInt32LE(this.data);
19932
+ this._ensureLoopBoundary(this._barCount, Gp3To5Importer._maxBarCount, 'bar count');
19854
19933
  this._trackCount = IOHelper.readInt32LE(this.data);
19934
+ this._ensureLoopBoundary(this._trackCount, Gp3To5Importer._maxTrackCount, 'track count');
19855
19935
  this.readMasterBars();
19856
19936
  this.readTracks();
19857
19937
  this.readBars();
@@ -19899,35 +19979,54 @@
19899
19979
  Logger.debug(this.name, `Guitar Pro version ${version} detected`);
19900
19980
  }
19901
19981
  readScoreInformation() {
19902
- this._score.title = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19903
- this._score.subTitle = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19904
- this._score.artist = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19905
- this._score.album = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19906
- this._score.words = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19982
+ this._score.title = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19983
+ this._score.subTitle = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19984
+ this._score.artist = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19985
+ this._score.album = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19986
+ this._score.words = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19907
19987
  this._score.music =
19908
19988
  this._versionNumber >= 500
19909
- ? GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding)
19989
+ ? GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize)
19910
19990
  : this._score.words;
19911
- this._score.copyright = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19912
- this._score.tab = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19913
- this._score.instructions = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
19991
+ this._score.copyright = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19992
+ this._score.tab = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19993
+ this._score.instructions = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19914
19994
  const noticeLines = IOHelper.readInt32LE(this.data);
19995
+ this._ensureLoopBoundary(noticeLines, Gp3To5Importer._maxNoticeLines, 'notice line count');
19915
19996
  let notice = '';
19916
19997
  for (let i = 0; i < noticeLines; i++) {
19917
19998
  if (i > 0) {
19918
19999
  notice += '\r\n';
19919
20000
  }
19920
- notice += GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding)?.toString();
20001
+ notice += GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize)?.toString();
19921
20002
  }
19922
20003
  this._score.notices = notice;
19923
20004
  }
20005
+ // very generous thresholds for values which control loop boundaries
20006
+ // prevents DoS or resource exhaustion for corrupt files or files with malicious intent
20007
+ // not configurable, as realistically GP3-5 files will not exceed these values,
20008
+ // I don't hink anyone is that verbose in the small GP5 box where you can add notices
20009
+ static _maxNoticeLines = 1000;
20010
+ // I haven't encountered such a long song in the wild. beyond 1000 bars something is clearly off
20011
+ static _maxBarCount = 1000;
20012
+ // I think GP5 itself limits already to ~10. 100 tracks is just unrealistic, proof me wrong
20013
+ static _maxTrackCount = 100;
20014
+ // nobody reallistically writes that many beats in one bar either.
20015
+ static _maxBeatCount = 100;
20016
+ // I think GP5 already limits this to way less, very generous to allow 4 times more than likely the UI supports
20017
+ static _maxBendPointCount = BendPoint.MaxPosition * 4;
20018
+ _ensureLoopBoundary(value, maximumValue, label) {
20019
+ if (value > maximumValue) {
20020
+ throw new OverflowError(`'${label}' with value ${value} has exceeded the internal safety threshold of ${maximumValue}`);
20021
+ }
20022
+ }
19924
20023
  readLyrics() {
19925
20024
  this._lyrics = [];
19926
20025
  this._lyricsTrack = IOHelper.readInt32LE(this.data) - 1;
19927
20026
  for (let i = 0; i < 5; i++) {
19928
20027
  const lyrics = new Lyrics();
19929
20028
  lyrics.startBar = IOHelper.readInt32LE(this.data) - 1;
19930
- lyrics.text = GpBinaryHelpers.gpReadStringInt(this.data, this.settings.importer.encoding);
20029
+ lyrics.text = GpBinaryHelpers.gpReadStringInt(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19931
20030
  this._lyrics.push(lyrics);
19932
20031
  }
19933
20032
  }
@@ -19944,41 +20043,41 @@
19944
20043
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Title).isVisible =
19945
20044
  (flags & (0x01 << 0)) !== 0;
19946
20045
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Title).template =
19947
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20046
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19948
20047
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.SubTitle).isVisible =
19949
20048
  (flags & (0x01 << 1)) !== 0;
19950
20049
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.SubTitle).template =
19951
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20050
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19952
20051
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Artist).isVisible =
19953
20052
  (flags & (0x01 << 2)) !== 0;
19954
20053
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Artist).template =
19955
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20054
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19956
20055
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Album).isVisible =
19957
20056
  (flags & (0x01 << 3)) !== 0;
19958
20057
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Album).template =
19959
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20058
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19960
20059
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Words).isVisible =
19961
20060
  (flags & (0x01 << 4)) !== 0;
19962
20061
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Words).template =
19963
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20062
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19964
20063
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Music).isVisible =
19965
20064
  (flags & (0x01 << 5)) !== 0;
19966
20065
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Music).template =
19967
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20066
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19968
20067
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.WordsAndMusic).isVisible =
19969
20068
  (flags & (0x01 << 6)) !== 0;
19970
20069
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.WordsAndMusic).template =
19971
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20070
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19972
20071
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Copyright).isVisible =
19973
20072
  (flags & (0x01 << 7)) !== 0;
19974
20073
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.Copyright).template =
19975
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20074
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19976
20075
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.CopyrightSecondLine).isVisible =
19977
20076
  (flags & (0x01 << 7)) !== 0;
19978
20077
  ModelUtils.getOrCreateHeaderFooterStyle(this._score, ScoreSubElement.CopyrightSecondLine).template =
19979
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20078
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19980
20079
  // page number format
19981
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20080
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
19982
20081
  }
19983
20082
  readPlaybackInfos() {
19984
20083
  this._playbackInfos = [];
@@ -20059,7 +20158,7 @@
20059
20158
  // marker
20060
20159
  if ((flags & 0x20) !== 0) {
20061
20160
  const section = new Section();
20062
- section.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20161
+ section.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20063
20162
  section.marker = '';
20064
20163
  GpBinaryHelpers.gpReadColor(this.data, false);
20065
20164
  newMasterBar.section = section;
@@ -20226,9 +20325,9 @@
20226
20325
  // 1 byte PRE
20227
20326
  this.data.skip(4);
20228
20327
  // RSE: effect name
20229
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20328
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20230
20329
  // RSE: effect category
20231
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20330
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20232
20331
  }
20233
20332
  }
20234
20333
  else {
@@ -20285,6 +20384,7 @@
20285
20384
  }
20286
20385
  const newVoice = new Voice$1();
20287
20386
  bar.addVoice(newVoice);
20387
+ this._ensureLoopBoundary(beatCount, Gp3To5Importer._maxBeatCount, 'beat count');
20288
20388
  for (let i = 0; i < beatCount; i++) {
20289
20389
  this.readBeat(track, bar, newVoice);
20290
20390
  }
@@ -20363,7 +20463,7 @@
20363
20463
  }
20364
20464
  const beatTextAsLyrics = this.settings.importer.beatTextAsLyrics && track.index !== this._lyricsTrack; // detect if not lyrics track
20365
20465
  if ((flags & 0x04) !== 0) {
20366
- const text = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding);
20466
+ const text = GpBinaryHelpers.gpReadStringIntUnused(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20367
20467
  if (beatTextAsLyrics) {
20368
20468
  const lyrics = new Lyrics();
20369
20469
  lyrics.text = text.trim();
@@ -20538,7 +20638,7 @@
20538
20638
  }
20539
20639
  else {
20540
20640
  const strings = this._versionNumber >= 406 ? 7 : 6;
20541
- chord.name = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20641
+ chord.name = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20542
20642
  chord.firstFret = IOHelper.readInt32LE(this.data);
20543
20643
  if (chord.firstFret > 0) {
20544
20644
  for (let i = 0; i < strings; i++) {
@@ -20649,6 +20749,7 @@
20649
20749
  this.data.readByte(); // type
20650
20750
  IOHelper.readInt32LE(this.data); // value
20651
20751
  const pointCount = IOHelper.readInt32LE(this.data);
20752
+ this._ensureLoopBoundary(pointCount, Gp3To5Importer._maxBendPointCount, 'tremolo bar point count');
20652
20753
  if (pointCount > 0) {
20653
20754
  for (let i = 0; i < pointCount; i++) {
20654
20755
  const point = new BendPoint(0, 0);
@@ -20708,7 +20809,7 @@
20708
20809
  const phaser = IOHelper.readSInt8(this.data);
20709
20810
  const tremolo = IOHelper.readSInt8(this.data);
20710
20811
  if (this._versionNumber >= 500) {
20711
- tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20812
+ tableChange.tempoName = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20712
20813
  }
20713
20814
  tableChange.tempo = IOHelper.readInt32LE(this.data);
20714
20815
  // durations (in number of beats)
@@ -20752,8 +20853,8 @@
20752
20853
  }
20753
20854
  // unknown
20754
20855
  if (this._versionNumber >= 510) {
20755
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20756
- GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
20856
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20857
+ GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding, this.settings.importer.maxDecodingBufferSize);
20757
20858
  }
20758
20859
  if (tableChange.volume >= 0) {
20759
20860
  const volumeAutomation = new Automation();
@@ -20917,6 +21018,7 @@
20917
21018
  this.data.readByte(); // type
20918
21019
  IOHelper.readInt32LE(this.data); // value
20919
21020
  const pointCount = IOHelper.readInt32LE(this.data);
21021
+ this._ensureLoopBoundary(pointCount, Gp3To5Importer._maxBendPointCount, 'note bend point count');
20920
21022
  if (pointCount > 0) {
20921
21023
  for (let i = 0; i < pointCount; i++) {
20922
21024
  const point = new BendPoint(0, 0);
@@ -21105,25 +21207,28 @@
21105
21207
  * Skips an integer (4byte) and reads a string using
21106
21208
  * a bytesize
21107
21209
  */
21108
- static gpReadStringIntUnused(data, encoding) {
21210
+ static gpReadStringIntUnused(data, encoding, maxDecodingBufferSize) {
21109
21211
  data.skip(4);
21110
- return GpBinaryHelpers.gpReadString(data, data.readByte(), encoding);
21212
+ return GpBinaryHelpers.gpReadString(data, data.readByte(), encoding, maxDecodingBufferSize);
21111
21213
  }
21112
21214
  /**
21113
21215
  * Reads an integer as size, and then the string itself
21114
21216
  */
21115
- static gpReadStringInt(data, encoding) {
21116
- return GpBinaryHelpers.gpReadString(data, IOHelper.readInt32LE(data), encoding);
21217
+ static gpReadStringInt(data, encoding, maxDecodingBufferSize) {
21218
+ return GpBinaryHelpers.gpReadString(data, IOHelper.readInt32LE(data), encoding, maxDecodingBufferSize);
21117
21219
  }
21118
21220
  /**
21119
21221
  * Reads an integer as size, skips a byte and reads the string itself
21120
21222
  */
21121
- static gpReadStringIntByte(data, encoding) {
21223
+ static gpReadStringIntByte(data, encoding, maxDecodingBufferSize) {
21122
21224
  const length = IOHelper.readInt32LE(data) - 1;
21123
21225
  data.readByte();
21124
- return GpBinaryHelpers.gpReadString(data, length, encoding);
21226
+ return GpBinaryHelpers.gpReadString(data, length, encoding, maxDecodingBufferSize);
21125
21227
  }
21126
- static gpReadString(data, length, encoding) {
21228
+ static gpReadString(data, length, encoding, maxDecodingBufferSize) {
21229
+ if (length > maxDecodingBufferSize) {
21230
+ throw new OverflowError(`Detected string exceeding maxDecodingBufferSize at offset ${data.position}`);
21231
+ }
21127
21232
  const b = new Uint8Array(length);
21128
21233
  data.read(b, 0, b.length);
21129
21234
  return IOHelper.toString(b, encoding);
@@ -21257,17 +21362,17 @@
21257
21362
  class BinaryStylesheet {
21258
21363
  _types = new Map();
21259
21364
  raw = new Map();
21260
- constructor(data) {
21365
+ constructor(data, maxDecodingBufferSize = 0) {
21261
21366
  if (data) {
21262
- this._read(data);
21367
+ this._read(data, maxDecodingBufferSize);
21263
21368
  }
21264
21369
  }
21265
- _read(data) {
21370
+ _read(data, maxDecodingBufferSize) {
21266
21371
  // BinaryStylesheet apears to be big-endien
21267
21372
  const readable = ByteBuffer.fromBuffer(data);
21268
21373
  const entryCount = IOHelper.readInt32BE(readable);
21269
21374
  for (let i = 0; i < entryCount; i++) {
21270
- const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8');
21375
+ const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8', maxDecodingBufferSize);
21271
21376
  const type = readable.readByte();
21272
21377
  this._types.set(key, type);
21273
21378
  switch (type) {
@@ -21284,7 +21389,7 @@
21284
21389
  this.addValue(key, fvalue);
21285
21390
  break;
21286
21391
  case DataType.String:
21287
- const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8');
21392
+ const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8', maxDecodingBufferSize);
21288
21393
  this.addValue(key, s);
21289
21394
  break;
21290
21395
  case DataType.Point:
@@ -24661,7 +24766,7 @@
24661
24766
  // at first we need to load the binary file system
24662
24767
  // from the GPX container
24663
24768
  Logger.debug(this.name, 'Loading ZIP entries');
24664
- const fileSystem = new ZipReader(this.data);
24769
+ const fileSystem = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
24665
24770
  let entries;
24666
24771
  try {
24667
24772
  entries = fileSystem.read();
@@ -24710,7 +24815,7 @@
24710
24815
  const score = gpifParser.score;
24711
24816
  if (binaryStylesheetData) {
24712
24817
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
24713
- const stylesheet = new BinaryStylesheet(binaryStylesheetData);
24818
+ const stylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
24714
24819
  stylesheet.apply(score);
24715
24820
  Logger.debug(this.name, 'BinaryStylesheet parsed');
24716
24821
  }
@@ -24731,15 +24836,6 @@
24731
24836
  }
24732
24837
  }
24733
24838
 
24734
- /**
24735
- * @internal
24736
- */
24737
- class EndOfReaderError extends AlphaTabError {
24738
- constructor() {
24739
- super(exports.AlphaTabErrorType.Format, 'Unexpected end of data within reader');
24740
- }
24741
- }
24742
-
24743
24839
  /**
24744
24840
  * This utility public class allows bitwise reading of a stream
24745
24841
  * @internal
@@ -25085,7 +25181,7 @@
25085
25181
  const score = gpifParser.score;
25086
25182
  if (binaryStylesheetData) {
25087
25183
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
25088
- const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData);
25184
+ const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
25089
25185
  binaryStylesheet.apply(score);
25090
25186
  Logger.debug(this.name, 'BinaryStylesheet parsed');
25091
25187
  }
@@ -25462,7 +25558,7 @@
25462
25558
  return this._score;
25463
25559
  }
25464
25560
  _extractMusicXml() {
25465
- const zip = new ZipReader(this.data);
25561
+ const zip = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
25466
25562
  let entries;
25467
25563
  try {
25468
25564
  entries = zip.read();
@@ -32827,6 +32923,17 @@
32827
32923
  * ![Disabled](https://alphatab.net/img/reference/property/beattextaslyrics-disabled.png)
32828
32924
  */
32829
32925
  beatTextAsLyrics = false;
32926
+ /**
32927
+ * This setting controls the escape hatch for handling potentially malicous or corrupt
32928
+ * input files. At selected spots in the codebase, we use this buffer size as maximum
32929
+ * allowed sizes. e.g. during unzipping or decoding strings.
32930
+ * This prevents resource exhaustion, especially when alphaTab is used on server side.
32931
+ * Increase this buffer size if you need to handle very big files.
32932
+ * @defaultValue `128000000`
32933
+ * @category Core
32934
+ * @since 1.9.0
32935
+ */
32936
+ maxDecodingBufferSize = 128000000;
32830
32937
  }
32831
32938
 
32832
32939
  /**
@@ -34152,6 +34259,7 @@
34152
34259
  o.set("encoding", obj.encoding);
34153
34260
  o.set("mergepartgroupsinmusicxml", obj.mergePartGroupsInMusicXml);
34154
34261
  o.set("beattextaslyrics", obj.beatTextAsLyrics);
34262
+ o.set("maxdecodingbuffersize", obj.maxDecodingBufferSize);
34155
34263
  return o;
34156
34264
  }
34157
34265
  static setProperty(obj, property, v) {
@@ -34165,6 +34273,9 @@
34165
34273
  case "beattextaslyrics":
34166
34274
  obj.beatTextAsLyrics = v;
34167
34275
  return true;
34276
+ case "maxdecodingbuffersize":
34277
+ obj.maxDecodingBufferSize = v;
34278
+ return true;
34168
34279
  }
34169
34280
  return false;
34170
34281
  }
@@ -34688,12 +34799,12 @@
34688
34799
  const importers = Environment.buildImporters();
34689
34800
  Logger.debug('ScoreLoader', `Loading score from ${data.length} bytes using ${importers.length} importers`);
34690
34801
  let score = null;
34691
- const bb = ByteBuffer.fromBuffer(data);
34802
+ const readable = new ThrowingReadable(ByteBuffer.fromBuffer(data));
34692
34803
  for (const importer of importers) {
34693
- bb.reset();
34804
+ readable.reset();
34694
34805
  try {
34695
34806
  Logger.debug('ScoreLoader', `Importing using importer ${importer.name}`);
34696
- importer.init(bb, settings);
34807
+ importer.init(readable, settings);
34697
34808
  score = importer.readScore();
34698
34809
  Logger.debug('ScoreLoader', `Score imported using ${importer.name}`);
34699
34810
  break;