1
1
import type { FileType , FileStat } from './types' ;
2
- import { GeneratorState , EntryType } from './types' ;
2
+ import { GeneratorState , EntryType , HeaderSize } from './types' ;
3
3
import * as errors from './errors' ;
4
4
import * as utils from './utils' ;
5
5
import * as constants from './constants' ;
@@ -27,14 +27,14 @@ import * as constants from './constants';
27
27
* 500 12 '\0' (unused)
28
28
*
29
29
* Note that all numbers are in stringified octal format.
30
- *
30
+ *
31
31
* The following data will be left blank (null):
32
32
* - Link name
33
33
* - Owner user name
34
34
* - Owner group name
35
35
* - Device major
36
36
* - Device minor
37
- *
37
+ *
38
38
* This is because this implementation does not interact with linked files.
39
39
* Owner user name and group name cannot be extracted via regular stat-ing,
40
40
* so it is left blank. In virtual situations, this field won't be useful
@@ -43,16 +43,40 @@ import * as constants from './constants';
43
43
* these fields have been left blank.
44
44
*/
45
45
class Generator {
46
- protected state : GeneratorState = GeneratorState . READY ;
46
+ protected state : GeneratorState = GeneratorState . HEADER ;
47
47
protected remainingBytes = 0 ;
48
48
49
- protected generateHeader ( filePath : string , type : FileType , stat : FileStat ) {
49
+ protected generateHeader ( filePath : string , type : FileType , stat : FileStat ) : Uint8Array {
50
50
if ( filePath . length > 255 ) {
51
51
throw new errors . ErrorVirtualTarGeneratorInvalidFileName (
52
52
'The file name must shorter than 255 characters' ,
53
53
) ;
54
54
}
55
55
56
+ if ( stat ?. size != null && stat ?. size > 0o7777777 ) {
57
+ throw new errors . ErrorVirtualTarGeneratorInvalidStat (
58
+ 'The file size must be smaller than 7.99 GiB (8,589,934,591 bytes)' ,
59
+ ) ;
60
+ }
61
+
62
+ if (
63
+ stat ?. username != null &&
64
+ stat ?. username . length > HeaderSize . OWNER_USERNAME
65
+ ) {
66
+ throw new errors . ErrorVirtualTarGeneratorInvalidStat (
67
+ `The username must not exceed ${ HeaderSize . OWNER_USERNAME } bytes` ,
68
+ ) ;
69
+ }
70
+
71
+ if (
72
+ stat ?. groupname != null &&
73
+ stat ?. groupname . length > HeaderSize . OWNER_GROUPNAME
74
+ ) {
75
+ throw new errors . ErrorVirtualTarGeneratorInvalidStat (
76
+ `The groupname must not exceed ${ HeaderSize . OWNER_GROUPNAME } bytes` ,
77
+ ) ;
78
+ }
79
+
56
80
const header = new Uint8Array ( constants . BLOCK_SIZE ) ;
57
81
58
82
// Every directory in tar must have a trailing slash
@@ -66,6 +90,8 @@ class Generator {
66
90
utils . writeFileMode ( header , stat . mode ) ;
67
91
utils . writeOwnerUid ( header , stat . uid ) ;
68
92
utils . writeOwnerGid ( header , stat . gid ) ;
93
+ utils . writeOwnerUserName ( header , stat . username ) ;
94
+ utils . writeOwnerGroupName ( header , stat . groupname ) ;
69
95
utils . writeFileSize ( header , stat . size ) ;
70
96
utils . writeFileMtime ( header , stat . mtime ) ;
71
97
@@ -77,11 +103,11 @@ class Generator {
77
103
}
78
104
79
105
generateFile ( filePath : string , stat : FileStat ) : Uint8Array {
80
- if ( this . state === GeneratorState . READY ) {
106
+ if ( this . state === GeneratorState . HEADER ) {
81
107
// Make sure the size is valid
82
108
if ( stat . size == null ) {
83
109
throw new errors . ErrorVirtualTarGeneratorInvalidStat (
84
- 'Files should have valid file sizes' ,
110
+ 'Files must have valid file sizes' ,
85
111
) ;
86
112
}
87
113
@@ -97,59 +123,58 @@ class Generator {
97
123
return generatedBlock ;
98
124
}
99
125
throw new errors . ErrorVirtualTarGeneratorInvalidState (
100
- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
126
+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
101
127
GeneratorState [ this . state ]
102
128
} `,
103
129
) ;
104
130
}
105
131
106
- generateDirectory ( filePath : string , stat : FileStat ) : Uint8Array {
107
- if ( this . state === GeneratorState . READY ) {
132
+ generateDirectory ( filePath : string , stat ?: FileStat ) : Uint8Array {
133
+ if ( this . state === GeneratorState . HEADER ) {
134
+ // The size is zero for directories. Override this value in the stat if
135
+ // set.
108
136
const directoryStat : FileStat = {
137
+ ...stat ,
109
138
size : 0 ,
110
- mode : stat . mode ,
111
- mtime : stat . mtime ,
112
- uid : stat . uid ,
113
- gid : stat . gid ,
114
139
} ;
115
140
return this . generateHeader ( filePath , 'directory' , directoryStat ) ;
116
141
}
117
142
throw new errors . ErrorVirtualTarGeneratorInvalidState (
118
- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
143
+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
119
144
GeneratorState [ this . state ]
120
145
} `,
121
146
) ;
122
147
}
123
148
124
149
generateExtended ( size : number ) : Uint8Array {
125
- if ( this . state === GeneratorState . READY ) {
150
+ if ( this . state === GeneratorState . HEADER ) {
126
151
this . state = GeneratorState . DATA ;
127
152
this . remainingBytes = size ;
128
153
return this . generateHeader ( './PaxHeader' , 'extended' , { size } ) ;
129
154
}
130
155
throw new errors . ErrorVirtualTarGeneratorInvalidState (
131
- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
156
+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
132
157
GeneratorState [ this . state ]
133
158
} `,
134
159
) ;
135
160
}
136
161
137
162
generateData ( data : Uint8Array ) : Uint8Array {
138
- if ( data . byteLength > constants . BLOCK_SIZE ) {
139
- throw new errors . ErrorVirtualTarGeneratorBlockSize (
140
- `Expected data to be ${ constants . BLOCK_SIZE } bytes but received ${ data . byteLength } bytes` ,
141
- ) ;
142
- }
143
-
144
163
if ( this . state === GeneratorState . DATA ) {
164
+ if ( data . byteLength > constants . BLOCK_SIZE ) {
165
+ throw new errors . ErrorVirtualTarGeneratorBlockSize (
166
+ `Expected data to be ${ constants . BLOCK_SIZE } bytes but received ${ data . byteLength } bytes` ,
167
+ ) ;
168
+ }
169
+
145
170
if ( this . remainingBytes >= constants . BLOCK_SIZE ) {
146
171
this . remainingBytes -= constants . BLOCK_SIZE ;
147
- if ( this . remainingBytes === 0 ) this . state = GeneratorState . READY ;
172
+ if ( this . remainingBytes === 0 ) this . state = GeneratorState . HEADER ;
148
173
return data ;
149
174
} else {
150
175
// Update state
151
176
this . remainingBytes = 0 ;
152
- this . state = GeneratorState . READY ;
177
+ this . state = GeneratorState . HEADER ;
153
178
154
179
// Pad the remaining data with nulls
155
180
const paddedData = new Uint8Array ( constants . BLOCK_SIZE ) ;
@@ -168,9 +193,9 @@ class Generator {
168
193
// Creates a single null block. A null block is a block filled with all zeros.
169
194
// This is needed to end the archive, as two of these blocks mark the end of
170
195
// archive.
171
- generateEnd ( ) {
196
+ generateEnd ( ) : Uint8Array {
172
197
switch ( this . state ) {
173
- case GeneratorState . READY :
198
+ case GeneratorState . HEADER :
174
199
this . state = GeneratorState . NULL ;
175
200
break ;
176
201
case GeneratorState . NULL :
0 commit comments