]> Cypherpunks repositories - gostls13.git/commitdiff
archive/tar: move parse/format functionality into strconv.go
authorJoe Tsai <joetsai@digital-static.net>
Sat, 3 Sep 2016 03:15:12 +0000 (20:15 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 29 Sep 2016 18:38:28 +0000 (18:38 +0000)
Move all parse/format related functionality into strconv.go
and thoroughly test them. This also reduces the amount of noise
inside reader.go and writer.go.

There was zero functionality change other than moving code around.

Change-Id: I3bc288d10c20ebb3814b30b75d8acd7be62b85d7
Reviewed-on: https://go-review.googlesource.com/28470
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/archive/tar/common.go
src/archive/tar/reader.go
src/archive/tar/reader_test.go
src/archive/tar/strconv.go [new file with mode: 0644]
src/archive/tar/strconv_test.go [new file with mode: 0644]
src/archive/tar/writer.go
src/archive/tar/writer_test.go

index 2a1e4321826195572f685e6435edc01c929cfc54..38997fe7543d6cba55c7cf91befe9ba1f4513a34 100644 (file)
@@ -13,7 +13,6 @@
 package tar
 
 import (
-       "bytes"
        "errors"
        "fmt"
        "os"
@@ -271,28 +270,6 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
        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 {
index fa1c48adeb610ebb66b552f46638edafddaf0d81..462fb8d5d472a002e3ec91b0a3130648f20a1bf1 100644 (file)
@@ -22,8 +22,6 @@ var (
        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),
@@ -40,10 +38,6 @@ type Reader struct {
        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 {
@@ -346,42 +340,6 @@ func mergePAX(hdr *Header, headers map[string]string) (err error) {
        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) {
@@ -423,111 +381,6 @@ 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.
index 9ffc8d6459b46e2153355c8b4b4df39c0d50d9f8..15b942fafe84aeade9125c3cfc14515eca457486 100644 (file)
@@ -465,81 +465,6 @@ func TestPartialRead(t *testing.T) {
        }
 }
 
-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
@@ -1035,116 +960,78 @@ func TestReadHeaderOnly(t *testing.T) {
        }
 }
 
-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)
                }
        }
 }
diff --git a/src/archive/tar/strconv.go b/src/archive/tar/strconv.go
new file mode 100644 (file)
index 0000000..2619bcd
--- /dev/null
@@ -0,0 +1,254 @@
+// 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
+}
diff --git a/src/archive/tar/strconv_test.go b/src/archive/tar/strconv_test.go
new file mode 100644 (file)
index 0000000..7caecb5
--- /dev/null
@@ -0,0 +1,257 @@
+// 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)
+               }
+       }
+}
index 6acc055ca481e88f505ab4e9c12a5ea10c4e583b..bd6e7e5b58388c651a0cd305557271e05ad7ff74 100644 (file)
@@ -42,10 +42,6 @@ type Writer struct {
        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} }
 
@@ -71,56 +67,6 @@ func (tw *Writer) Flush() error {
        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.
@@ -340,22 +286,6 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) erro
        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.
index 27aa8e5dab67243fc7014333226761a836f70b40..a4f3f4e43deac437edbb2ada693ce193fd82b3f7 100644 (file)
@@ -9,7 +9,6 @@ import (
        "fmt"
        "io"
        "io/ioutil"
-       "math"
        "os"
        "reflect"
        "sort"
@@ -608,115 +607,3 @@ func TestSplitUSTARPath(t *testing.T) {
                }
        }
 }
-
-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)
-               }
-       }
-}