11import type { FileType , FileStat } from './types' ;
2- import { GeneratorState , EntryType } from './types' ;
2+ import { GeneratorState , EntryType , HeaderSize } from './types' ;
33import * as errors from './errors' ;
44import * as utils from './utils' ;
55import * as constants from './constants' ;
@@ -27,14 +27,14 @@ import * as constants from './constants';
2727 * 500 12 '\0' (unused)
2828 *
2929 * Note that all numbers are in stringified octal format.
30- *
30+ *
3131 * The following data will be left blank (null):
3232 * - Link name
3333 * - Owner user name
3434 * - Owner group name
3535 * - Device major
3636 * - Device minor
37- *
37+ *
3838 * This is because this implementation does not interact with linked files.
3939 * Owner user name and group name cannot be extracted via regular stat-ing,
4040 * so it is left blank. In virtual situations, this field won't be useful
@@ -43,16 +43,40 @@ import * as constants from './constants';
4343 * these fields have been left blank.
4444 */
4545class Generator {
46- protected state : GeneratorState = GeneratorState . READY ;
46+ protected state : GeneratorState = GeneratorState . HEADER ;
4747 protected remainingBytes = 0 ;
4848
49- protected generateHeader ( filePath : string , type : FileType , stat : FileStat ) {
49+ protected generateHeader ( filePath : string , type : FileType , stat : FileStat ) : Uint8Array {
5050 if ( filePath . length > 255 ) {
5151 throw new errors . ErrorVirtualTarGeneratorInvalidFileName (
5252 'The file name must shorter than 255 characters' ,
5353 ) ;
5454 }
5555
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+
5680 const header = new Uint8Array ( constants . BLOCK_SIZE ) ;
5781
5882 // Every directory in tar must have a trailing slash
@@ -66,6 +90,8 @@ class Generator {
6690 utils . writeFileMode ( header , stat . mode ) ;
6791 utils . writeOwnerUid ( header , stat . uid ) ;
6892 utils . writeOwnerGid ( header , stat . gid ) ;
93+ utils . writeOwnerUserName ( header , stat . username ) ;
94+ utils . writeOwnerGroupName ( header , stat . groupname ) ;
6995 utils . writeFileSize ( header , stat . size ) ;
7096 utils . writeFileMtime ( header , stat . mtime ) ;
7197
@@ -77,11 +103,11 @@ class Generator {
77103 }
78104
79105 generateFile ( filePath : string , stat : FileStat ) : Uint8Array {
80- if ( this . state === GeneratorState . READY ) {
106+ if ( this . state === GeneratorState . HEADER ) {
81107 // Make sure the size is valid
82108 if ( stat . size == null ) {
83109 throw new errors . ErrorVirtualTarGeneratorInvalidStat (
84- 'Files should have valid file sizes' ,
110+ 'Files must have valid file sizes' ,
85111 ) ;
86112 }
87113
@@ -97,59 +123,58 @@ class Generator {
97123 return generatedBlock ;
98124 }
99125 throw new errors . ErrorVirtualTarGeneratorInvalidState (
100- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
126+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
101127 GeneratorState [ this . state ]
102128 } `,
103129 ) ;
104130 }
105131
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.
108136 const directoryStat : FileStat = {
137+ ...stat ,
109138 size : 0 ,
110- mode : stat . mode ,
111- mtime : stat . mtime ,
112- uid : stat . uid ,
113- gid : stat . gid ,
114139 } ;
115140 return this . generateHeader ( filePath , 'directory' , directoryStat ) ;
116141 }
117142 throw new errors . ErrorVirtualTarGeneratorInvalidState (
118- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
143+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
119144 GeneratorState [ this . state ]
120145 } `,
121146 ) ;
122147 }
123148
124149 generateExtended ( size : number ) : Uint8Array {
125- if ( this . state === GeneratorState . READY ) {
150+ if ( this . state === GeneratorState . HEADER ) {
126151 this . state = GeneratorState . DATA ;
127152 this . remainingBytes = size ;
128153 return this . generateHeader ( './PaxHeader' , 'extended' , { size } ) ;
129154 }
130155 throw new errors . ErrorVirtualTarGeneratorInvalidState (
131- `Expected state ${ GeneratorState [ GeneratorState . READY ] } but got ${
156+ `Expected state ${ GeneratorState [ GeneratorState . HEADER ] } but got ${
132157 GeneratorState [ this . state ]
133158 } `,
134159 ) ;
135160 }
136161
137162 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-
144163 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+
145170 if ( this . remainingBytes >= constants . BLOCK_SIZE ) {
146171 this . remainingBytes -= constants . BLOCK_SIZE ;
147- if ( this . remainingBytes === 0 ) this . state = GeneratorState . READY ;
172+ if ( this . remainingBytes === 0 ) this . state = GeneratorState . HEADER ;
148173 return data ;
149174 } else {
150175 // Update state
151176 this . remainingBytes = 0 ;
152- this . state = GeneratorState . READY ;
177+ this . state = GeneratorState . HEADER ;
153178
154179 // Pad the remaining data with nulls
155180 const paddedData = new Uint8Array ( constants . BLOCK_SIZE ) ;
@@ -168,9 +193,9 @@ class Generator {
168193 // Creates a single null block. A null block is a block filled with all zeros.
169194 // This is needed to end the archive, as two of these blocks mark the end of
170195 // archive.
171- generateEnd ( ) {
196+ generateEnd ( ) : Uint8Array {
172197 switch ( this . state ) {
173- case GeneratorState . READY :
198+ case GeneratorState . HEADER :
174199 this . state = GeneratorState . NULL ;
175200 break ;
176201 case GeneratorState . NULL :
0 commit comments