src/archive/tar/strconv.go GO 330 lines View on github.com → Search inside
1// Copyright 2016 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package tar67import (8	"bytes"9	"fmt"10	"strconv"11	"strings"12	"time"13)1415// hasNUL reports whether the NUL character exists within s.16func hasNUL(s string) bool {17	return strings.Contains(s, "\x00")18}1920// isASCII reports whether the input is an ASCII C-style string.21func isASCII(s string) bool {22	for _, c := range s {23		if c >= 0x80 || c == 0x00 {24			return false25		}26	}27	return true28}2930// toASCII converts the input to an ASCII C-style string.31// This is a best effort conversion, so invalid characters are dropped.32func toASCII(s string) string {33	if isASCII(s) {34		return s35	}36	b := make([]byte, 0, len(s))37	for _, c := range s {38		if c < 0x80 && c != 0x00 {39			b = append(b, byte(c))40		}41	}42	return string(b)43}4445type parser struct {46	err error // Last error seen47}4849type formatter struct {50	err error // Last error seen51}5253// parseString parses bytes as a NUL-terminated C-style string.54// If a NUL byte is not found then the whole slice is returned as a string.55func (*parser) parseString(b []byte) string {56	if i := bytes.IndexByte(b, 0); i >= 0 {57		return string(b[:i])58	}59	return string(b)60}6162// formatString copies s into b, NUL-terminating if possible.63func (f *formatter) formatString(b []byte, s string) {64	if len(s) > len(b) {65		f.err = ErrFieldTooLong66	}67	copy(b, s)68	if len(s) < len(b) {69		b[len(s)] = 070	}7172	// Some buggy readers treat regular files with a trailing slash73	// in the V7 path field as a directory even though the full path74	// recorded elsewhere (e.g., via PAX record) contains no trailing slash.75	if len(s) > len(b) && b[len(b)-1] == '/' {76		n := len(strings.TrimRight(s[:len(b)-1], "/"))77		b[n] = 0 // Replace trailing slash with NUL terminator78	}79}8081// fitsInBase256 reports whether x can be encoded into n bytes using base-25682// encoding. Unlike octal encoding, base-256 encoding does not require that the83// string ends with a NUL character. Thus, all n bytes are available for output.84//85// If operating in binary mode, this assumes strict GNU binary mode; which means86// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is87// equivalent to the sign bit in two's complement form.88func fitsInBase256(n int, x int64) bool {89	binBits := uint(n-1) * 890	return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)91}9293// parseNumeric parses the input as being encoded in either base-256 or octal.94// This function may return negative numbers.95// If parsing fails or an integer overflow occurs, err will be set.96func (p *parser) parseNumeric(b []byte) int64 {97	// Check for base-256 (binary) format first.98	// If the first bit is set, then all following bits constitute a two's99	// complement encoded number in big-endian byte order.100	if len(b) > 0 && b[0]&0x80 != 0 {101		// Handling negative numbers relies on the following identity:102		//	-a-1 == ^a103		//104		// If the number is negative, we use an inversion mask to invert the105		// data bytes and treat the value as an unsigned number.106		var inv byte // 0x00 if positive or zero, 0xff if negative107		if b[0]&0x40 != 0 {108			inv = 0xff109		}110111		var x uint64112		for i, c := range b {113			c ^= inv // Inverts c only if inv is 0xff, otherwise does nothing114			if i == 0 {115				c &= 0x7f // Ignore signal bit in first byte116			}117			if (x >> 56) > 0 {118				p.err = ErrHeader // Integer overflow119				return 0120			}121			x = x<<8 | uint64(c)122		}123		if (x >> 63) > 0 {124			p.err = ErrHeader // Integer overflow125			return 0126		}127		if inv == 0xff {128			return ^int64(x)129		}130		return int64(x)131	}132133	// Normal case is base-8 (octal) format.134	return p.parseOctal(b)135}136137// formatNumeric encodes x into b using base-8 (octal) encoding if possible.138// Otherwise it will attempt to use base-256 (binary) encoding.139func (f *formatter) formatNumeric(b []byte, x int64) {140	if fitsInOctal(len(b), x) {141		f.formatOctal(b, x)142		return143	}144145	if fitsInBase256(len(b), x) {146		for i := len(b) - 1; i >= 0; i-- {147			b[i] = byte(x)148			x >>= 8149		}150		b[0] |= 0x80 // Highest bit indicates binary format151		return152	}153154	f.formatOctal(b, 0) // Last resort, just write zero155	f.err = ErrFieldTooLong156}157158func (p *parser) parseOctal(b []byte) int64 {159	// Because unused fields are filled with NULs, we need160	// to skip leading NULs. Fields may also be padded with161	// spaces or NULs.162	// So we remove leading and trailing NULs and spaces to163	// be sure.164	b = bytes.Trim(b, " \x00")165166	if len(b) == 0 {167		return 0168	}169	x, perr := strconv.ParseUint(p.parseString(b), 8, 64)170	if perr != nil {171		p.err = ErrHeader172	}173	return int64(x)174}175176func (f *formatter) formatOctal(b []byte, x int64) {177	if !fitsInOctal(len(b), x) {178		x = 0 // Last resort, just write zero179		f.err = ErrFieldTooLong180	}181182	s := strconv.FormatInt(x, 8)183	// Add leading zeros, but leave room for a NUL.184	if n := len(b) - len(s) - 1; n > 0 {185		s = strings.Repeat("0", n) + s186	}187	f.formatString(b, s)188}189190// fitsInOctal reports whether the integer x fits in a field n-bytes long191// using octal encoding with the appropriate NUL terminator.192func fitsInOctal(n int, x int64) bool {193	octBits := uint(n-1) * 3194	return x >= 0 && (n >= 22 || x < 1<<octBits)195}196197// parsePAXTime takes a string of the form %d.%d as described in the PAX198// specification. Note that this implementation allows for negative timestamps,199// which is allowed for by the PAX specification, but not always portable.200func parsePAXTime(s string) (time.Time, error) {201	const maxNanoSecondDigits = 9202203	// Split string into seconds and sub-seconds parts.204	ss, sn, _ := strings.Cut(s, ".")205206	// Parse the seconds.207	secs, err := strconv.ParseInt(ss, 10, 64)208	if err != nil {209		return time.Time{}, ErrHeader210	}211	if len(sn) == 0 {212		return time.Unix(secs, 0), nil // No sub-second values213	}214215	// Parse the nanoseconds.216	// Initialize an array with '0's to handle right padding automatically.217	nanoDigits := [maxNanoSecondDigits]byte{'0', '0', '0', '0', '0', '0', '0', '0', '0'}218	for i := range len(sn) {219		switch c := sn[i]; {220		case c < '0' || c > '9':221			return time.Time{}, ErrHeader222		case i < len(nanoDigits):223			nanoDigits[i] = c224		}225	}226	nsecs, _ := strconv.ParseInt(string(nanoDigits[:]), 10, 64) // Must succeed after validation227	if len(ss) > 0 && ss[0] == '-' {228		return time.Unix(secs, -1*nsecs), nil // Negative correction229	}230	return time.Unix(secs, nsecs), nil231}232233// formatPAXTime converts ts into a time of the form %d.%d as described in the234// PAX specification. This function is capable of negative timestamps.235func formatPAXTime(ts time.Time) (s string) {236	secs, nsecs := ts.Unix(), ts.Nanosecond()237	if nsecs == 0 {238		return strconv.FormatInt(secs, 10)239	}240241	// If seconds is negative, then perform correction.242	sign := ""243	if secs < 0 {244		sign = "-"             // Remember sign245		secs = -(secs + 1)     // Add a second to secs246		nsecs = -(nsecs - 1e9) // Take that second away from nsecs247	}248	return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")249}250251// parsePAXRecord parses the input PAX record string into a key-value pair.252// If parsing is successful, it will slice off the currently read record and253// return the remainder as r.254func parsePAXRecord(s string) (k, v, r string, err error) {255	// The size field ends at the first space.256	nStr, rest, ok := strings.Cut(s, " ")257	if !ok {258		return "", "", s, ErrHeader259	}260261	// Parse the first token as a decimal integer.262	n, perr := strconv.ParseInt(nStr, 10, 0) // Intentionally parse as native int263	if perr != nil || n < 5 || n > int64(len(s)) {264		return "", "", s, ErrHeader265	}266	n -= int64(len(nStr) + 1) // convert from index in s to index in rest267	if n <= 0 {268		return "", "", s, ErrHeader269	}270271	// Extract everything between the space and the final newline.272	rec, nl, rem := rest[:n-1], rest[n-1:n], rest[n:]273	if nl != "\n" {274		return "", "", s, ErrHeader275	}276277	// The first equals separates the key from the value.278	k, v, ok = strings.Cut(rec, "=")279	if !ok {280		return "", "", s, ErrHeader281	}282283	if !validPAXRecord(k, v) {284		return "", "", s, ErrHeader285	}286	return k, v, rem, nil287}288289// formatPAXRecord formats a single PAX record, prefixing it with the290// appropriate length.291func formatPAXRecord(k, v string) (string, error) {292	if !validPAXRecord(k, v) {293		return "", ErrHeader294	}295296	const padding = 3 // Extra padding for ' ', '=', and '\n'297	size := len(k) + len(v) + padding298	size += len(strconv.Itoa(size))299	record := strconv.Itoa(size) + " " + k + "=" + v + "\n"300301	// Final adjustment if adding size field increased the record size.302	if len(record) != size {303		size = len(record)304		record = strconv.Itoa(size) + " " + k + "=" + v + "\n"305	}306	return record, nil307}308309// validPAXRecord reports whether the key-value pair is valid where each310// record is formatted as:311//312//	"%d %s=%s\n" % (size, key, value)313//314// Keys and values should be UTF-8, but the number of bad writers out there315// forces us to be more liberal.316// Thus, we only reject all keys with NUL, and only reject NULs in values317// for the PAX version of the USTAR string fields.318// The key must not contain an '=' character.319func validPAXRecord(k, v string) bool {320	if k == "" || strings.Contains(k, "=") {321		return false322	}323	switch k {324	case paxPath, paxLinkpath, paxUname, paxGname:325		return !hasNUL(v)326	default:327		return !hasNUL(k)328	}329}

Code quality findings 3

Ensure errors are handled or logged
warning correctness unhandled-error
if err != nil {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
b = append(b, byte(c))
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, c := range b {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.