8
8
use DateTimeInterface ;
9
9
use DeflateContext ;
10
10
use RuntimeException ;
11
+ use ZipStream \Exception \FileSizeIncorrectException ;
11
12
use ZipStream \Exception \OverflowException ;
12
13
use ZipStream \Exception \ResourceActionException ;
14
+ use ZipStream \Exception \SimulationFileUnknownException ;
13
15
use ZipStream \Exception \StreamNotReadableException ;
14
16
use ZipStream \Exception \StreamNotSeekableException ;
15
17
@@ -32,8 +34,6 @@ class File
32
34
33
35
private readonly string $ fileName ;
34
36
35
- private int $ totalSize = 0 ;
36
-
37
37
/**
38
38
* @var resource
39
39
*/
@@ -44,15 +44,18 @@ class File
44
44
*/
45
45
public function __construct (
46
46
string $ fileName ,
47
- private int $ startOffset ,
47
+ private readonly OperationMode $ operationMode ,
48
+ private readonly int $ startOffset ,
48
49
private readonly CompressionMethod $ compressionMethod ,
49
50
private readonly string $ comment ,
50
51
private readonly DateTimeInterface $ lastModificationDateTime ,
51
52
private readonly int $ deflateLevel ,
52
53
private readonly ?int $ maxSize ,
54
+ private readonly ?int $ exactSize ,
53
55
private readonly bool $ enableZip64 ,
54
56
private readonly bool $ enableZeroHeader ,
55
57
private readonly Closure $ send ,
58
+ private readonly Closure $ recordSentBytes ,
56
59
$ stream ,
57
60
) {
58
61
$ this ->fileName = self ::filterFilename ($ fileName );
@@ -81,20 +84,61 @@ public function __construct(
81
84
82
85
public function process (): string
83
86
{
84
- if (!$ this ->enableZeroHeader ) {
87
+ $ forecastSize = $ this ->forecastSize ();
88
+
89
+ if ($ this ->enableZeroHeader ) {
90
+ // No calculation required
91
+ } elseif ($ this ->isSimulation () && $ forecastSize ) {
92
+ $ this ->uncompressedSize = $ forecastSize ;
93
+ $ this ->compressedSize = $ forecastSize ;
94
+ } elseif ($ this ->operationMode === OperationMode::SIMULATE_STRICT ) {
95
+ throw new SimulationFileUnknownException ();
96
+ } else {
85
97
$ this ->readStream (send: false );
86
98
if (rewind ($ this ->stream ) === false ) {
87
99
throw new ResourceActionException ('rewind ' , $ this ->stream );
88
100
}
89
101
}
90
102
91
103
$ this ->addFileHeader ();
92
- $ this ->readStream (send: true );
93
- $ this ->addFileFooter ();
94
104
105
+ $ detectedSize = $ forecastSize ?? $ this ->compressedSize ;
106
+
107
+ if (
108
+ $ this ->isSimulation () &&
109
+ $ detectedSize > 0
110
+ ) {
111
+ ($ this ->recordSentBytes )($ detectedSize );
112
+ } elseif ($ this ->operationMode === OperationMode::SIMULATE_STRICT ) {
113
+ throw new SimulationFileUnknownException ();
114
+ } else {
115
+ $ this ->readStream (send: true );
116
+ }
117
+
118
+ $ this ->addFileFooter ();
95
119
return $ this ->getCdrFile ();
96
120
}
97
121
122
+ private function forecastSize (): ?int
123
+ {
124
+ if ($ this ->compressionMethod !== CompressionMethod::STORE ) {
125
+ return null ;
126
+ }
127
+ if ($ this ->exactSize ) {
128
+ return $ this ->exactSize ;
129
+ }
130
+ $ fstat = fstat ($ this ->stream );
131
+ if (!$ fstat || !array_key_exists ('size ' , $ fstat ) || $ fstat ['size ' ] < 1 ) {
132
+ return null ;
133
+ }
134
+
135
+ if ($ this ->maxSize !== null && $ this ->maxSize < $ fstat ['size ' ]) {
136
+ return $ this ->maxSize ;
137
+ }
138
+
139
+ return $ fstat ['size ' ];
140
+ }
141
+
98
142
/**
99
143
* Create and send zip header for this file.
100
144
*/
@@ -127,8 +171,6 @@ private function addFileHeader(): void
127
171
128
172
129
173
($ this ->send )($ data );
130
-
131
- $ this ->totalSize += strlen ($ data );
132
174
}
133
175
134
176
/**
@@ -239,8 +281,6 @@ private function addFileFooter(): void
239
281
}
240
282
241
283
($ this ->send )($ footer );
242
-
243
- $ this ->totalSize += strlen ($ footer );
244
284
}
245
285
246
286
private function readStream (bool $ send ): void
@@ -251,8 +291,16 @@ private function readStream(bool $send): void
251
291
252
292
$ deflate = $ this ->compressionInit ();
253
293
254
- while (!feof ($ this ->stream ) && ($ this ->maxSize === null || $ this ->uncompressedSize < $ this ->maxSize )) {
255
- $ readLength = min (($ this ->maxSize ?? PHP_INT_MAX ) - $ this ->uncompressedSize , self ::CHUNKED_READ_BLOCK_SIZE );
294
+ while (
295
+ !feof ($ this ->stream ) &&
296
+ ($ this ->maxSize === null || $ this ->uncompressedSize < $ this ->maxSize ) &&
297
+ ($ this ->exactSize === null || $ this ->uncompressedSize < $ this ->exactSize )
298
+ ) {
299
+ $ readLength = min (
300
+ ($ this ->maxSize ?? PHP_INT_MAX ) - $ this ->uncompressedSize ,
301
+ ($ this ->exactSize ?? PHP_INT_MAX ) - $ this ->uncompressedSize ,
302
+ self ::CHUNKED_READ_BLOCK_SIZE
303
+ );
256
304
257
305
$ data = fread ($ this ->stream , $ readLength );
258
306
@@ -272,10 +320,13 @@ private function readStream(bool $send): void
272
320
273
321
if ($ send ) {
274
322
($ this ->send )($ data );
275
- $ this ->totalSize += strlen ($ data );
276
323
}
277
324
}
278
325
326
+ if ($ this ->exactSize && $ this ->uncompressedSize !== $ this ->exactSize ) {
327
+ throw new FileSizeIncorrectException (expectedSize: $ this ->exactSize , actualSize: $ this ->uncompressedSize );
328
+ }
329
+
279
330
$ this ->crc = hexdec (hash_final ($ hash ));
280
331
}
281
332
@@ -334,4 +385,9 @@ private function getCdrFile(): string
334
385
: $ this ->startOffset ,
335
386
);
336
387
}
388
+
389
+ private function isSimulation (): bool
390
+ {
391
+ return $ this ->operationMode === OperationMode::SIMULATE_LAX || $ this ->operationMode === OperationMode::SIMULATE_STRICT ;
392
+ }
337
393
}
0 commit comments