1
+ using System . Buffers ;
1
2
using System . IO . Compression ;
2
3
3
4
namespace HotChocolate . Fusion . Packaging ;
@@ -6,15 +7,17 @@ internal sealed class ArchiveSession : IDisposable
6
7
{
7
8
private readonly Dictionary < string , FileEntry > _files = [ ] ;
8
9
private readonly ZipArchive _archive ;
10
+ private readonly FusionArchiveReadOptions _readOptions ;
9
11
private FusionArchiveMode _mode ;
10
12
private bool _disposed ;
11
13
12
- public ArchiveSession ( ZipArchive archive , FusionArchiveMode mode )
14
+ public ArchiveSession ( ZipArchive archive , FusionArchiveMode mode , FusionArchiveReadOptions readOptions )
13
15
{
14
16
ArgumentNullException . ThrowIfNull ( archive ) ;
15
17
16
18
_archive = archive ;
17
19
_mode = mode ;
20
+ _readOptions = readOptions ;
18
21
}
19
22
20
23
public bool HasUncommittedChanges
@@ -39,7 +42,7 @@ public IEnumerable<string> GetFiles()
39
42
return files ;
40
43
}
41
44
42
- public async Task < bool > ExistsAsync ( string path , CancellationToken cancellationToken )
45
+ public async Task < bool > ExistsAsync ( string path , FileKind kind , CancellationToken cancellationToken )
43
46
{
44
47
if ( _files . TryGetValue ( path , out var file ) )
45
48
{
@@ -49,12 +52,7 @@ public async Task<bool> ExistsAsync(string path, CancellationToken cancellationT
49
52
if ( _mode is not FusionArchiveMode . Create && _archive . GetEntry ( path ) is { } entry )
50
53
{
51
54
file = FileEntry . Read ( path ) ;
52
- #if NET10_0_OR_GREATER
53
- await entry . ExtractToFileAsync ( file . TempPath , cancellationToken ) ;
54
- #else
55
- entry . ExtractToFile ( file . TempPath ) ;
56
- await Task . CompletedTask ;
57
- #endif
55
+ await ExtractFileAsync ( entry , file , GetAllowedSize ( kind ) , cancellationToken ) ;
58
56
_files . Add ( path , file ) ;
59
57
return true ;
60
58
}
@@ -72,7 +70,7 @@ public bool Exists(string path)
72
70
return _mode is not FusionArchiveMode . Create && _archive . GetEntry ( path ) is not null ;
73
71
}
74
72
75
- public async Task < Stream > OpenReadAsync ( string path , CancellationToken cancellationToken )
73
+ public async Task < Stream > OpenReadAsync ( string path , FileKind kind , CancellationToken cancellationToken )
76
74
{
77
75
if ( _files . TryGetValue ( path , out var file ) )
78
76
{
@@ -87,12 +85,7 @@ public async Task<Stream> OpenReadAsync(string path, CancellationToken cancellat
87
85
if ( _mode is not FusionArchiveMode . Create && _archive . GetEntry ( path ) is { } entry )
88
86
{
89
87
file = FileEntry . Read ( path ) ;
90
- #if NET10_0_OR_GREATER
91
- await entry . ExtractToFileAsync ( file . TempPath , cancellationToken ) ;
92
- #else
93
- entry . ExtractToFile ( file . TempPath ) ;
94
- await Task . CompletedTask ;
95
- #endif
88
+ await ExtractFileAsync ( entry , file , GetAllowedSize ( kind ) , cancellationToken ) ;
96
89
var stream = File . OpenRead ( file . TempPath ) ;
97
90
_files . Add ( path , file ) ;
98
91
return stream ;
@@ -181,21 +174,66 @@ await _archive.CreateEntryFromFileAsync(
181
174
}
182
175
}
183
176
177
+ private static async Task ExtractFileAsync (
178
+ ZipArchiveEntry zipEntry ,
179
+ FileEntry fileEntry ,
180
+ int maxAllowedSize ,
181
+ CancellationToken cancellationToken )
182
+ {
183
+ var buffer = ArrayPool < byte > . Shared . Rent ( 4096 ) ;
184
+ var consumed = 0 ;
185
+
186
+ await using var readStream = zipEntry . Open ( ) ;
187
+ await using var writeStream = File . Open ( fileEntry . TempPath , FileMode . Create , FileAccess . Write ) ;
188
+
189
+ int read ;
190
+ while ( ( read = await readStream . ReadAsync ( buffer , cancellationToken ) ) > 0 )
191
+ {
192
+ consumed += read ;
193
+
194
+ if ( consumed > maxAllowedSize )
195
+ {
196
+ throw new InvalidOperationException (
197
+ $ "File is too large and exceeds the allowed size of { maxAllowedSize } .") ;
198
+ }
199
+
200
+ await writeStream . WriteAsync ( buffer . AsMemory ( 0 , read ) , cancellationToken ) ;
201
+ }
202
+ }
203
+
204
+ private int GetAllowedSize ( FileKind kind )
205
+ => kind switch
206
+ {
207
+ FileKind . Schema
208
+ => _readOptions . MaxAllowedSchemaSize ,
209
+ FileKind . Manifest or FileKind . Settings or FileKind . Metadata or FileKind . Signature
210
+ => _readOptions . MaxAllowedSettingsSize ,
211
+ _ => throw new ArgumentOutOfRangeException ( nameof ( kind ) , kind , null )
212
+ } ;
213
+
184
214
public void Dispose ( )
185
215
{
186
216
if ( _disposed )
187
217
{
188
218
return ;
189
219
}
190
220
191
- _disposed = true ;
192
221
foreach ( var file in _files . Values )
193
222
{
194
- if ( file . State is not FileState . Deleted )
223
+ if ( file . State is not FileState . Deleted && File . Exists ( file . TempPath ) )
195
224
{
196
- File . Delete ( file . TempPath ) ;
225
+ try
226
+ {
227
+ File . Delete ( file . TempPath ) ;
228
+ }
229
+ catch
230
+ {
231
+ // ignore
232
+ }
197
233
}
198
234
}
235
+
236
+ _disposed = true ;
199
237
}
200
238
201
239
private class FileEntry
0 commit comments