1
2
3
4
5
6
7
8
9
10
11 package tar
12
13 import (
14 "errors"
15 "fmt"
16 "internal/godebug"
17 "io/fs"
18 "maps"
19 "math"
20 "path"
21 "reflect"
22 "strconv"
23 "strings"
24 "time"
25 )
26
27
28
29
30
31 var tarinsecurepath = godebug.New("tarinsecurepath")
32
33 var (
34 ErrHeader = errors.New("archive/tar: invalid tar header")
35 ErrWriteTooLong = errors.New("archive/tar: write too long")
36 ErrFieldTooLong = errors.New("archive/tar: header field too long")
37 ErrWriteAfterClose = errors.New("archive/tar: write after close")
38 ErrInsecurePath = errors.New("archive/tar: insecure file path")
39 errMissData = errors.New("archive/tar: sparse file references non-existent data")
40 errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data")
41 errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole")
42 errSparseTooLong = errors.New("archive/tar: sparse map too long")
43 )
44
45 type headerError []string
46
47 func (he headerError) Error() string {
48 const prefix = "archive/tar: cannot encode header"
49 var ss []string
50 for _, s := range he {
51 if s != "" {
52 ss = append(ss, s)
53 }
54 }
55 if len(ss) == 0 {
56 return prefix
57 }
58 return fmt.Sprintf("%s: %v", prefix, strings.Join(ss, "; and "))
59 }
60
61
62 const (
63
64 TypeReg = '0'
65
66
67 TypeRegA = '\x00'
68
69
70 TypeLink = '1'
71 TypeSymlink = '2'
72 TypeChar = '3'
73 TypeBlock = '4'
74 TypeDir = '5'
75 TypeFifo = '6'
76
77
78 TypeCont = '7'
79
80
81
82
83 TypeXHeader = 'x'
84
85
86
87
88
89 TypeXGlobalHeader = 'g'
90
91
92 TypeGNUSparse = 'S'
93
94
95
96
97 TypeGNULongName = 'L'
98 TypeGNULongLink = 'K'
99 )
100
101
102 const (
103 paxNone = ""
104 paxPath = "path"
105 paxLinkpath = "linkpath"
106 paxSize = "size"
107 paxUid = "uid"
108 paxGid = "gid"
109 paxUname = "uname"
110 paxGname = "gname"
111 paxMtime = "mtime"
112 paxAtime = "atime"
113 paxCtime = "ctime"
114 paxCharset = "charset"
115 paxComment = "comment"
116
117 paxSchilyXattr = "SCHILY.xattr."
118
119
120 paxGNUSparse = "GNU.sparse."
121 paxGNUSparseNumBlocks = "GNU.sparse.numblocks"
122 paxGNUSparseOffset = "GNU.sparse.offset"
123 paxGNUSparseNumBytes = "GNU.sparse.numbytes"
124 paxGNUSparseMap = "GNU.sparse.map"
125 paxGNUSparseName = "GNU.sparse.name"
126 paxGNUSparseMajor = "GNU.sparse.major"
127 paxGNUSparseMinor = "GNU.sparse.minor"
128 paxGNUSparseSize = "GNU.sparse.size"
129 paxGNUSparseRealSize = "GNU.sparse.realsize"
130 )
131
132
133
134
135
136 var basicKeys = map[string]bool{
137 paxPath: true, paxLinkpath: true, paxSize: true, paxUid: true, paxGid: true,
138 paxUname: true, paxGname: true, paxMtime: true, paxAtime: true, paxCtime: true,
139 }
140
141
142
143
144
145
146
147
148 type Header struct {
149
150
151
152 Typeflag byte
153
154 Name string
155 Linkname string
156
157 Size int64
158 Mode int64
159 Uid int
160 Gid int
161 Uname string
162 Gname string
163
164
165
166
167
168
169 ModTime time.Time
170 AccessTime time.Time
171 ChangeTime time.Time
172
173 Devmajor int64
174 Devminor int64
175
176
177
178
179
180
181
182
183
184
185
186
187 Xattrs map[string]string
188
189
190
191
192
193
194
195
196
197
198
199 PAXRecords map[string]string
200
201
202
203
204
205
206
207
208
209
210 Format Format
211 }
212
213
214 type sparseEntry struct{ Offset, Length int64 }
215
216 func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 type (
251 sparseDatas []sparseEntry
252 sparseHoles []sparseEntry
253 )
254
255
256
257 func validateSparseEntries(sp []sparseEntry, size int64) bool {
258
259
260 if size < 0 {
261 return false
262 }
263 var pre sparseEntry
264 for _, cur := range sp {
265 switch {
266 case cur.Offset < 0 || cur.Length < 0:
267 return false
268 case cur.Offset > math.MaxInt64-cur.Length:
269 return false
270 case cur.endOffset() > size:
271 return false
272 case pre.endOffset() > cur.Offset:
273 return false
274 }
275 pre = cur
276 }
277 return true
278 }
279
280
281
282
283
284
285
286
287 func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
288 dst := src[:0]
289 for _, s := range src {
290 pos, end := s.Offset, s.endOffset()
291 pos += blockPadding(+pos)
292 if end != size {
293 end -= blockPadding(-end)
294 }
295 if pos < end {
296 dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})
297 }
298 }
299 return dst
300 }
301
302
303
304
305
306
307
308
309
310 func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
311 dst := src[:0]
312 var pre sparseEntry
313 for _, cur := range src {
314 if cur.Length == 0 {
315 continue
316 }
317 pre.Length = cur.Offset - pre.Offset
318 if pre.Length > 0 {
319 dst = append(dst, pre)
320 }
321 pre.Offset = cur.endOffset()
322 }
323 pre.Length = size - pre.Offset
324 return append(dst, pre)
325 }
326
327
328
329
330
331 type fileState interface {
332 logicalRemaining() int64
333 physicalRemaining() int64
334 }
335
336
337
338
339
340
341
342
343
344 func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) {
345 format = FormatUSTAR | FormatPAX | FormatGNU
346 paxHdrs = make(map[string]string)
347
348 var whyNoUSTAR, whyNoPAX, whyNoGNU string
349 var preferPAX bool
350 verifyString := func(s string, size int, name, paxKey string) {
351
352
353
354 tooLong := len(s) > size
355 allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath
356 if hasNUL(s) || (tooLong && !allowLongGNU) {
357 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s)
358 format.mustNotBe(FormatGNU)
359 }
360 if !isASCII(s) || tooLong {
361 canSplitUSTAR := paxKey == paxPath
362 if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok {
363 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s)
364 format.mustNotBe(FormatUSTAR)
365 }
366 if paxKey == paxNone {
367 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s)
368 format.mustNotBe(FormatPAX)
369 } else {
370 paxHdrs[paxKey] = s
371 }
372 }
373 if v, ok := h.PAXRecords[paxKey]; ok && v == s {
374 paxHdrs[paxKey] = v
375 }
376 }
377 verifyNumeric := func(n int64, size int, name, paxKey string) {
378 if !fitsInBase256(size, n) {
379 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n)
380 format.mustNotBe(FormatGNU)
381 }
382 if !fitsInOctal(size, n) {
383 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n)
384 format.mustNotBe(FormatUSTAR)
385 if paxKey == paxNone {
386 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n)
387 format.mustNotBe(FormatPAX)
388 } else {
389 paxHdrs[paxKey] = strconv.FormatInt(n, 10)
390 }
391 }
392 if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) {
393 paxHdrs[paxKey] = v
394 }
395 }
396 verifyTime := func(ts time.Time, size int, name, paxKey string) {
397 if ts.IsZero() {
398 return
399 }
400 if !fitsInBase256(size, ts.Unix()) {
401 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts)
402 format.mustNotBe(FormatGNU)
403 }
404 isMtime := paxKey == paxMtime
405 fitsOctal := fitsInOctal(size, ts.Unix())
406 if (isMtime && !fitsOctal) || !isMtime {
407 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts)
408 format.mustNotBe(FormatUSTAR)
409 }
410 needsNano := ts.Nanosecond() != 0
411 if !isMtime || !fitsOctal || needsNano {
412 preferPAX = true
413 if paxKey == paxNone {
414 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts)
415 format.mustNotBe(FormatPAX)
416 } else {
417 paxHdrs[paxKey] = formatPAXTime(ts)
418 }
419 }
420 if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) {
421 paxHdrs[paxKey] = v
422 }
423 }
424
425
426 var blk block
427 v7 := blk.toV7()
428 ustar := blk.toUSTAR()
429 gnu := blk.toGNU()
430 verifyString(h.Name, len(v7.name()), "Name", paxPath)
431 verifyString(h.Linkname, len(v7.linkName()), "Linkname", paxLinkpath)
432 verifyString(h.Uname, len(ustar.userName()), "Uname", paxUname)
433 verifyString(h.Gname, len(ustar.groupName()), "Gname", paxGname)
434 verifyNumeric(h.Mode, len(v7.mode()), "Mode", paxNone)
435 verifyNumeric(int64(h.Uid), len(v7.uid()), "Uid", paxUid)
436 verifyNumeric(int64(h.Gid), len(v7.gid()), "Gid", paxGid)
437 verifyNumeric(h.Size, len(v7.size()), "Size", paxSize)
438 verifyNumeric(h.Devmajor, len(ustar.devMajor()), "Devmajor", paxNone)
439 verifyNumeric(h.Devminor, len(ustar.devMinor()), "Devminor", paxNone)
440 verifyTime(h.ModTime, len(v7.modTime()), "ModTime", paxMtime)
441 verifyTime(h.AccessTime, len(gnu.accessTime()), "AccessTime", paxAtime)
442 verifyTime(h.ChangeTime, len(gnu.changeTime()), "ChangeTime", paxCtime)
443
444
445 var whyOnlyPAX, whyOnlyGNU string
446 switch h.Typeflag {
447 case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse:
448
449 if strings.HasSuffix(h.Name, "/") {
450 return FormatUnknown, nil, headerError{"filename may not have trailing slash"}
451 }
452 case TypeXHeader, TypeGNULongName, TypeGNULongLink:
453 return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"}
454 case TypeXGlobalHeader:
455 h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format}
456 if !reflect.DeepEqual(h, h2) {
457 return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"}
458 }
459 whyOnlyPAX = "only PAX supports TypeXGlobalHeader"
460 format.mayOnlyBe(FormatPAX)
461 }
462 if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
463 return FormatUnknown, nil, headerError{"negative size on header-only type"}
464 }
465
466
467 if len(h.Xattrs) > 0 {
468 for k, v := range h.Xattrs {
469 paxHdrs[paxSchilyXattr+k] = v
470 }
471 whyOnlyPAX = "only PAX supports Xattrs"
472 format.mayOnlyBe(FormatPAX)
473 }
474 if len(h.PAXRecords) > 0 {
475 for k, v := range h.PAXRecords {
476 switch _, exists := paxHdrs[k]; {
477 case exists:
478 continue
479 case h.Typeflag == TypeXGlobalHeader:
480 paxHdrs[k] = v
481 case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse):
482 paxHdrs[k] = v
483 }
484 }
485 whyOnlyPAX = "only PAX supports PAXRecords"
486 format.mayOnlyBe(FormatPAX)
487 }
488 for k, v := range paxHdrs {
489 if !validPAXRecord(k, v) {
490 return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)}
491 }
492 }
493
494
495
496
516
517
518 if wantFormat := h.Format; wantFormat != FormatUnknown {
519 if wantFormat.has(FormatPAX) && !preferPAX {
520 wantFormat.mayBe(FormatUSTAR)
521 }
522 format.mayOnlyBe(wantFormat)
523 }
524 if format == FormatUnknown {
525 switch h.Format {
526 case FormatUSTAR:
527 err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU}
528 case FormatPAX:
529 err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU}
530 case FormatGNU:
531 err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX}
532 default:
533 err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU}
534 }
535 }
536 return format, paxHdrs, err
537 }
538
539
540 func (h *Header) FileInfo() fs.FileInfo {
541 return headerFileInfo{h}
542 }
543
544
545 type headerFileInfo struct {
546 h *Header
547 }
548
549 func (fi headerFileInfo) Size() int64 { return fi.h.Size }
550 func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
551 func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
552 func (fi headerFileInfo) Sys() any { return fi.h }
553
554
555 func (fi headerFileInfo) Name() string {
556 if fi.IsDir() {
557 return path.Base(path.Clean(fi.h.Name))
558 }
559 return path.Base(fi.h.Name)
560 }
561
562
563 func (fi headerFileInfo) Mode() (mode fs.FileMode) {
564
565 mode = fs.FileMode(fi.h.Mode).Perm()
566
567
568 if fi.h.Mode&c_ISUID != 0 {
569 mode |= fs.ModeSetuid
570 }
571 if fi.h.Mode&c_ISGID != 0 {
572 mode |= fs.ModeSetgid
573 }
574 if fi.h.Mode&c_ISVTX != 0 {
575 mode |= fs.ModeSticky
576 }
577
578
579 switch m := fs.FileMode(fi.h.Mode) &^ 07777; m {
580 case c_ISDIR:
581 mode |= fs.ModeDir
582 case c_ISFIFO:
583 mode |= fs.ModeNamedPipe
584 case c_ISLNK:
585 mode |= fs.ModeSymlink
586 case c_ISBLK:
587 mode |= fs.ModeDevice
588 case c_ISCHR:
589 mode |= fs.ModeDevice
590 mode |= fs.ModeCharDevice
591 case c_ISSOCK:
592 mode |= fs.ModeSocket
593 }
594
595 switch fi.h.Typeflag {
596 case TypeSymlink:
597 mode |= fs.ModeSymlink
598 case TypeChar:
599 mode |= fs.ModeDevice
600 mode |= fs.ModeCharDevice
601 case TypeBlock:
602 mode |= fs.ModeDevice
603 case TypeDir:
604 mode |= fs.ModeDir
605 case TypeFifo:
606 mode |= fs.ModeNamedPipe
607 }
608
609 return mode
610 }
611
612 func (fi headerFileInfo) String() string {
613 return fs.FormatFileInfo(fi)
614 }
615
616
617 var sysStat func(fi fs.FileInfo, h *Header, doNameLookups bool) error
618
619 const (
620
621
622 c_ISUID = 04000
623 c_ISGID = 02000
624 c_ISVTX = 01000
625
626
627
628 c_ISDIR = 040000
629 c_ISFIFO = 010000
630 c_ISREG = 0100000
631 c_ISLNK = 0120000
632 c_ISBLK = 060000
633 c_ISCHR = 020000
634 c_ISSOCK = 0140000
635 )
636
637
638
639
640
641
642
643
644
645
646
647
648 func FileInfoHeader(fi fs.FileInfo, link string) (*Header, error) {
649 if fi == nil {
650 return nil, errors.New("archive/tar: FileInfo is nil")
651 }
652 fm := fi.Mode()
653 h := &Header{
654 Name: fi.Name(),
655 ModTime: fi.ModTime(),
656 Mode: int64(fm.Perm()),
657 }
658 switch {
659 case fm.IsRegular():
660 h.Typeflag = TypeReg
661 h.Size = fi.Size()
662 case fi.IsDir():
663 h.Typeflag = TypeDir
664 h.Name += "/"
665 case fm&fs.ModeSymlink != 0:
666 h.Typeflag = TypeSymlink
667 h.Linkname = link
668 case fm&fs.ModeDevice != 0:
669 if fm&fs.ModeCharDevice != 0 {
670 h.Typeflag = TypeChar
671 } else {
672 h.Typeflag = TypeBlock
673 }
674 case fm&fs.ModeNamedPipe != 0:
675 h.Typeflag = TypeFifo
676 case fm&fs.ModeSocket != 0:
677 return nil, fmt.Errorf("archive/tar: sockets not supported")
678 default:
679 return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
680 }
681 if fm&fs.ModeSetuid != 0 {
682 h.Mode |= c_ISUID
683 }
684 if fm&fs.ModeSetgid != 0 {
685 h.Mode |= c_ISGID
686 }
687 if fm&fs.ModeSticky != 0 {
688 h.Mode |= c_ISVTX
689 }
690
691
692 if sys, ok := fi.Sys().(*Header); ok {
693
694
695 h.Uid = sys.Uid
696 h.Gid = sys.Gid
697 h.Uname = sys.Uname
698 h.Gname = sys.Gname
699 h.AccessTime = sys.AccessTime
700 h.ChangeTime = sys.ChangeTime
701 h.Xattrs = maps.Clone(sys.Xattrs)
702 if sys.Typeflag == TypeLink {
703
704 h.Typeflag = TypeLink
705 h.Size = 0
706 h.Linkname = sys.Linkname
707 }
708 h.PAXRecords = maps.Clone(sys.PAXRecords)
709 }
710 var doNameLookups = true
711 if iface, ok := fi.(FileInfoNames); ok {
712 doNameLookups = false
713 var err error
714 h.Gname, err = iface.Gname()
715 if err != nil {
716 return nil, err
717 }
718 h.Uname, err = iface.Uname()
719 if err != nil {
720 return nil, err
721 }
722 }
723 if sysStat != nil {
724 return h, sysStat(fi, h, doNameLookups)
725 }
726 return h, nil
727 }
728
729
730
731
732 type FileInfoNames interface {
733 fs.FileInfo
734
735 Uname() (string, error)
736
737 Gname() (string, error)
738 }
739
740
741
742 func isHeaderOnlyType(flag byte) bool {
743 switch flag {
744 case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
745 return true
746 default:
747 return false
748 }
749 }
750
View as plain text