Commit ef8e85e8 authored by Joe Tsai's avatar Joe Tsai Committed by Joe Tsai

archive/tar: fix parsePAXTime

Issues fixed:
* Could not handle quantity of seconds greater than 1<<31 on
32bit machines since strconv.ParseInt did not treat integers as 64b.
* Did not handle negative timestamps properly if nanoseconds were used.
Note that "-123.456" should result in a call to time.Unix(-123, -456000000).
* Incorrectly allowed a '-' right after the '.' (e.g., -123.-456)
* Did not detect invalid input after the truncation point (e.g., 123.123456789badbadbad).

Note that negative timestamps are allowed by PAX, but are not guaranteed
to be portable. See the relevant specification:
<<<
If pax encounters a file with a negative timestamp in copy or write mode,
it can reject the file, substitute a non-negative timestamp, or generate
a non-portable timestamp with a leading '-'.
>>>

Since the previous behavior already partially supported negative timestamps,
we are bound by Go's compatibility rules to keep support for them.
However, we should at least make sure we handle them properly.

Change-Id: I5686997708bfb59110ea7981175427290be737d1
Reviewed-on: https://go-review.googlesource.com/31441
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent f35b8658
...@@ -12,8 +12,6 @@ import ( ...@@ -12,8 +12,6 @@ import (
"time" "time"
) )
const maxNanoSecondIntSize = 9
func isASCII(s string) bool { func isASCII(s string) bool {
for _, c := range s { for _, c := range s {
if c >= 0x80 { if c >= 0x80 {
...@@ -165,40 +163,41 @@ func (f *formatter) formatOctal(b []byte, x int64) { ...@@ -165,40 +163,41 @@ func (f *formatter) formatOctal(b []byte, x int64) {
f.formatString(b, s) f.formatString(b, s)
} }
// parsePAXTime takes a string of the form %d.%d as described in // parsePAXTime takes a string of the form %d.%d as described in the PAX
// the PAX specification. // specification. Note that this implementation allows for negative timestamps,
func parsePAXTime(t string) (time.Time, error) { // which is allowed for by the PAX specification, but not always portable.
buf := []byte(t) func parsePAXTime(s string) (time.Time, error) {
pos := bytes.IndexByte(buf, '.') const maxNanoSecondDigits = 9
var seconds, nanoseconds int64
var err error // Split string into seconds and sub-seconds parts.
if pos == -1 { ss, sn := s, ""
seconds, err = strconv.ParseInt(t, 10, 0) if pos := strings.IndexByte(s, '.'); pos >= 0 {
if err != nil { ss, sn = s[:pos], s[pos+1:]
return time.Time{}, err }
}
// Parse the seconds.
secs, err := strconv.ParseInt(ss, 10, 64)
if err != nil {
return time.Time{}, ErrHeader
}
if len(sn) == 0 {
return time.Unix(secs, 0), nil // No sub-second values
}
// Parse the nanoseconds.
if strings.Trim(sn, "0123456789") != "" {
return time.Time{}, ErrHeader
}
if len(sn) < maxNanoSecondDigits {
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
} else { } else {
seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0) sn = sn[:maxNanoSecondDigits] // Right truncate
if err != nil { }
return time.Time{}, err nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
} if len(ss) > 0 && ss[0] == '-' {
nanoBuf := string(buf[pos+1:]) return time.Unix(secs, -1*int64(nsecs)), nil // Negative correction
// 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 time.Unix(secs, int64(nsecs)), nil
return ts, nil
} }
// TODO(dsnet): Implement formatPAXTime. // TODO(dsnet): Implement formatPAXTime.
......
...@@ -156,20 +156,82 @@ func TestFormatNumeric(t *testing.T) { ...@@ -156,20 +156,82 @@ func TestFormatNumeric(t *testing.T) {
} }
func TestParsePAXTime(t *testing.T) { func TestParsePAXTime(t *testing.T) {
timestamps := map[string]time.Time{ vectors := []struct {
"1350244992.023960108": time.Unix(1350244992, 23960108), // The common case in string
"1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value want time.Time
"1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value ok bool
"1350244992": time.Unix(1350244992, 0), // Low precision value }{
{"1350244992.023960108", time.Unix(1350244992, 23960108), true},
{"1350244992.02396010", time.Unix(1350244992, 23960100), true},
{"1350244992.0239601089", time.Unix(1350244992, 23960108), true},
{"1350244992.3", time.Unix(1350244992, 300000000), true},
{"1350244992", time.Unix(1350244992, 0), true},
{"-1.000000001", time.Unix(-1, -1e0+0e0), true},
{"-1.000001", time.Unix(-1, -1e3+0e0), true},
{"-1.001000", time.Unix(-1, -1e6+0e0), true},
{"-1", time.Unix(-1, -0e0+0e0), true},
{"-1.999000", time.Unix(-1, -1e9+1e6), true},
{"-1.999999", time.Unix(-1, -1e9+1e3), true},
{"-1.999999999", time.Unix(-1, -1e9+1e0), true},
{"0.000000001", time.Unix(0, 1e0+0e0), true},
{"0.000001", time.Unix(0, 1e3+0e0), true},
{"0.001000", time.Unix(0, 1e6+0e0), true},
{"0", time.Unix(0, 0e0), true},
{"0.999000", time.Unix(0, 1e9-1e6), true},
{"0.999999", time.Unix(0, 1e9-1e3), true},
{"0.999999999", time.Unix(0, 1e9-1e0), true},
{"1.000000001", time.Unix(+1, +1e0-0e0), true},
{"1.000001", time.Unix(+1, +1e3-0e0), true},
{"1.001000", time.Unix(+1, +1e6-0e0), true},
{"1", time.Unix(+1, +0e0-0e0), true},
{"1.999000", time.Unix(+1, +1e9-1e6), true},
{"1.999999", time.Unix(+1, +1e9-1e3), true},
{"1.999999999", time.Unix(+1, +1e9-1e0), true},
{"-1350244992.023960108", time.Unix(-1350244992, -23960108), true},
{"-1350244992.02396010", time.Unix(-1350244992, -23960100), true},
{"-1350244992.0239601089", time.Unix(-1350244992, -23960108), true},
{"-1350244992.3", time.Unix(-1350244992, -300000000), true},
{"-1350244992", time.Unix(-1350244992, 0), true},
{"", time.Time{}, false},
{"0", time.Unix(0, 0), true},
{"1.", time.Unix(1, 0), true},
{"0.0", time.Unix(0, 0), true},
{".5", time.Time{}, false},
{"-1.3", time.Unix(-1, -3e8), true},
{"-1.0", time.Unix(-1, -0e0), true},
{"-0.0", time.Unix(-0, -0e0), true},
{"-0.1", time.Unix(-0, -1e8), true},
{"-0.01", time.Unix(-0, -1e7), true},
{"-0.99", time.Unix(-0, -99e7), true},
{"-0.98", time.Unix(-0, -98e7), true},
{"-1.1", time.Unix(-1, -1e8), true},
{"-1.01", time.Unix(-1, -1e7), true},
{"-2.99", time.Unix(-2, -99e7), true},
{"-5.98", time.Unix(-5, -98e7), true},
{"-", time.Time{}, false},
{"+", time.Time{}, false},
{"-1.-1", time.Time{}, false},
{"99999999999999999999999999999999999999999999999", time.Time{}, false},
{"0.123456789abcdef", time.Time{}, false},
{"foo", time.Time{}, false},
{"\x00", time.Time{}, false},
{"𝟵𝟴𝟳𝟲𝟱.𝟰𝟯𝟮𝟭𝟬", time.Time{}, false}, // Unicode numbers (U+1D7EC to U+1D7F5)
{"98765﹒43210", time.Time{}, false}, // Unicode period (U+FE52)
} }
for input, expected := range timestamps { for _, v := range vectors {
ts, err := parsePAXTime(input) ts, err := parsePAXTime(v.in)
if err != nil { ok := (err == nil)
t.Fatal(err) if v.ok != ok {
if v.ok {
t.Errorf("parsePAXTime(%q): got parsing failure, want success", v.in)
} else {
t.Errorf("parsePAXTime(%q): got parsing success, want failure", v.in)
}
} }
if !ts.Equal(expected) { if ok && !ts.Equal(v.want) {
t.Fatalf("Time parsing failure %s %s", ts, expected) t.Errorf("parsePAXTime(%q): got (%ds %dns), want (%ds %dns)",
v.in, ts.Unix(), ts.Nanosecond(), v.want.Unix(), v.want.Nanosecond())
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment