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