@coderline/alphatab 1.9.0-alpha.1803 → 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.1803 (develop, build 1803)
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.1803';
213
- static date = '2026-05-15T04:13:04.606Z';
214
- static commit = 'a87a8635a0a0306cdab1c7fe2e9e72576ed6f795';
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}`);
@@ -10321,7 +10321,7 @@
10321
10321
  encoding = 'utf-8';
10322
10322
  }
10323
10323
  const decoder = new TextDecoder(encoding);
10324
- return decoder.decode(data.buffer);
10324
+ return decoder.decode(data);
10325
10325
  }
10326
10326
  static _detectEncoding(data) {
10327
10327
  if (data.length > 2 && data[0] === 0xfe && data[1] === 0xff) {
@@ -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();
@@ -19751,7 +19829,7 @@
19751
19829
  static _versionString = 'FICHIER GUITAR PRO ';
19752
19830
  // NOTE: General Midi only defines percussion instruments from 35-81
19753
19831
  // Guitar Pro 5 allowed GS extensions (27-34 and 82-87)
19754
- // GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
19832
+ // GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
19755
19833
  // (even if they are not correct)
19756
19834
  // we can support this properly in future when we allow custom alphaTex articulation definitions
19757
19835
  // then we don't need to rely on GP specifics anymore but handle things on export/import
@@ -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);
@@ -21142,12 +21247,13 @@
21142
21247
  * @returns
21143
21248
  */
21144
21249
  static gpReadStringByteLength(data, length, encoding) {
21250
+ // Fixed-width string field: 1 length byte + `length` data bytes, decoded
21251
+ // up to min(stringLength, length). Always consumes 1 + length bytes.
21145
21252
  const stringLength = data.readByte();
21146
- const s = GpBinaryHelpers.gpReadString(data, stringLength, encoding);
21147
- if (stringLength < length) {
21148
- data.skip(length - stringLength);
21149
- }
21150
- return s;
21253
+ const fieldBytes = new Uint8Array(length);
21254
+ data.read(fieldBytes, 0, length);
21255
+ const effectiveLength = Math.min(stringLength, length);
21256
+ return IOHelper.toString(fieldBytes.subarray(0, effectiveLength), encoding);
21151
21257
  }
21152
21258
  }
21153
21259
  /**
@@ -21256,17 +21362,17 @@
21256
21362
  class BinaryStylesheet {
21257
21363
  _types = new Map();
21258
21364
  raw = new Map();
21259
- constructor(data) {
21365
+ constructor(data, maxDecodingBufferSize = 0) {
21260
21366
  if (data) {
21261
- this._read(data);
21367
+ this._read(data, maxDecodingBufferSize);
21262
21368
  }
21263
21369
  }
21264
- _read(data) {
21370
+ _read(data, maxDecodingBufferSize) {
21265
21371
  // BinaryStylesheet apears to be big-endien
21266
21372
  const readable = ByteBuffer.fromBuffer(data);
21267
21373
  const entryCount = IOHelper.readInt32BE(readable);
21268
21374
  for (let i = 0; i < entryCount; i++) {
21269
- const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8');
21375
+ const key = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8', maxDecodingBufferSize);
21270
21376
  const type = readable.readByte();
21271
21377
  this._types.set(key, type);
21272
21378
  switch (type) {
@@ -21283,7 +21389,7 @@
21283
21389
  this.addValue(key, fvalue);
21284
21390
  break;
21285
21391
  case DataType.String:
21286
- const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8');
21392
+ const s = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8', maxDecodingBufferSize);
21287
21393
  this.addValue(key, s);
21288
21394
  break;
21289
21395
  case DataType.Point:
@@ -24367,6 +24473,10 @@
24367
24473
  }
24368
24474
  // build masterbar automations
24369
24475
  for (const [barNumber, automations] of this._masterTrackAutomations) {
24476
+ if (barNumber < 0 || barNumber >= this.score.masterBars.length) {
24477
+ // automation references a bar that is not in the score's masterBars list
24478
+ continue;
24479
+ }
24370
24480
  const masterBar = this.score.masterBars[barNumber];
24371
24481
  for (let i = 0, j = automations.length; i < j; i++) {
24372
24482
  const automation = automations[i];
@@ -24656,7 +24766,7 @@
24656
24766
  // at first we need to load the binary file system
24657
24767
  // from the GPX container
24658
24768
  Logger.debug(this.name, 'Loading ZIP entries');
24659
- const fileSystem = new ZipReader(this.data);
24769
+ const fileSystem = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
24660
24770
  let entries;
24661
24771
  try {
24662
24772
  entries = fileSystem.read();
@@ -24705,7 +24815,7 @@
24705
24815
  const score = gpifParser.score;
24706
24816
  if (binaryStylesheetData) {
24707
24817
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
24708
- const stylesheet = new BinaryStylesheet(binaryStylesheetData);
24818
+ const stylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
24709
24819
  stylesheet.apply(score);
24710
24820
  Logger.debug(this.name, 'BinaryStylesheet parsed');
24711
24821
  }
@@ -24726,15 +24836,6 @@
24726
24836
  }
24727
24837
  }
24728
24838
 
24729
- /**
24730
- * @internal
24731
- */
24732
- class EndOfReaderError extends AlphaTabError {
24733
- constructor() {
24734
- super(exports.AlphaTabErrorType.Format, 'Unexpected end of data within reader');
24735
- }
24736
- }
24737
-
24738
24839
  /**
24739
24840
  * This utility public class allows bitwise reading of a stream
24740
24841
  * @internal
@@ -25080,7 +25181,7 @@
25080
25181
  const score = gpifParser.score;
25081
25182
  if (binaryStylesheetData) {
25082
25183
  Logger.debug(this.name, 'Start Parsing BinaryStylesheet');
25083
- const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData);
25184
+ const binaryStylesheet = new BinaryStylesheet(binaryStylesheetData, this.settings.importer.maxDecodingBufferSize);
25084
25185
  binaryStylesheet.apply(score);
25085
25186
  Logger.debug(this.name, 'BinaryStylesheet parsed');
25086
25187
  }
@@ -25457,7 +25558,7 @@
25457
25558
  return this._score;
25458
25559
  }
25459
25560
  _extractMusicXml() {
25460
- const zip = new ZipReader(this.data);
25561
+ const zip = new ZipReader(this.data, this.settings.importer.maxDecodingBufferSize);
25461
25562
  let entries;
25462
25563
  try {
25463
25564
  entries = zip.read();
@@ -32822,6 +32923,17 @@
32822
32923
  * ![Disabled](https://alphatab.net/img/reference/property/beattextaslyrics-disabled.png)
32823
32924
  */
32824
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;
32825
32937
  }
32826
32938
 
32827
32939
  /**
@@ -34147,6 +34259,7 @@
34147
34259
  o.set("encoding", obj.encoding);
34148
34260
  o.set("mergepartgroupsinmusicxml", obj.mergePartGroupsInMusicXml);
34149
34261
  o.set("beattextaslyrics", obj.beatTextAsLyrics);
34262
+ o.set("maxdecodingbuffersize", obj.maxDecodingBufferSize);
34150
34263
  return o;
34151
34264
  }
34152
34265
  static setProperty(obj, property, v) {
@@ -34160,6 +34273,9 @@
34160
34273
  case "beattextaslyrics":
34161
34274
  obj.beatTextAsLyrics = v;
34162
34275
  return true;
34276
+ case "maxdecodingbuffersize":
34277
+ obj.maxDecodingBufferSize = v;
34278
+ return true;
34163
34279
  }
34164
34280
  return false;
34165
34281
  }
@@ -34683,12 +34799,12 @@
34683
34799
  const importers = Environment.buildImporters();
34684
34800
  Logger.debug('ScoreLoader', `Loading score from ${data.length} bytes using ${importers.length} importers`);
34685
34801
  let score = null;
34686
- const bb = ByteBuffer.fromBuffer(data);
34802
+ const readable = new ThrowingReadable(ByteBuffer.fromBuffer(data));
34687
34803
  for (const importer of importers) {
34688
- bb.reset();
34804
+ readable.reset();
34689
34805
  try {
34690
34806
  Logger.debug('ScoreLoader', `Importing using importer ${importer.name}`);
34691
- importer.init(bb, settings);
34807
+ importer.init(readable, settings);
34692
34808
  score = importer.readScore();
34693
34809
  Logger.debug('ScoreLoader', `Score imported using ${importer.name}`);
34694
34810
  break;