mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-29 23:31:28 +01:00 
			
		
		
		
	This updates all source files to use a new standard header for copyright and license declaration. Notably, copyright no longer includes a date, and we now use the standard SPDX-License-Identifier header. This commit was done almost entirely mechanically with perl, and then some minimal manual fixes. Updates #6865 Signed-off-by: Will Norris <will@tailscale.com>
		
			
				
	
	
		
			183 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| // Package deb extracts metadata from Debian packages.
 | |
| package deb
 | |
| 
 | |
| import (
 | |
| 	"archive/tar"
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"compress/gzip"
 | |
| 	"crypto/md5"
 | |
| 	"crypto/sha1"
 | |
| 	"crypto/sha256"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // Info is the Debian package metadata needed to integrate the package
 | |
| // into a repository.
 | |
| type Info struct {
 | |
| 	// Version is the version of the package, as reported by dpkg.
 | |
| 	Version string
 | |
| 	// Arch is the Debian CPU architecture the package is for.
 | |
| 	Arch string
 | |
| 	// Control is the entire contents of the package's control file,
 | |
| 	// with leading and trailing whitespace removed.
 | |
| 	Control []byte
 | |
| 	// MD5 is the MD5 hash of the package file.
 | |
| 	MD5 []byte
 | |
| 	// SHA1 is the SHA1 hash of the package file.
 | |
| 	SHA1 []byte
 | |
| 	// SHA256 is the SHA256 hash of the package file.
 | |
| 	SHA256 []byte
 | |
| }
 | |
| 
 | |
| // ReadFile returns Debian package metadata from the .deb file at path.
 | |
| func ReadFile(path string) (*Info, error) {
 | |
| 	f, err := os.Open(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return Read(f)
 | |
| }
 | |
| 
 | |
| // Read returns Debian package metadata from the .deb file in r.
 | |
| func Read(r io.Reader) (*Info, error) {
 | |
| 	b := bufio.NewReader(r)
 | |
| 
 | |
| 	m5, s1, s256 := md5.New(), sha1.New(), sha256.New()
 | |
| 	summers := io.MultiWriter(m5, s1, s256)
 | |
| 	r = io.TeeReader(b, summers)
 | |
| 
 | |
| 	t, err := findControlTar(r)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("searching for control.tar.gz: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	control, err := findControlFile(t)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("searching for control file in control.tar.gz: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	arch, version, err := findArchAndVersion(control)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("extracting version and architecture from control file: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Exhaust the remainder of r, so that the summers see the entire file.
 | |
| 	if _, err := io.Copy(io.Discard, r); err != nil {
 | |
| 		return nil, fmt.Errorf("hashing file: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return &Info{
 | |
| 		Version: version,
 | |
| 		Arch:    arch,
 | |
| 		Control: control,
 | |
| 		MD5:     m5.Sum(nil),
 | |
| 		SHA1:    s1.Sum(nil),
 | |
| 		SHA256:  s256.Sum(nil),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // findControlTar reads r as an `ar` archive, finds a tarball named
 | |
| // `control.tar.gz` within, and returns a reader for that file.
 | |
| func findControlTar(r io.Reader) (tarReader io.Reader, err error) {
 | |
| 	var magic [8]byte
 | |
| 	if _, err := io.ReadFull(r, magic[:]); err != nil {
 | |
| 		return nil, fmt.Errorf("reading ar magic: %w", err)
 | |
| 	}
 | |
| 	if string(magic[:]) != "!<arch>\n" {
 | |
| 		return nil, fmt.Errorf("not an ar file (bad magic %q)", magic)
 | |
| 	}
 | |
| 
 | |
| 	for {
 | |
| 		var hdr [60]byte
 | |
| 		if _, err := io.ReadFull(r, hdr[:]); err != nil {
 | |
| 			return nil, fmt.Errorf("reading file header: %w", err)
 | |
| 		}
 | |
| 		filename := strings.TrimSpace(string(hdr[:16]))
 | |
| 		size, err := strconv.ParseInt(strings.TrimSpace(string(hdr[48:58])), 10, 64)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("reading size of file %q: %w", filename, err)
 | |
| 		}
 | |
| 		if filename == "control.tar.gz" {
 | |
| 			return io.LimitReader(r, size), nil
 | |
| 		}
 | |
| 
 | |
| 		// files in ar are padded out to 2 bytes.
 | |
| 		if size%2 == 1 {
 | |
| 			size++
 | |
| 		}
 | |
| 		if _, err := io.CopyN(io.Discard, r, size); err != nil {
 | |
| 			return nil, fmt.Errorf("seeking past file %q: %w", filename, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // findControlFile reads r as a tar.gz archive, finds a file named
 | |
| // `control` within, and returns its contents.
 | |
| func findControlFile(r io.Reader) (control []byte, err error) {
 | |
| 	gz, err := gzip.NewReader(r)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("decompressing control.tar.gz: %w", err)
 | |
| 	}
 | |
| 	defer gz.Close()
 | |
| 
 | |
| 	tr := tar.NewReader(gz)
 | |
| 	for {
 | |
| 		hdr, err := tr.Next()
 | |
| 		if err != nil {
 | |
| 			if errors.Is(err, io.EOF) {
 | |
| 				return nil, errors.New("EOF while looking for control file in control.tar.gz")
 | |
| 			}
 | |
| 			return nil, fmt.Errorf("reading tar header: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if filepath.Clean(hdr.Name) != "control" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Found control file
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	bs, err := io.ReadAll(tr)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("reading control file: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return bytes.TrimSpace(bs), nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	archKey    = []byte("Architecture:")
 | |
| 	versionKey = []byte("Version:")
 | |
| )
 | |
| 
 | |
| // findArchAndVersion extracts the architecture and version strings
 | |
| // from the given control file.
 | |
| func findArchAndVersion(control []byte) (arch string, version string, err error) {
 | |
| 	b := bytes.NewBuffer(control)
 | |
| 	for {
 | |
| 		l, err := b.ReadBytes('\n')
 | |
| 		if err != nil {
 | |
| 			return "", "", err
 | |
| 		}
 | |
| 		if bytes.HasPrefix(l, archKey) {
 | |
| 			arch = string(bytes.TrimSpace(l[len(archKey):]))
 | |
| 		} else if bytes.HasPrefix(l, versionKey) {
 | |
| 			version = string(bytes.TrimSpace(l[len(versionKey):]))
 | |
| 		}
 | |
| 		if arch != "" && version != "" {
 | |
| 			return arch, version, nil
 | |
| 		}
 | |
| 	}
 | |
| }
 |