@@ -5,42 +5,67 @@ import * as errors from './errors';
5
5
import * as utils from './utils' ;
6
6
7
7
/**
8
- * The TAR headers follow this structure:
9
- * Start Size Description
10
- * ------------------------------
11
- * 0 100 File name (first 100 bytes)
12
- * 100 8 File mode (null-padded octal)
13
- * 108 8 Owner user id (null-padded octal)
14
- * 116 8 Owner group id (null-padded octal)
15
- * 124 12 File size in bytes (null-padded octal, 0 for directories)
16
- * 136 12 Mtime (null-padded octal)
17
- * 148 8 Checksum (fill with ASCII spaces for computation)
18
- * 156 1 Type flag ('0' for file, '5' for directory)
19
- * 157 100 Link name (null-terminated ASCII/UTF-8)
20
- * 257 6 'ustar\0' (magic string)
21
- * 263 2 '00' (ustar version)
22
- * 265 32 Owner user name (null-terminated ASCII/UTF-8)
23
- * 297 32 Owner group name (null-terminated ASCII/UTF-8)
24
- * 329 8 Device major (unset in this implementation)
25
- * 337 8 Device minor (unset in this implementation)
26
- * 345 155 File name (last 155 bytes, total 255 bytes, null-padded)
27
- * 500 12 '\0' (unused)
8
+ * The Generator can be used to generate blocks for a tar archive. The generator
9
+ * can create three kinds of headers: FILE, DIRECTORY, and EXTENDED. The file and
10
+ * directory is expected, but the extended header is able to store additional
11
+ * metadata that does not fit in the standard header.
12
+ *
13
+ * This class can also be used to generate data chunks padded to 512 bytes. Note
14
+ * that the chunk size shouldn't exceed 512 bytes.
15
+ *
16
+ * Note that the generator maintains an internal state and must be used for
17
+ * operations like generating data chunks, end chunks, or headers, otherwise an
18
+ * error will be thrown.
19
+ *
20
+ * For reference, this is the structure of a tar header.
21
+ *
22
+ * | Start | Size | Description |
23
+ * |--------|------|-----------------------------------------------------------|
24
+ * | 0 | 100 | File name (first 100 bytes) |
25
+ * | 100 | 8 | File mode (null-padded octal) |
26
+ * | 108 | 8 | Owner user ID (null-padded octal) |
27
+ * | 116 | 8 | Owner group ID (null-padded octal) |
28
+ * | 124 | 12 | File size in bytes (null-padded octal, 0 for directories) |
29
+ * | 136 | 12 | Mtime (null-padded octal) |
30
+ * | 148 | 8 | Checksum (fill with ASCII spaces for computation) |
31
+ * | 156 | 1 | Type flag ('0' for file, '5' for directory) |
32
+ * | 157 | 100 | Link name (null-terminated ASCII/UTF-8) |
33
+ * | 257 | 6 | 'ustar\0' (magic string) |
34
+ * | 263 | 2 | '00' (ustar version) |
35
+ * | 265 | 32 | Owner user name (null-terminated ASCII/UTF-8) |
36
+ * | 297 | 32 | Owner group name (null-terminated ASCII/UTF-8) |
37
+ * | 329 | 8 | Device major (unset in this implementation) |
38
+ * | 337 | 8 | Device minor (unset in this implementation) |
39
+ * | 345 | 155 | File name (last 155 bytes, total 255 bytes, null-padded) |
40
+ * | 500 | 12 | '\0' (unused) |
28
41
*
29
- * Note that all numbers are in stringified octal format.
42
+ * Note that all numbers are in stringified octal format, as opposed to the
43
+ * numbers used in the extended header, which are all in stringified decimal.
30
44
*
31
45
* The following data will be left blank (null):
32
46
* - Link name
33
- * - Owner user name
34
- * - Owner group name
35
47
* - Device major
36
48
* - Device minor
37
49
*
38
- * This is because this implementation does not interact with linked files.
39
- * Owner user name and group name cannot be extracted via regular stat-ing,
40
- * so it is left blank. In virtual situations, this field won't be useful
41
- * anyways. The device major and minor are specific to linux kernel, which
42
- * is not relevant to this virtual tar implementation. This is the reason
43
- * these fields have been left blank.
50
+ * This is because this implementation does not interact with linked files.
51
+ * The device major and minor are specific to linux kernel, which is not
52
+ * relevant to this virtual tar implementation. This is the reason these fields
53
+ * have been left blank.
54
+ *
55
+ * The data for extended headers is formatted slightly differently, with the
56
+ * general format following this structure.
57
+ * <size> <key>=<value>\n
58
+ *
59
+ * Here, the <size> stands for the byte length of the entire line (including the
60
+ * size number itself, the space, the equals, and the \n). Unlike in regular
61
+ * strings, the end marker for a key-value pair is the \n (newline) character.
62
+ * Moreover, unlike the USTAR header, the numbers are written in stringified
63
+ * decimal format.
64
+ *
65
+ * The key can be any supported metadata key, and the value is binary data
66
+ * storing the actual value. These are the currently supported keys for
67
+ * the extended metadata:
68
+ * - path (corresponding to file path if it is longer than 255 characters)
44
69
*/
45
70
class Generator {
46
71
protected state : GeneratorState = GeneratorState . HEADER ;
@@ -85,6 +110,7 @@ class Generator {
85
110
filePath = filePath . endsWith ( '/' ) ? filePath : filePath + '/' ;
86
111
}
87
112
113
+ // Write the relevant sections in the header with the provided data
88
114
utils . writeUstarMagic ( header ) ;
89
115
utils . writeFileType ( header , type ) ;
90
116
utils . writeFilePath ( header , filePath ) ;
@@ -103,10 +129,27 @@ class Generator {
103
129
return header ;
104
130
}
105
131
132
+ /**
133
+ * Generates a file header based on the file path and the stat. Note that the
134
+ * stat must provide a size for the file, but all other fields are optional.
135
+ * If the file path is longer than 255 characters, then an error will be
136
+ * thrown. An extended header needs to be generated first, then the file path
137
+ * can be set to an empty string.
138
+ *
139
+ * The content of the file must follow this header in separate chunks.
140
+ *
141
+ * @param filePath the path of the file relative to the tar root
142
+ * @param stat the stats of the file
143
+ * @returns one 512-byte chunk corresponding to the header
144
+ *
145
+ * @see {@link generateExtended } for generating headers with extended metadata
146
+ * @see {@link generateDirectory } for generating directory headers instead
147
+ * @see {@link generateData } for generating data chunks
148
+ */
106
149
generateFile ( filePath : string , stat : FileStat ) : Uint8Array {
107
150
if ( this . state === GeneratorState . HEADER ) {
108
151
// Make sure the size is valid
109
- if ( stat . size == null ) {
152
+ if ( stat . size == null || stat . size < 0 ) {
110
153
throw new errors . ErrorVirtualTarGeneratorInvalidStat (
111
154
'Files must have valid file sizes' ,
112
155
) ;
@@ -130,6 +173,19 @@ class Generator {
130
173
) ;
131
174
}
132
175
176
+ /**
177
+ * Generates a directory header based on the file path and the stat. Note that
178
+ * the size is ignored and set to 0 for directories. If the file path is longer
179
+ * than 255 characters, then an error will be thrown. An extended header needs
180
+ * to be generated first, then the file path can be set to an empty string.
181
+ *
182
+ * @param filePath the path of the file relative to the tar root
183
+ * @param stat the stats of the file
184
+ * @returns one 512-byte chunk corresponding to the header
185
+ *
186
+ * @see {@link generateExtended } for generating headers with extended metadata
187
+ * @see {@link generateFile } for generating file headers instead
188
+ */
133
189
generateDirectory ( filePath : string , stat ?: FileStat ) : Uint8Array {
134
190
if ( this . state === GeneratorState . HEADER ) {
135
191
// The size is zero for directories. Override this value in the stat if
@@ -147,6 +203,14 @@ class Generator {
147
203
) ;
148
204
}
149
205
206
+ /**
207
+ * Generates an extended metadata header based on the total size of the data
208
+ * following the header. If there is no need for extended metadata, then avoid
209
+ * using this, as it would just waste space.
210
+ *
211
+ * @param size the size of the binary data block containing the metadata
212
+ * @returns one 512-byte chunk corresponding to the header
213
+ */
150
214
generateExtended ( size : number ) : Uint8Array {
151
215
if ( this . state === GeneratorState . HEADER ) {
152
216
this . state = GeneratorState . DATA ;
@@ -160,6 +224,22 @@ class Generator {
160
224
) ;
161
225
}
162
226
227
+ /**
228
+ * Generates a data block. The input must be 512 bytes in size or smaller. The
229
+ * input data cannot be chunked smaller than 512 bytes. For example, if the
230
+ * file size is 1023 bytes, then you need to provide a 512-byte chunk first,
231
+ * then provide the remaining 511-byte chunk later. You can not chunk it up
232
+ * like sending over the first 100 bytes, then sending over the next 512.
233
+ *
234
+ * This method is used to generate blocks for both a file and the exnteded
235
+ * header.
236
+ *
237
+ * @param data a block of binary data (512-bytes at largest)
238
+ * @returns one 512-byte padded chunk corresponding to the data block
239
+ *
240
+ * @see {@link generateExtended } for generating headers with extended metadata
241
+ * @see {@link generateFile } for generating file headers preceeding data block
242
+ */
163
243
generateData ( data : Uint8Array ) : Uint8Array {
164
244
if ( this . state === GeneratorState . DATA ) {
165
245
if ( data . byteLength > constants . BLOCK_SIZE ) {
@@ -198,9 +278,13 @@ class Generator {
198
278
) ;
199
279
}
200
280
201
- // Creates a single null block. A null block is a block filled with all zeros.
202
- // This is needed to end the archive, as two of these blocks mark the end of
203
- // archive.
281
+ /**
282
+ * Generates a null chunk. Two invocations are needed to create a valid
283
+ * archive end marker. After two invocations, the generator state will be
284
+ * set to ENDED and no further data can be fed through the generator.
285
+ *
286
+ * @returns one 512-byte null chunk
287
+ */
204
288
generateEnd ( ) : Uint8Array {
205
289
switch ( this . state ) {
206
290
case GeneratorState . HEADER :
0 commit comments