package tar
import (
- "bytes"
"errors"
"fmt"
"os"
return h, nil
}
-func isASCII(s string) bool {
- for _, c := range s {
- if c >= 0x80 {
- return false
- }
- }
- return true
-}
-
-func toASCII(s string) string {
- if isASCII(s) {
- return s
- }
- var buf bytes.Buffer
- for _, c := range s {
- if c < 0x80 {
- buf.WriteByte(byte(c))
- }
- }
- return buf.String()
-}
-
// isHeaderOnlyType checks if the given type flag is of the type that has no
// data section even if a size is specified.
func isHeaderOnlyType(flag byte) bool {
ErrHeader = errors.New("archive/tar: invalid tar header")
)
-const maxNanoSecondIntSize = 9
-
// A Reader provides sequential access to the contents of a tar archive.
// A tar archive consists of a sequence of files.
// The Next method advances to the next file in the archive (including the first),
err error
}
-type parser struct {
- err error // Last error seen
-}
-
// A numBytesReader is an io.Reader with a numBytes method, returning the number
// of bytes remaining in the underlying encoded data.
type numBytesReader interface {
return nil
}
-// parsePAXTime takes a string of the form %d.%d as described in
-// the PAX specification.
-func parsePAXTime(t string) (time.Time, error) {
- buf := []byte(t)
- pos := bytes.IndexByte(buf, '.')
- var seconds, nanoseconds int64
- var err error
- if pos == -1 {
- seconds, err = strconv.ParseInt(t, 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- } else {
- seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- nanoBuf := string(buf[pos+1:])
- // Pad as needed before converting to a decimal.
- // For example .030 -> .030000000 -> 30000000 nanoseconds
- if len(nanoBuf) < maxNanoSecondIntSize {
- // Right pad
- nanoBuf += strings.Repeat("0", maxNanoSecondIntSize-len(nanoBuf))
- } else if len(nanoBuf) > maxNanoSecondIntSize {
- // Right truncate
- nanoBuf = nanoBuf[:maxNanoSecondIntSize]
- }
- nanoseconds, err = strconv.ParseInt(nanoBuf, 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- }
- ts := time.Unix(seconds, nanoseconds)
- return ts, nil
-}
-
// parsePAX parses PAX headers.
// If an extended header (type 'x') is invalid, ErrHeader is returned
func parsePAX(r io.Reader) (map[string]string, error) {
return headers, nil
}
-// parsePAXRecord parses the input PAX record string into a key-value pair.
-// If parsing is successful, it will slice off the currently read record and
-// return the remainder as r.
-//
-// A PAX record is of the following form:
-// "%d %s=%s\n" % (size, key, value)
-func parsePAXRecord(s string) (k, v, r string, err error) {
- // The size field ends at the first space.
- sp := strings.IndexByte(s, ' ')
- if sp == -1 {
- return "", "", s, ErrHeader
- }
-
- // Parse the first token as a decimal integer.
- n, perr := strconv.ParseInt(s[:sp], 10, 0) // Intentionally parse as native int
- if perr != nil || n < 5 || int64(len(s)) < n {
- return "", "", s, ErrHeader
- }
-
- // Extract everything between the space and the final newline.
- rec, nl, rem := s[sp+1:n-1], s[n-1:n], s[n:]
- if nl != "\n" {
- return "", "", s, ErrHeader
- }
-
- // The first equals separates the key from the value.
- eq := strings.IndexByte(rec, '=')
- if eq == -1 {
- return "", "", s, ErrHeader
- }
- return rec[:eq], rec[eq+1:], rem, nil
-}
-
-// parseString parses bytes as a NUL-terminated C-style string.
-// If a NUL byte is not found then the whole slice is returned as a string.
-func (*parser) parseString(b []byte) string {
- n := 0
- for n < len(b) && b[n] != 0 {
- n++
- }
- return string(b[0:n])
-}
-
-// parseNumeric parses the input as being encoded in either base-256 or octal.
-// This function may return negative numbers.
-// If parsing fails or an integer overflow occurs, err will be set.
-func (p *parser) parseNumeric(b []byte) int64 {
- // Check for base-256 (binary) format first.
- // If the first bit is set, then all following bits constitute a two's
- // complement encoded number in big-endian byte order.
- if len(b) > 0 && b[0]&0x80 != 0 {
- // Handling negative numbers relies on the following identity:
- // -a-1 == ^a
- //
- // If the number is negative, we use an inversion mask to invert the
- // data bytes and treat the value as an unsigned number.
- var inv byte // 0x00 if positive or zero, 0xff if negative
- if b[0]&0x40 != 0 {
- inv = 0xff
- }
-
- var x uint64
- for i, c := range b {
- c ^= inv // Inverts c only if inv is 0xff, otherwise does nothing
- if i == 0 {
- c &= 0x7f // Ignore signal bit in first byte
- }
- if (x >> 56) > 0 {
- p.err = ErrHeader // Integer overflow
- return 0
- }
- x = x<<8 | uint64(c)
- }
- if (x >> 63) > 0 {
- p.err = ErrHeader // Integer overflow
- return 0
- }
- if inv == 0xff {
- return ^int64(x)
- }
- return int64(x)
- }
-
- // Normal case is base-8 (octal) format.
- return p.parseOctal(b)
-}
-
-func (p *parser) parseOctal(b []byte) int64 {
- // Because unused fields are filled with NULs, we need
- // to skip leading NULs. Fields may also be padded with
- // spaces or NULs.
- // So we remove leading and trailing NULs and spaces to
- // be sure.
- b = bytes.Trim(b, " \x00")
-
- if len(b) == 0 {
- return 0
- }
- x, perr := strconv.ParseUint(p.parseString(b), 8, 64)
- if perr != nil {
- p.err = ErrHeader
- }
- return int64(x)
-}
-
// skipUnread skips any unread bytes in the existing file entry, as well as any
// alignment padding. It returns io.ErrUnexpectedEOF if any io.EOF is
// encountered in the data portion; it is okay to hit io.EOF in the padding.
}
}
-func TestParsePAXHeader(t *testing.T) {
- paxTests := [][3]string{
- {"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths
- {"a", "a=name", "9 a=name\n"}, // Test case involving multiple acceptable length
- {"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}}
- for _, test := range paxTests {
- key, expected, raw := test[0], test[1], test[2]
- reader := bytes.NewReader([]byte(raw))
- headers, err := parsePAX(reader)
- if err != nil {
- t.Errorf("Couldn't parse correctly formatted headers: %v", err)
- continue
- }
- if strings.EqualFold(headers[key], expected) {
- t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected)
- continue
- }
- trailer := make([]byte, 100)
- n, err := reader.Read(trailer)
- if err != io.EOF || n != 0 {
- t.Error("Buffer wasn't consumed")
- }
- }
- badHeaderTests := [][]byte{
- []byte("3 somelongkey=\n"),
- []byte("50 tooshort=\n"),
- }
- for _, test := range badHeaderTests {
- if _, err := parsePAX(bytes.NewReader(test)); err != ErrHeader {
- t.Fatal("Unexpected success when parsing bad header")
- }
- }
-}
-
-func TestParsePAXTime(t *testing.T) {
- // Some valid PAX time values
- timestamps := map[string]time.Time{
- "1350244992.023960108": time.Unix(1350244992, 23960108), // The common case
- "1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value
- "1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
- "1350244992": time.Unix(1350244992, 0), // Low precision value
- }
- for input, expected := range timestamps {
- ts, err := parsePAXTime(input)
- if err != nil {
- t.Fatal(err)
- }
- if !ts.Equal(expected) {
- t.Fatalf("Time parsing failure %s %s", ts, expected)
- }
- }
-}
-
-func TestMergePAX(t *testing.T) {
- hdr := new(Header)
- // Test a string, integer, and time based value.
- headers := map[string]string{
- "path": "a/b/c",
- "uid": "1000",
- "mtime": "1350244992.023960108",
- }
- err := mergePAX(hdr, headers)
- if err != nil {
- t.Fatal(err)
- }
- want := &Header{
- Name: "a/b/c",
- Uid: 1000,
- ModTime: time.Unix(1350244992, 23960108),
- }
- if !reflect.DeepEqual(hdr, want) {
- t.Errorf("incorrect merge: got %+v, want %+v", hdr, want)
- }
-}
-
func TestSparseFileReader(t *testing.T) {
var vectors = []struct {
realSize int64 // Real size of the output file
}
}
-func TestParsePAXRecord(t *testing.T) {
- var medName = strings.Repeat("CD", 50)
- var longName = strings.Repeat("AB", 100)
-
- var vectors = []struct {
- input string
- residual string
- outputKey string
- outputVal string
- ok bool
- }{
- {"6 k=v\n\n", "\n", "k", "v", true},
- {"19 path=/etc/hosts\n", "", "path", "/etc/hosts", true},
- {"210 path=" + longName + "\nabc", "abc", "path", longName, true},
- {"110 path=" + medName + "\n", "", "path", medName, true},
- {"9 foo=ba\n", "", "foo", "ba", true},
- {"11 foo=bar\n\x00", "\x00", "foo", "bar", true},
- {"18 foo=b=\nar=\n==\x00\n", "", "foo", "b=\nar=\n==\x00", true},
- {"27 foo=hello9 foo=ba\nworld\n", "", "foo", "hello9 foo=ba\nworld", true},
- {"27 ☺☻☹=日a本b語ç\nmeow mix", "meow mix", "☺☻☹", "日a本b語ç", true},
- {"17 \x00hello=\x00world\n", "", "\x00hello", "\x00world", true},
- {"1 k=1\n", "1 k=1\n", "", "", false},
- {"6 k~1\n", "6 k~1\n", "", "", false},
- {"6_k=1\n", "6_k=1\n", "", "", false},
- {"6 k=1 ", "6 k=1 ", "", "", false},
- {"632 k=1\n", "632 k=1\n", "", "", false},
- {"16 longkeyname=hahaha\n", "16 longkeyname=hahaha\n", "", "", false},
- {"3 somelongkey=\n", "3 somelongkey=\n", "", "", false},
- {"50 tooshort=\n", "50 tooshort=\n", "", "", false},
- }
+func TestMergePAX(t *testing.T) {
+ vectors := []struct {
+ in map[string]string
+ want *Header
+ ok bool
+ }{{
+ in: map[string]string{
+ "path": "a/b/c",
+ "uid": "1000",
+ "mtime": "1350244992.023960108",
+ },
+ want: &Header{
+ Name: "a/b/c",
+ Uid: 1000,
+ ModTime: time.Unix(1350244992, 23960108),
+ },
+ ok: true,
+ }, {
+ in: map[string]string{
+ "gid": "gtgergergersagersgers",
+ },
+ }, {
+ in: map[string]string{
+ "missing": "missing",
+ "SCHILY.xattr.key": "value",
+ },
+ want: &Header{
+ Xattrs: map[string]string{"key": "value"},
+ },
+ ok: true,
+ }}
- for _, v := range vectors {
- key, val, res, err := parsePAXRecord(v.input)
- ok := (err == nil)
- if v.ok != ok {
- if v.ok {
- t.Errorf("parsePAXRecord(%q): got parsing failure, want success", v.input)
- } else {
- t.Errorf("parsePAXRecord(%q): got parsing success, want failure", v.input)
- }
- }
- if ok && (key != v.outputKey || val != v.outputVal) {
- t.Errorf("parsePAXRecord(%q): got (%q: %q), want (%q: %q)",
- v.input, key, val, v.outputKey, v.outputVal)
+ for i, v := range vectors {
+ got := new(Header)
+ err := mergePAX(got, v.in)
+ if v.ok && !reflect.DeepEqual(*got, *v.want) {
+ t.Errorf("test %d, mergePAX(...):\ngot %+v\nwant %+v", i, *got, *v.want)
}
- if res != v.residual {
- t.Errorf("parsePAXRecord(%q): got residual %q, want residual %q",
- v.input, res, v.residual)
+ if ok := err == nil; ok != v.ok {
+ t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok)
}
}
}
-func TestParseNumeric(t *testing.T) {
- var vectors = []struct {
- input string
- output int64
- ok bool
+func TestParsePAX(t *testing.T) {
+ vectors := []struct {
+ in string
+ want map[string]string
+ ok bool
}{
- // Test base-256 (binary) encoded values.
- {"", 0, true},
- {"\x80", 0, true},
- {"\x80\x00", 0, true},
- {"\x80\x00\x00", 0, true},
- {"\xbf", (1 << 6) - 1, true},
- {"\xbf\xff", (1 << 14) - 1, true},
- {"\xbf\xff\xff", (1 << 22) - 1, true},
- {"\xff", -1, true},
- {"\xff\xff", -1, true},
- {"\xff\xff\xff", -1, true},
- {"\xc0", -1 * (1 << 6), true},
- {"\xc0\x00", -1 * (1 << 14), true},
- {"\xc0\x00\x00", -1 * (1 << 22), true},
- {"\x87\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
- {"\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
- {"\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
- {"\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
- {"\x80\x7f\xff\xff\xff\xff\xff\xff\xff", math.MaxInt64, true},
- {"\x80\x80\x00\x00\x00\x00\x00\x00\x00", 0, false},
- {"\xff\x80\x00\x00\x00\x00\x00\x00\x00", math.MinInt64, true},
- {"\xff\x7f\xff\xff\xff\xff\xff\xff\xff", 0, false},
- {"\xf5\xec\xd1\xc7\x7e\x5f\x26\x48\x81\x9f\x8f\x9b", 0, false},
-
- // Test base-8 (octal) encoded values.
- {"0000000\x00", 0, true},
- {" \x0000000\x00", 0, true},
- {" \x0000003\x00", 3, true},
- {"00000000227\x00", 0227, true},
- {"032033\x00 ", 032033, true},
- {"320330\x00 ", 0320330, true},
- {"0000660\x00 ", 0660, true},
- {"\x00 0000660\x00 ", 0660, true},
- {"0123456789abcdef", 0, false},
- {"0123456789\x00abcdef", 0, false},
- {"01234567\x0089abcdef", 342391, true},
- {"0123\x7e\x5f\x264123", 0, false},
+ {"", nil, true},
+ {"6 k=1\n", map[string]string{"k": "1"}, true},
+ {"10 a=name\n", map[string]string{"a": "name"}, true},
+ {"9 a=name\n", map[string]string{"a": "name"}, true},
+ {"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true},
+ {"3 somelongkey=\n", nil, false},
+ {"50 tooshort=\n", nil, false},
+ {"23 GNU.sparse.offset=0\n25 GNU.sparse.numbytes=1\n" +
+ "23 GNU.sparse.offset=2\n25 GNU.sparse.numbytes=3\n",
+ map[string]string{"GNU.sparse.map": "0,1,2,3"}, true},
+ {"13 key1=haha\n13 key2=nana\n13 key3=kaka\n",
+ map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true},
}
- for _, v := range vectors {
- var p parser
- num := p.parseNumeric([]byte(v.input))
- ok := (p.err == nil)
- if v.ok != ok {
- if v.ok {
- t.Errorf("parseNumeric(%q): got parsing failure, want success", v.input)
- } else {
- t.Errorf("parseNumeric(%q): got parsing success, want failure", v.input)
- }
+ for i, v := range vectors {
+ r := strings.NewReader(v.in)
+ got, err := parsePAX(r)
+ if !reflect.DeepEqual(got, v.want) && !(len(got) == 0 && len(v.want) == 0) {
+ t.Errorf("test %d, parsePAX(...):\ngot %v\nwant %v", i, got, v.want)
}
- if ok && num != v.output {
- t.Errorf("parseNumeric(%q): got %d, want %d", v.input, num, v.output)
+ if ok := err == nil; ok != v.ok {
+ t.Errorf("test %d, parsePAX(...): got %v, want %v", i, ok, v.ok)
}
}
}
--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tar
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const maxNanoSecondIntSize = 9
+
+func isASCII(s string) bool {
+ for _, c := range s {
+ if c >= 0x80 {
+ return false
+ }
+ }
+ return true
+}
+
+func toASCII(s string) string {
+ if isASCII(s) {
+ return s
+ }
+ var buf bytes.Buffer
+ for _, c := range s {
+ if c < 0x80 {
+ buf.WriteByte(byte(c))
+ }
+ }
+ return buf.String()
+}
+
+type parser struct {
+ err error // Last error seen
+}
+
+type formatter struct {
+ err error // Last error seen
+}
+
+// parseString parses bytes as a NUL-terminated C-style string.
+// If a NUL byte is not found then the whole slice is returned as a string.
+func (*parser) parseString(b []byte) string {
+ n := 0
+ for n < len(b) && b[n] != 0 {
+ n++
+ }
+ return string(b[0:n])
+}
+
+// Write s into b, terminating it with a NUL if there is room.
+func (f *formatter) formatString(b []byte, s string) {
+ if len(s) > len(b) {
+ f.err = ErrFieldTooLong
+ return
+ }
+ ascii := toASCII(s)
+ copy(b, ascii)
+ if len(ascii) < len(b) {
+ b[len(ascii)] = 0
+ }
+}
+
+// fitsInBase256 reports whether x can be encoded into n bytes using base-256
+// encoding. Unlike octal encoding, base-256 encoding does not require that the
+// string ends with a NUL character. Thus, all n bytes are available for output.
+//
+// If operating in binary mode, this assumes strict GNU binary mode; which means
+// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
+// equivalent to the sign bit in two's complement form.
+func fitsInBase256(n int, x int64) bool {
+ var binBits = uint(n-1) * 8
+ return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
+}
+
+// parseNumeric parses the input as being encoded in either base-256 or octal.
+// This function may return negative numbers.
+// If parsing fails or an integer overflow occurs, err will be set.
+func (p *parser) parseNumeric(b []byte) int64 {
+ // Check for base-256 (binary) format first.
+ // If the first bit is set, then all following bits constitute a two's
+ // complement encoded number in big-endian byte order.
+ if len(b) > 0 && b[0]&0x80 != 0 {
+ // Handling negative numbers relies on the following identity:
+ // -a-1 == ^a
+ //
+ // If the number is negative, we use an inversion mask to invert the
+ // data bytes and treat the value as an unsigned number.
+ var inv byte // 0x00 if positive or zero, 0xff if negative
+ if b[0]&0x40 != 0 {
+ inv = 0xff
+ }
+
+ var x uint64
+ for i, c := range b {
+ c ^= inv // Inverts c only if inv is 0xff, otherwise does nothing
+ if i == 0 {
+ c &= 0x7f // Ignore signal bit in first byte
+ }
+ if (x >> 56) > 0 {
+ p.err = ErrHeader // Integer overflow
+ return 0
+ }
+ x = x<<8 | uint64(c)
+ }
+ if (x >> 63) > 0 {
+ p.err = ErrHeader // Integer overflow
+ return 0
+ }
+ if inv == 0xff {
+ return ^int64(x)
+ }
+ return int64(x)
+ }
+
+ // Normal case is base-8 (octal) format.
+ return p.parseOctal(b)
+}
+
+// Write x into b, as binary (GNUtar/star extension).
+func (f *formatter) formatNumeric(b []byte, x int64) {
+ if fitsInBase256(len(b), x) {
+ for i := len(b) - 1; i >= 0; i-- {
+ b[i] = byte(x)
+ x >>= 8
+ }
+ b[0] |= 0x80 // Highest bit indicates binary format
+ return
+ }
+
+ f.formatOctal(b, 0) // Last resort, just write zero
+ f.err = ErrFieldTooLong
+}
+
+func (p *parser) parseOctal(b []byte) int64 {
+ // Because unused fields are filled with NULs, we need
+ // to skip leading NULs. Fields may also be padded with
+ // spaces or NULs.
+ // So we remove leading and trailing NULs and spaces to
+ // be sure.
+ b = bytes.Trim(b, " \x00")
+
+ if len(b) == 0 {
+ return 0
+ }
+ x, perr := strconv.ParseUint(p.parseString(b), 8, 64)
+ if perr != nil {
+ p.err = ErrHeader
+ }
+ return int64(x)
+}
+
+// Encode x as an octal ASCII string and write it into b with leading zeros.
+func (f *formatter) formatOctal(b []byte, x int64) {
+ s := strconv.FormatInt(x, 8)
+ // leading zeros, but leave room for a NUL.
+ for len(s)+1 < len(b) {
+ s = "0" + s
+ }
+ f.formatString(b, s)
+}
+
+// parsePAXTime takes a string of the form %d.%d as described in
+// the PAX specification.
+func parsePAXTime(t string) (time.Time, error) {
+ buf := []byte(t)
+ pos := bytes.IndexByte(buf, '.')
+ var seconds, nanoseconds int64
+ var err error
+ if pos == -1 {
+ seconds, err = strconv.ParseInt(t, 10, 0)
+ if err != nil {
+ return time.Time{}, err
+ }
+ } else {
+ seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0)
+ if err != nil {
+ return time.Time{}, err
+ }
+ nanoBuf := string(buf[pos+1:])
+ // Pad as needed before converting to a decimal.
+ // For example .030 -> .030000000 -> 30000000 nanoseconds
+ if len(nanoBuf) < maxNanoSecondIntSize {
+ // Right pad
+ nanoBuf += strings.Repeat("0", maxNanoSecondIntSize-len(nanoBuf))
+ } else if len(nanoBuf) > maxNanoSecondIntSize {
+ // Right truncate
+ nanoBuf = nanoBuf[:maxNanoSecondIntSize]
+ }
+ nanoseconds, err = strconv.ParseInt(nanoBuf, 10, 0)
+ if err != nil {
+ return time.Time{}, err
+ }
+ }
+ ts := time.Unix(seconds, nanoseconds)
+ return ts, nil
+}
+
+// TODO(dsnet): Implement formatPAXTime.
+
+// parsePAXRecord parses the input PAX record string into a key-value pair.
+// If parsing is successful, it will slice off the currently read record and
+// return the remainder as r.
+//
+// A PAX record is of the following form:
+// "%d %s=%s\n" % (size, key, value)
+func parsePAXRecord(s string) (k, v, r string, err error) {
+ // The size field ends at the first space.
+ sp := strings.IndexByte(s, ' ')
+ if sp == -1 {
+ return "", "", s, ErrHeader
+ }
+
+ // Parse the first token as a decimal integer.
+ n, perr := strconv.ParseInt(s[:sp], 10, 0) // Intentionally parse as native int
+ if perr != nil || n < 5 || int64(len(s)) < n {
+ return "", "", s, ErrHeader
+ }
+
+ // Extract everything between the space and the final newline.
+ rec, nl, rem := s[sp+1:n-1], s[n-1:n], s[n:]
+ if nl != "\n" {
+ return "", "", s, ErrHeader
+ }
+
+ // The first equals separates the key from the value.
+ eq := strings.IndexByte(rec, '=')
+ if eq == -1 {
+ return "", "", s, ErrHeader
+ }
+ return rec[:eq], rec[eq+1:], rem, nil
+}
+
+// formatPAXRecord formats a single PAX record, prefixing it with the
+// appropriate length.
+func formatPAXRecord(k, v string) string {
+ const padding = 3 // Extra padding for ' ', '=', and '\n'
+ size := len(k) + len(v) + padding
+ size += len(strconv.Itoa(size))
+ record := fmt.Sprintf("%d %s=%s\n", size, k, v)
+
+ // Final adjustment if adding size field increased the record size.
+ if len(record) != size {
+ size = len(record)
+ record = fmt.Sprintf("%d %s=%s\n", size, k, v)
+ }
+ return record
+}
--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tar
+
+import (
+ "math"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestFitsInBase256(t *testing.T) {
+ vectors := []struct {
+ in int64
+ width int
+ ok bool
+ }{
+ {+1, 8, true},
+ {0, 8, true},
+ {-1, 8, true},
+ {1 << 56, 8, false},
+ {(1 << 56) - 1, 8, true},
+ {-1 << 56, 8, true},
+ {(-1 << 56) - 1, 8, false},
+ {121654, 8, true},
+ {-9849849, 8, true},
+ {math.MaxInt64, 9, true},
+ {0, 9, true},
+ {math.MinInt64, 9, true},
+ {math.MaxInt64, 12, true},
+ {0, 12, true},
+ {math.MinInt64, 12, true},
+ }
+
+ for _, v := range vectors {
+ ok := fitsInBase256(v.width, v.in)
+ if ok != v.ok {
+ t.Errorf("fitsInBase256(%d, %d): got %v, want %v", v.in, v.width, ok, v.ok)
+ }
+ }
+}
+
+func TestParseNumeric(t *testing.T) {
+ vectors := []struct {
+ in string
+ want int64
+ ok bool
+ }{
+ // Test base-256 (binary) encoded values.
+ {"", 0, true},
+ {"\x80", 0, true},
+ {"\x80\x00", 0, true},
+ {"\x80\x00\x00", 0, true},
+ {"\xbf", (1 << 6) - 1, true},
+ {"\xbf\xff", (1 << 14) - 1, true},
+ {"\xbf\xff\xff", (1 << 22) - 1, true},
+ {"\xff", -1, true},
+ {"\xff\xff", -1, true},
+ {"\xff\xff\xff", -1, true},
+ {"\xc0", -1 * (1 << 6), true},
+ {"\xc0\x00", -1 * (1 << 14), true},
+ {"\xc0\x00\x00", -1 * (1 << 22), true},
+ {"\x87\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
+ {"\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
+ {"\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
+ {"\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
+ {"\x80\x7f\xff\xff\xff\xff\xff\xff\xff", math.MaxInt64, true},
+ {"\x80\x80\x00\x00\x00\x00\x00\x00\x00", 0, false},
+ {"\xff\x80\x00\x00\x00\x00\x00\x00\x00", math.MinInt64, true},
+ {"\xff\x7f\xff\xff\xff\xff\xff\xff\xff", 0, false},
+ {"\xf5\xec\xd1\xc7\x7e\x5f\x26\x48\x81\x9f\x8f\x9b", 0, false},
+
+ // Test base-8 (octal) encoded values.
+ {"0000000\x00", 0, true},
+ {" \x0000000\x00", 0, true},
+ {" \x0000003\x00", 3, true},
+ {"00000000227\x00", 0227, true},
+ {"032033\x00 ", 032033, true},
+ {"320330\x00 ", 0320330, true},
+ {"0000660\x00 ", 0660, true},
+ {"\x00 0000660\x00 ", 0660, true},
+ {"0123456789abcdef", 0, false},
+ {"0123456789\x00abcdef", 0, false},
+ {"01234567\x0089abcdef", 342391, true},
+ {"0123\x7e\x5f\x264123", 0, false},
+ }
+
+ for _, v := range vectors {
+ var p parser
+ got := p.parseNumeric([]byte(v.in))
+ ok := (p.err == nil)
+ if ok != v.ok {
+ if v.ok {
+ t.Errorf("parseNumeric(%q): got parsing failure, want success", v.in)
+ } else {
+ t.Errorf("parseNumeric(%q): got parsing success, want failure", v.in)
+ }
+ }
+ if ok && got != v.want {
+ t.Errorf("parseNumeric(%q): got %d, want %d", v.in, got, v.want)
+ }
+ }
+}
+
+func TestFormatNumeric(t *testing.T) {
+ vectors := []struct {
+ in int64
+ want string
+ ok bool
+ }{
+ // Test base-256 (binary) encoded values.
+ {-1, "\xff", true},
+ {-1, "\xff\xff", true},
+ {-1, "\xff\xff\xff", true},
+ {(1 << 0), "0", false},
+ {(1 << 8) - 1, "\x80\xff", true},
+ {(1 << 8), "0\x00", false},
+ {(1 << 16) - 1, "\x80\xff\xff", true},
+ {(1 << 16), "00\x00", false},
+ {-1 * (1 << 0), "\xff", true},
+ {-1*(1<<0) - 1, "0", false},
+ {-1 * (1 << 8), "\xff\x00", true},
+ {-1*(1<<8) - 1, "0\x00", false},
+ {-1 * (1 << 16), "\xff\x00\x00", true},
+ {-1*(1<<16) - 1, "00\x00", false},
+ {537795476381659745, "0000000\x00", false},
+ {537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true},
+ {-615126028225187231, "0000000\x00", false},
+ {-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true},
+ {math.MaxInt64, "0000000\x00", false},
+ {math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true},
+ {math.MinInt64, "0000000\x00", false},
+ {math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
+ {math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true},
+ {math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
+ }
+
+ for _, v := range vectors {
+ var f formatter
+ got := make([]byte, len(v.want))
+ f.formatNumeric(got, v.in)
+ ok := (f.err == nil)
+ if ok != v.ok {
+ if v.ok {
+ t.Errorf("formatNumeric(%d): got formatting failure, want success", v.in)
+ } else {
+ t.Errorf("formatNumeric(%d): got formatting success, want failure", v.in)
+ }
+ }
+ if string(got) != v.want {
+ t.Errorf("formatNumeric(%d): got %q, want %q", v.in, got, v.want)
+ }
+ }
+}
+
+func TestParsePAXTime(t *testing.T) {
+ timestamps := map[string]time.Time{
+ "1350244992.023960108": time.Unix(1350244992, 23960108), // The common case
+ "1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value
+ "1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
+ "1350244992": time.Unix(1350244992, 0), // Low precision value
+ }
+
+ for input, expected := range timestamps {
+ ts, err := parsePAXTime(input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !ts.Equal(expected) {
+ t.Fatalf("Time parsing failure %s %s", ts, expected)
+ }
+ }
+}
+
+func TestParsePAXRecord(t *testing.T) {
+ medName := strings.Repeat("CD", 50)
+ longName := strings.Repeat("AB", 100)
+
+ vectors := []struct {
+ in string
+ wantRes string
+ wantKey string
+ wantVal string
+ ok bool
+ }{
+ {"6 k=v\n\n", "\n", "k", "v", true},
+ {"19 path=/etc/hosts\n", "", "path", "/etc/hosts", true},
+ {"210 path=" + longName + "\nabc", "abc", "path", longName, true},
+ {"110 path=" + medName + "\n", "", "path", medName, true},
+ {"9 foo=ba\n", "", "foo", "ba", true},
+ {"11 foo=bar\n\x00", "\x00", "foo", "bar", true},
+ {"18 foo=b=\nar=\n==\x00\n", "", "foo", "b=\nar=\n==\x00", true},
+ {"27 foo=hello9 foo=ba\nworld\n", "", "foo", "hello9 foo=ba\nworld", true},
+ {"27 ☺☻☹=日a本b語ç\nmeow mix", "meow mix", "☺☻☹", "日a本b語ç", true},
+ {"17 \x00hello=\x00world\n", "", "\x00hello", "\x00world", true},
+ {"1 k=1\n", "1 k=1\n", "", "", false},
+ {"6 k~1\n", "6 k~1\n", "", "", false},
+ {"6_k=1\n", "6_k=1\n", "", "", false},
+ {"6 k=1 ", "6 k=1 ", "", "", false},
+ {"632 k=1\n", "632 k=1\n", "", "", false},
+ {"16 longkeyname=hahaha\n", "16 longkeyname=hahaha\n", "", "", false},
+ {"3 somelongkey=\n", "3 somelongkey=\n", "", "", false},
+ {"50 tooshort=\n", "50 tooshort=\n", "", "", false},
+ }
+
+ for _, v := range vectors {
+ key, val, res, err := parsePAXRecord(v.in)
+ ok := (err == nil)
+ if ok != v.ok {
+ if v.ok {
+ t.Errorf("parsePAXRecord(%q): got parsing failure, want success", v.in)
+ } else {
+ t.Errorf("parsePAXRecord(%q): got parsing success, want failure", v.in)
+ }
+ }
+ if v.ok && (key != v.wantKey || val != v.wantVal) {
+ t.Errorf("parsePAXRecord(%q): got (%q: %q), want (%q: %q)",
+ v.in, key, val, v.wantKey, v.wantVal)
+ }
+ if res != v.wantRes {
+ t.Errorf("parsePAXRecord(%q): got residual %q, want residual %q",
+ v.in, res, v.wantRes)
+ }
+ }
+}
+
+func TestFormatPAXRecord(t *testing.T) {
+ medName := strings.Repeat("CD", 50)
+ longName := strings.Repeat("AB", 100)
+
+ vectors := []struct {
+ inKey string
+ inVal string
+ want string
+ }{
+ {"k", "v", "6 k=v\n"},
+ {"path", "/etc/hosts", "19 path=/etc/hosts\n"},
+ {"path", longName, "210 path=" + longName + "\n"},
+ {"path", medName, "110 path=" + medName + "\n"},
+ {"foo", "ba", "9 foo=ba\n"},
+ {"foo", "bar", "11 foo=bar\n"},
+ {"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"},
+ {"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"},
+ {"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"},
+ {"\x00hello", "\x00world", "17 \x00hello=\x00world\n"},
+ }
+
+ for _, v := range vectors {
+ got := formatPAXRecord(v.inKey, v.inVal)
+ if got != v.want {
+ t.Errorf("formatPAXRecord(%q, %q): got %q, want %q",
+ v.inKey, v.inVal, got, v.want)
+ }
+ }
+}
paxHdrBuff block // buffer to use in writeHeader when writing a PAX header
}
-type formatter struct {
- err error // Last error seen
-}
-
// NewWriter creates a new Writer writing to w.
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
return tw.err
}
-// Write s into b, terminating it with a NUL if there is room.
-func (f *formatter) formatString(b []byte, s string) {
- if len(s) > len(b) {
- f.err = ErrFieldTooLong
- return
- }
- ascii := toASCII(s)
- copy(b, ascii)
- if len(ascii) < len(b) {
- b[len(ascii)] = 0
- }
-}
-
-// Encode x as an octal ASCII string and write it into b with leading zeros.
-func (f *formatter) formatOctal(b []byte, x int64) {
- s := strconv.FormatInt(x, 8)
- // leading zeros, but leave room for a NUL.
- for len(s)+1 < len(b) {
- s = "0" + s
- }
- f.formatString(b, s)
-}
-
-// fitsInBase256 reports whether x can be encoded into n bytes using base-256
-// encoding. Unlike octal encoding, base-256 encoding does not require that the
-// string ends with a NUL character. Thus, all n bytes are available for output.
-//
-// If operating in binary mode, this assumes strict GNU binary mode; which means
-// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
-// equivalent to the sign bit in two's complement form.
-func fitsInBase256(n int, x int64) bool {
- var binBits = uint(n-1) * 8
- return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
-}
-
-// Write x into b, as binary (GNUtar/star extension).
-func (f *formatter) formatNumeric(b []byte, x int64) {
- if fitsInBase256(len(b), x) {
- for i := len(b) - 1; i >= 0; i-- {
- b[i] = byte(x)
- x >>= 8
- }
- b[0] |= 0x80 // Highest bit indicates binary format
- return
- }
-
- f.formatOctal(b, 0) // Last resort, just write zero
- f.err = ErrFieldTooLong
-}
-
var (
minTime = time.Unix(0, 0)
// There is room for 11 octal digits (33 bits) of mtime.
return nil
}
-// formatPAXRecord formats a single PAX record, prefixing it with the
-// appropriate length.
-func formatPAXRecord(k, v string) string {
- const padding = 3 // Extra padding for ' ', '=', and '\n'
- size := len(k) + len(v) + padding
- size += len(strconv.Itoa(size))
- record := fmt.Sprintf("%d %s=%s\n", size, k, v)
-
- // Final adjustment if adding size field increased the record size.
- if len(record) != size {
- size = len(record)
- record = fmt.Sprintf("%d %s=%s\n", size, k, v)
- }
- return record
-}
-
// Write writes to the current entry in the tar archive.
// Write returns the error ErrWriteTooLong if more than
// hdr.Size bytes are written after WriteHeader.
"fmt"
"io"
"io/ioutil"
- "math"
"os"
"reflect"
"sort"
}
}
}
-
-func TestFormatPAXRecord(t *testing.T) {
- var medName = strings.Repeat("CD", 50)
- var longName = strings.Repeat("AB", 100)
-
- var vectors = []struct {
- inputKey string
- inputVal string
- output string
- }{
- {"k", "v", "6 k=v\n"},
- {"path", "/etc/hosts", "19 path=/etc/hosts\n"},
- {"path", longName, "210 path=" + longName + "\n"},
- {"path", medName, "110 path=" + medName + "\n"},
- {"foo", "ba", "9 foo=ba\n"},
- {"foo", "bar", "11 foo=bar\n"},
- {"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"},
- {"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"},
- {"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"},
- {"\x00hello", "\x00world", "17 \x00hello=\x00world\n"},
- }
-
- for _, v := range vectors {
- output := formatPAXRecord(v.inputKey, v.inputVal)
- if output != v.output {
- t.Errorf("formatPAXRecord(%q, %q): got %q, want %q",
- v.inputKey, v.inputVal, output, v.output)
- }
- }
-}
-
-func TestFitsInBase256(t *testing.T) {
- var vectors = []struct {
- input int64
- width int
- ok bool
- }{
- {+1, 8, true},
- {0, 8, true},
- {-1, 8, true},
- {1 << 56, 8, false},
- {(1 << 56) - 1, 8, true},
- {-1 << 56, 8, true},
- {(-1 << 56) - 1, 8, false},
- {121654, 8, true},
- {-9849849, 8, true},
- {math.MaxInt64, 9, true},
- {0, 9, true},
- {math.MinInt64, 9, true},
- {math.MaxInt64, 12, true},
- {0, 12, true},
- {math.MinInt64, 12, true},
- }
-
- for _, v := range vectors {
- ok := fitsInBase256(v.width, v.input)
- if ok != v.ok {
- t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok)
- }
- }
-}
-
-func TestFormatNumeric(t *testing.T) {
- var vectors = []struct {
- input int64
- output string
- ok bool
- }{
- // Test base-256 (binary) encoded values.
- {-1, "\xff", true},
- {-1, "\xff\xff", true},
- {-1, "\xff\xff\xff", true},
- {(1 << 0), "0", false},
- {(1 << 8) - 1, "\x80\xff", true},
- {(1 << 8), "0\x00", false},
- {(1 << 16) - 1, "\x80\xff\xff", true},
- {(1 << 16), "00\x00", false},
- {-1 * (1 << 0), "\xff", true},
- {-1*(1<<0) - 1, "0", false},
- {-1 * (1 << 8), "\xff\x00", true},
- {-1*(1<<8) - 1, "0\x00", false},
- {-1 * (1 << 16), "\xff\x00\x00", true},
- {-1*(1<<16) - 1, "00\x00", false},
- {537795476381659745, "0000000\x00", false},
- {537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true},
- {-615126028225187231, "0000000\x00", false},
- {-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true},
- {math.MaxInt64, "0000000\x00", false},
- {math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true},
- {math.MinInt64, "0000000\x00", false},
- {math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
- {math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true},
- {math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
- }
-
- for _, v := range vectors {
- var f formatter
- output := make([]byte, len(v.output))
- f.formatNumeric(output, v.input)
- ok := (f.err == nil)
- if ok != v.ok {
- if v.ok {
- t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input)
- } else {
- t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input)
- }
- }
- if string(output) != v.output {
- t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output)
- }
- }
-}