@@ -359,6 +359,7 @@ var JpegImage = (function jpegImage() {
359
359
var blocksPerLine = component . blocksPerLine ;
360
360
var blocksPerColumn = component . blocksPerColumn ;
361
361
var samplesPerLine = blocksPerLine << 3 ;
362
+ // Only 1 used per invocation of this function and garbage collected after invocation, so no need to account for its memory footprint.
362
363
var R = new Int32Array ( 64 ) , r = new Uint8Array ( 64 ) ;
363
364
364
365
// A port of poppler's IDCT method which in turn is taken from:
@@ -521,6 +522,8 @@ var JpegImage = (function jpegImage() {
521
522
}
522
523
}
523
524
525
+ requestMemoryAllocation ( samplesPerLine * blocksPerColumn * 8 ) ;
526
+
524
527
var i , j ;
525
528
for ( var blockRow = 0 ; blockRow < blocksPerColumn ; blockRow ++ ) {
526
529
var scanLine = blockRow << 3 ;
@@ -559,6 +562,7 @@ var JpegImage = (function jpegImage() {
559
562
xhr . send ( null ) ;
560
563
} ,
561
564
parse : function parse ( data ) {
565
+ var maxResolutionInPixels = this . opts . maxResolutionInMP * 1000 * 1000 ;
562
566
var offset = 0 , length = data . length ;
563
567
function readUint16 ( ) {
564
568
var value = ( data [ offset ] << 8 ) | data [ offset + 1 ] ;
@@ -590,7 +594,12 @@ var JpegImage = (function jpegImage() {
590
594
var blocksPerColumn = Math . ceil ( Math . ceil ( frame . scanLines / 8 ) * component . v / maxV ) ;
591
595
var blocksPerLineForMcu = mcusPerLine * component . h ;
592
596
var blocksPerColumnForMcu = mcusPerColumn * component . v ;
597
+ var blocksToAllocate = blocksPerColumnForMcu * blocksPerLineForMcu ;
593
598
var blocks = [ ] ;
599
+
600
+ // Each block is a Int32Array of length 64 (4 x 64 = 256 bytes)
601
+ requestMemoryAllocation ( blocksToAllocate * 256 ) ;
602
+
594
603
for ( var i = 0 ; i < blocksPerColumnForMcu ; i ++ ) {
595
604
var row = [ ] ;
596
605
for ( var j = 0 ; j < blocksPerLineForMcu ; j ++ )
@@ -685,6 +694,7 @@ var JpegImage = (function jpegImage() {
685
694
var quantizationTablesEnd = quantizationTablesLength + offset - 2 ;
686
695
while ( offset < quantizationTablesEnd ) {
687
696
var quantizationTableSpec = data [ offset ++ ] ;
697
+ requestMemoryAllocation ( 64 * 4 ) ;
688
698
var tableData = new Int32Array ( 64 ) ;
689
699
if ( ( quantizationTableSpec >> 4 ) === 0 ) { // 8 bit values
690
700
for ( j = 0 ; j < 64 ; j ++ ) {
@@ -714,6 +724,13 @@ var JpegImage = (function jpegImage() {
714
724
frame . samplesPerLine = readUint16 ( ) ;
715
725
frame . components = { } ;
716
726
frame . componentsOrder = [ ] ;
727
+
728
+ var pixelsInFrame = frame . scanLines * frame . samplesPerLine ;
729
+ if ( pixelsInFrame > maxResolutionInPixels ) {
730
+ var exceededAmount = Math . ceil ( ( pixelsInFrame - maxResolutionInPixels ) / 1e6 ) ;
731
+ throw new Error ( `maxResolutionInMP limit exceeded by ${ exceededAmount } MP` ) ;
732
+ }
733
+
717
734
var componentsCount = data [ offset ++ ] , componentId ;
718
735
var maxH = 0 , maxV = 0 ;
719
736
for ( i = 0 ; i < componentsCount ; i ++ ) {
@@ -739,8 +756,10 @@ var JpegImage = (function jpegImage() {
739
756
var huffmanTableSpec = data [ offset ++ ] ;
740
757
var codeLengths = new Uint8Array ( 16 ) ;
741
758
var codeLengthSum = 0 ;
742
- for ( j = 0 ; j < 16 ; j ++ , offset ++ )
759
+ for ( j = 0 ; j < 16 ; j ++ , offset ++ ) {
743
760
codeLengthSum += ( codeLengths [ j ] = data [ offset ] ) ;
761
+ }
762
+ requestMemoryAllocation ( 16 + codeLengthSum ) ;
744
763
var huffmanValues = new Uint8Array ( codeLengthSum ) ;
745
764
for ( j = 0 ; j < codeLengthSum ; j ++ , offset ++ )
746
765
huffmanValues [ j ] = data [ offset ] ;
@@ -832,6 +851,7 @@ var JpegImage = (function jpegImage() {
832
851
var Y , Cb , Cr , K , C , M , Ye , R , G , B ;
833
852
var colorTransform ;
834
853
var dataLength = width * height * this . components . length ;
854
+ requestMemoryAllocation ( dataLength ) ;
835
855
var data = new Uint8Array ( dataLength ) ;
836
856
switch ( this . components . length ) {
837
857
case 1 :
@@ -1009,6 +1029,31 @@ var JpegImage = (function jpegImage() {
1009
1029
}
1010
1030
} ;
1011
1031
1032
+
1033
+ // We cap the amount of memory used by jpeg-js to avoid unexpected OOMs from untrusted content.
1034
+ var totalBytesAllocated = 0 ;
1035
+ var maxMemoryUsageBytes = 0 ;
1036
+ function requestMemoryAllocation ( increaseAmount = 0 ) {
1037
+ var totalMemoryImpactBytes = totalBytesAllocated + increaseAmount ;
1038
+ if ( totalMemoryImpactBytes > maxMemoryUsageBytes ) {
1039
+ var exceededAmount = Math . ceil ( ( totalMemoryImpactBytes - maxMemoryUsageBytes ) / 1024 / 1024 ) ;
1040
+ throw new Error ( `maxMemoryUsageInMB limit exceeded by at least ${ exceededAmount } MB` ) ;
1041
+ }
1042
+
1043
+ totalBytesAllocated = totalMemoryImpactBytes ;
1044
+ }
1045
+
1046
+ constructor . resetMaxMemoryUsage = function ( maxMemoryUsageBytes_ ) {
1047
+ totalBytesAllocated = 0 ;
1048
+ maxMemoryUsageBytes = maxMemoryUsageBytes_ ;
1049
+ } ;
1050
+
1051
+ constructor . getBytesAllocated = function ( ) {
1052
+ return totalBytesAllocated ;
1053
+ } ;
1054
+
1055
+ constructor . requestMemoryAllocation = requestMemoryAllocation ;
1056
+
1012
1057
return constructor ;
1013
1058
} ) ( ) ;
1014
1059
@@ -1026,18 +1071,24 @@ function decode(jpegData, userOpts = {}) {
1026
1071
colorTransform : undefined ,
1027
1072
formatAsRGBA : true ,
1028
1073
tolerantDecoding : false ,
1074
+ maxResolutionInMP : 100 , // Don't decode more than 100 megapixels
1075
+ maxMemoryUsageInMB : 512 , // Don't decode if memory footprint is more than 512MB
1029
1076
} ;
1030
1077
1031
1078
var opts = { ...defaultOpts , ...userOpts } ;
1032
1079
var arr = new Uint8Array ( jpegData ) ;
1033
1080
var decoder = new JpegImage ( ) ;
1034
1081
decoder . opts = opts ;
1082
+ // If this constructor ever supports async decoding this will need to be done differently.
1083
+ // Until then, treating as singleton limit is fine.
1084
+ JpegImage . resetMaxMemoryUsage ( opts . maxMemoryUsageInMB * 1024 * 1024 ) ;
1035
1085
decoder . parse ( arr ) ;
1036
1086
decoder . colorTransform = opts . colorTransform ;
1037
1087
1038
1088
var channels = ( opts . formatAsRGBA ) ? 4 : 3 ;
1039
1089
var bytesNeeded = decoder . width * decoder . height * channels ;
1040
1090
try {
1091
+ JpegImage . requestMemoryAllocation ( bytesNeeded ) ;
1041
1092
var image = {
1042
1093
width : decoder . width ,
1043
1094
height : decoder . height ,
0 commit comments