Files
aptly/deb/format.go

252 lines
4.8 KiB
Go

package deb
import (
"bufio"
"errors"
"io"
"strings"
"unicode"
)
// Stanza or paragraph of Debian control file
type Stanza map[string]string
// Canonical order of fields in stanza
// Taken from: http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/vivid/apt/vivid/view/head:/apt-pkg/tagfile.cc#L504
var (
canonicalOrderRelease = []string{
"Origin",
"Label",
"Archive",
"Suite",
"Version",
"Codename",
"Date",
"Architectures",
"Architecture",
"Components",
"Component",
"Description",
"MD5Sum",
"SHA1",
"SHA256",
}
canonicalOrderBinary = []string{
"Package",
"Essential",
"Status",
"Priority",
"Section",
"Installed-Size",
"Maintainer",
"Original-Maintainer",
"Architecture",
"Source",
"Version",
"Replaces",
"Provides",
"Depends",
"Pre-Depends",
"Recommends",
"Suggests",
"Conflicts",
"Breaks",
"Conffiles",
"Filename",
"Size",
"MD5Sum",
"MD5sum",
"SHA1",
"SHA256",
"Description",
}
canonicalOrderSource = []string{
"Package",
"Source",
"Binary",
"Version",
"Priority",
"Section",
"Maintainer",
"Original-Maintainer",
"Build-Depends",
"Build-Depends-Indep",
"Build-Conflicts",
"Build-Conflicts-Indep",
"Architecture",
"Standards-Version",
"Format",
"Directory",
"Files",
}
)
// Copy returns copy of Stanza
func (s Stanza) Copy() (result Stanza) {
result = make(Stanza, len(s))
for k, v := range s {
result[k] = v
}
return
}
// Write single field from Stanza to writer
func writeField(w *bufio.Writer, field, value string) (err error) {
_, multiline := multilineFields[field]
if !multiline {
_, err = w.WriteString(field + ": " + value + "\n")
} else {
if !strings.HasSuffix(value, "\n") {
value = value + "\n"
}
if field != "Description" {
value = "\n" + value
}
_, err = w.WriteString(field + ":" + value)
}
return
}
// WriteTo saves stanza back to stream, modifying itself on the fly
func (s Stanza) WriteTo(w *bufio.Writer, isSource, isRelease bool) error {
canonicalOrder := canonicalOrderBinary
if isSource {
canonicalOrder = canonicalOrderSource
}
if isRelease {
canonicalOrder = canonicalOrderRelease
}
for _, field := range canonicalOrder {
value, ok := s[field]
if ok {
delete(s, field)
err := writeField(w, field, value)
if err != nil {
return err
}
}
}
for field, value := range s {
err := writeField(w, field, value)
if err != nil {
return err
}
}
return nil
}
// Parsing errors
var (
ErrMalformedStanza = errors.New("malformed stanza syntax")
)
var multilineFields = make(map[string]bool)
func init() {
multilineFields["Description"] = true
multilineFields["Files"] = true
multilineFields["Changes"] = true
multilineFields["Checksums-Sha1"] = true
multilineFields["Checksums-Sha256"] = true
multilineFields["Package-List"] = true
multilineFields["SHA256"] = true
multilineFields["SHA1"] = true
multilineFields["MD5Sum"] = true
}
func canonicalCase(field string) string {
upper := strings.ToUpper(field)
switch upper {
case "SHA1", "SHA256", "SHA512":
return upper
case "MD5SUM":
return "MD5Sum"
case "NOTAUTOMATIC":
return "NotAutomatic"
case "BUTAUTOMATICUPGRADES":
return "ButAutomaticUpgrades"
}
startOfWord := true
return strings.Map(func(r rune) rune {
if startOfWord {
startOfWord = false
return unicode.ToUpper(r)
}
if r == '-' {
startOfWord = true
}
return unicode.ToLower(r)
}, field)
}
// ControlFileReader implements reading of control files stanza by stanza
type ControlFileReader struct {
scanner *bufio.Scanner
}
// NewControlFileReader creates ControlFileReader, it wraps with buffering
func NewControlFileReader(r io.Reader) *ControlFileReader {
return &ControlFileReader{scanner: bufio.NewScanner(bufio.NewReaderSize(r, 32768))}
}
// ReadStanza reeads one stanza from control file
func (c *ControlFileReader) ReadStanza() (Stanza, error) {
stanza := make(Stanza, 32)
lastField := ""
lastFieldMultiline := false
for c.scanner.Scan() {
line := c.scanner.Text()
// Current stanza ends with empty line
if line == "" {
if len(stanza) > 0 {
return stanza, nil
}
continue
}
if line[0] == ' ' || line[0] == '\t' {
if lastFieldMultiline {
stanza[lastField] += line + "\n"
} else {
stanza[lastField] += strings.TrimSpace(line)
}
} else {
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return nil, ErrMalformedStanza
}
lastField = canonicalCase(parts[0])
_, lastFieldMultiline = multilineFields[lastField]
if lastFieldMultiline {
stanza[lastField] = parts[1]
if parts[1] != "" {
stanza[lastField] += "\n"
}
} else {
stanza[lastField] = strings.TrimSpace(parts[1])
}
}
}
if err := c.scanner.Err(); err != nil {
return nil, err
}
if len(stanza) > 0 {
return stanza, nil
}
return nil, nil
}