Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions packages/alphatab/src/importer/Gp3To5Importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1571,12 +1571,15 @@ export class GpBinaryHelpers {
* @returns
*/
public static gpReadStringByteLength(data: IReadable, length: number, encoding: string): string {
// Fixed-width string field: 1 length byte + `length` data bytes, decoded
// up to min(stringLength, length). Always consumes 1 + length bytes.
const stringLength: number = data.readByte();
const s: string = GpBinaryHelpers.gpReadString(data, stringLength, encoding);
if (stringLength < length) {
data.skip(length - stringLength);
}
return s;
const fieldBytes: Uint8Array = new Uint8Array(length);
data.read(fieldBytes, 0, length);
const effectiveLength: number = Math.max(0, Math.min(stringLength, length));
const decoded: Uint8Array = new Uint8Array(effectiveLength);
decoded.set(fieldBytes.subarray(0, effectiveLength));
return IOHelper.toString(decoded, encoding);
}
}

Expand Down
Binary file not shown.
26 changes: 26 additions & 0 deletions packages/alphatab/test/importer/Gp5Importer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Settings } from '@coderline/alphatab/Settings';
import { GpBinaryHelpers } from '@coderline/alphatab/importer/Gp3To5Importer';
import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer';
import { type Beat, BeatBeamingMode } from '@coderline/alphatab/model/Beat';
import { Direction } from '@coderline/alphatab/model/Direction';
import { Ottavia } from '@coderline/alphatab/model/Ottavia';
Expand Down Expand Up @@ -569,4 +571,28 @@ describe('Gp5ImporterTest', () => {
}
}
});

it('chord-name-overflow', async () => {
// GP5 file with a chord name length byte that exceeds the 21-byte field
// (length=32). Pre-fix, gpReadStringByteLength consumed the full 32 bytes,
// mis-aligning the stream and triggering an unbounded readBend loop.
const score = (
await GpImporterTestHelper.prepareImporterWithFile('guitarpro5/chord-name-overflow.gp5')
).readScore();
expect(score.tracks.length).to.equal(1);
expect(score.masterBars.length).to.equal(193);
});

it('gpReadStringByteLength caps consumption at field width', () => {
const sentinelByte = 0xca;
const fieldSize = 21;
const overlongHint = 32;
const buffer = ByteBuffer.fromBuffer(
new Uint8Array([overlongHint, ...new Array(fieldSize).fill(0x41), sentinelByte])
);
const result = GpBinaryHelpers.gpReadStringByteLength(buffer, fieldSize, 'utf-8');
expect(result).to.equal('A'.repeat(fieldSize));
expect(buffer.position).to.equal(1 + fieldSize);
expect(buffer.readByte()).to.equal(sentinelByte);
});
});