pax_global_header00006660000000000000000000000064145201726600014515gustar00rootroot0000000000000052 comment=246d63d4a8289011dff5db890058a7d9403f785a go-debian-0.16.0/000077500000000000000000000000001452017266000134265ustar00rootroot00000000000000go-debian-0.16.0/.gitignore000066400000000000000000000000051452017266000154110ustar00rootroot00000000000000*swp go-debian-0.16.0/.travis.yml000066400000000000000000000002161452017266000155360ustar00rootroot00000000000000language: go go_import_path: pault.ag/go/debian go: - "1.15.x" - "1.14.x" - "1.13.x" - "1.12.x" - "1.11.x" - "1.10.x" - "1.9.x" go-debian-0.16.0/AUTHORS000066400000000000000000000006341452017266000145010ustar00rootroot00000000000000Paul Tagliamonte Tianon Gravi Michael Stapelberg Tomáš Ebenlendr Hannes Lindström Aron Atkins aviau Jakub Wilk jonyoder Michael Cross trestletech go-debian-0.16.0/LICENSE000066400000000000000000000051021452017266000144310ustar00rootroot00000000000000Copyright (c) Paul R. Tagliamonte , 2015-2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Version module ============== Copyright © 2012 Michael Stapelberg and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Michael Stapelberg nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-debian-0.16.0/README.md000066400000000000000000000004071452017266000147060ustar00rootroot00000000000000go-debian ========= `go-debian` is a Debian Toolbelt for Go hackers! This package contains a bunch of helpful routines to help work with fun bits of Debian data. [![GoDoc](https://godoc.org/pault.ag/go/debian?status.svg)](https://godoc.org/pault.ag/go/debian) go-debian-0.16.0/changelog/000077500000000000000000000000001452017266000153555ustar00rootroot00000000000000go-debian-0.16.0/changelog/changelog.go000066400000000000000000000106501452017266000176350ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package changelog // import "pault.ag/go/debian/changelog" import ( "bufio" "fmt" "io" "os" "strings" "time" "pault.ag/go/debian/version" ) // A ChangelogEntry is the encapsulation for each entry for a given version // in a series of uploads. type ChangelogEntry struct { Source string Version version.Version Target string Arguments map[string]string Changelog string ChangedBy string When time.Time } const whenLayout = time.RFC1123Z // "Mon, 02 Jan 2006 15:04:05 -0700" type ChangelogEntries []ChangelogEntry func trim(line string) string { return strings.Trim(line, "\n\r\t ") } func partition(line, delim string) (string, string) { entries := strings.SplitN(line, delim, 2) if len(entries) != 2 { return line, "" } return entries[0], entries[1] } func ParseOne(reader *bufio.Reader) (*ChangelogEntry, error) { changeLog := ChangelogEntry{} var header string for { line, err := reader.ReadString('\n') if err != nil { return nil, err } if line == "\n" { continue } if !strings.HasPrefix(line, " ") { /* Great. Let's work with this. */ header = line break } else { return nil, fmt.Errorf("Unexpected line: %s", line) } } /* OK, so, we have a header. Let's run with it * hello (2.10-1) unstable; urgency=low */ arguments, options := partition(header, ";") /* Arguments: hello (2.10-1) unstable * Options: urgency=low, other=bar */ source, remainder := partition(arguments, "(") versionString, suite := partition(remainder, ")") var err error changeLog.Source = trim(source) changeLog.Version, err = version.Parse(trim(versionString)) if err != nil { return nil, err } changeLog.Target = trim(suite) changeLog.Arguments = map[string]string{} for _, entry := range strings.Split(options, ",") { key, value := partition(trim(entry), "=") changeLog.Arguments[trim(key)] = trim(value) } var signoff string /* OK, we've got the header. Let's zip down. */ for { line, err := reader.ReadString('\n') if err != nil { return nil, err } if !strings.HasPrefix(line, " ") && trim(line) != "" { return nil, fmt.Errorf("Error! Didn't get ending line!") } if strings.HasPrefix(line, " -- ") { signoff = line break } changeLog.Changelog = changeLog.Changelog + line } /* Right, so we have a signoff line */ _, signoff = partition(signoff, "--") /* Get rid of the leading " -- " */ whom, when := partition(signoff, " ") /* Split on the " " */ changeLog.ChangedBy = trim(whom) changeLog.When, err = time.Parse(whenLayout, trim(when)) if err != nil { return nil, fmt.Errorf("Failed parsing When %q: %v", when, err) } return &changeLog, nil } func ParseFileOne(path string) (*ChangelogEntry, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return ParseOne(bufio.NewReader(f)) } func Parse(reader io.Reader) (ChangelogEntries, error) { stream := bufio.NewReader(reader) ret := ChangelogEntries{} for { entry, err := ParseOne(stream) if err == io.EOF { break } if err != nil { return ChangelogEntries{}, err } ret = append(ret, *entry) } return ret, nil } func ParseFile(path string) (ChangelogEntries, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return Parse(bufio.NewReader(f)) } // vim: foldmethod=marker go-debian-0.16.0/changelog/changelog_test.go000066400000000000000000000060541452017266000206770ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package changelog_test import ( "bufio" "io" "log" "strings" "testing" "pault.ag/go/debian/changelog" ) /* * */ func isok(t *testing.T, err error) { if err != nil && err != io.EOF { log.Printf("Error! Error is not nil! %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ // {{{ test changelog entry var changeLog = `hello (2.10-1) unstable; urgency=low * New upstream release. * debian/patches: Drop 01-fix-i18n-of-default-message, no longer needed. * debian/patches: Drop 99-config-guess-config-sub, no longer needed. * debian/rules: Drop override_dh_auto_build hack, no longer needed. * Standards-Version: 3.9.6 (no changes for this). -- Santiago Vila Sun, 22 Mar 2015 11:56:00 +0100 hello (2.9-2) unstable; urgency=low * Apply patch from Reuben Thomas to fix i18n of default message. This is upstream commit c4aed00. Closes: #767172. * The previous change in src/hello.c trigger a rebuild of man/hello.1 that we don't need. Add a "touch man/hello.1" to avoid it. * Use Breaks: hello-debhelper (<< 2.9), not Conflicts, as hello-debhelper is deprecated. * Restore simple watch file from old hello package that was lost when the packages were renamed. * Update 99-config-guess-config-sub patch. -- Santiago Vila Thu, 06 Nov 2014 12:03:40 +0100 ` // }}} func TestChangelogEntry(t *testing.T) { changeLog, err := changelog.ParseOne(bufio.NewReader(strings.NewReader(changeLog))) isok(t, err) assert(t, changeLog.ChangedBy == "Santiago Vila ") } func TestChangelogEntries(t *testing.T) { changeLogs, err := changelog.Parse(strings.NewReader(changeLog)) isok(t, err) assert(t, len(changeLogs) == 2) } // vim: foldmethod=marker go-debian-0.16.0/changelog/doc.go000066400000000000000000000001461452017266000164520ustar00rootroot00000000000000/* Parse the Debian changelog format. */ package changelog // import "pault.ag/go/debian/changelog" go-debian-0.16.0/control/000077500000000000000000000000001452017266000151065ustar00rootroot00000000000000go-debian-0.16.0/control/changes.go000066400000000000000000000170431452017266000170520ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bufio" "fmt" "os" "path" "path/filepath" "strconv" "strings" "pault.ag/go/debian/dependency" "pault.ag/go/debian/internal" "pault.ag/go/debian/version" ) // {{{ .changes Files list entries type FileListChangesFileHash struct { FileHash Component string Priority string } func (c *FileListChangesFileHash) UnmarshalControl(data string) error { var err error c.Algorithm = "md5" vals := strings.Split(data, " ") if len(vals) < 5 { return fmt.Errorf("Error: Unknown File List Hash line: '%s'", data) } c.Hash = vals[0] c.Size, err = strconv.ParseInt(vals[1], 10, 64) if err != nil { return err } c.Component = vals[2] c.Priority = vals[3] c.Filename = vals[4] return nil } // }}} // The Changes struct is the default encapsulation of the Debian .changes // package filetype.This struct contains an anonymous member of type Paragraph, // allowing you to use the standard .Values and .Order of the Paragraph type. // // The .changes files are used by the Debian archive maintenance software to // process updates to packages. They consist of a single paragraph, possibly // surrounded by a PGP signature. That paragraph contains information from the // debian/control file and other data about the source package gathered via // debian/changelog and debian/rules. type Changes struct { Paragraph Filename string Format string Source string Binaries []string `control:"Binary" delim:" "` Architectures []dependency.Arch `control:"Architecture"` Version version.Version Origin string Distribution string Urgency string Maintainer string ChangedBy string `control:"Changed-By"` Closes []string Changes string ChecksumsSha1 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Files []FileListChangesFileHash `control:"Files" delim:"\n" strip:"\n\r\t "` } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new Changes struct, unless error is set to a value // other than nil. func ParseChangesFile(path string) (ret *Changes, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return ParseChanges(bufio.NewReader(f), path) } // Given a bufio.Reader, consume the Reader, and return a Changes object // for use. The "path" argument is used to set Changes.Filename, which // is used by Changes.GetDSC, Changes.Remove, Changes.Move and Changes.Copy to // figure out where all the files on the filesystem are. This value can be set // to something invalid if you're not using those functions. func ParseChanges(reader *bufio.Reader, path string) (*Changes, error) { ret := &Changes{Filename: path} return ret, Unmarshal(ret, reader) } // Return a list of FileListChangesFileHash entries from the `changes.Files` // entry, with the exception that each `Filename` will be joined to the root // directory of the Changes file. func (changes *Changes) AbsFiles() []FileListChangesFileHash { ret := []FileListChangesFileHash{} baseDir := filepath.Dir(changes.Filename) for _, hash := range changes.Files { hash.Filename = path.Join(baseDir, hash.Filename) ret = append(ret, hash) } return ret } // Return a DSC struct for the DSC listed in the .changes file. This requires // Changes.Filename to be correctly set, and for the .dsc file to exist // in the correct place next to the .changes. // // This function may also return an error if the given .changes does not // include the .dsc (binary-only upload) func (changes *Changes) GetDSC() (*DSC, error) { for _, file := range changes.Files { if strings.HasSuffix(file.Filename, ".dsc") { // Right, now lets resolve the absolute path. baseDir := filepath.Dir(changes.Filename) dsc, err := ParseDscFile(baseDir + "/" + file.Filename) if err != nil { return nil, err } return dsc, nil } } return nil, fmt.Errorf("No .dsc file in .changes") } // Copy the .changes file and all referenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .changes last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate Changes.Filename to match the new location. func (changes *Changes) Copy(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .changes to a non-directory") } for _, file := range changes.AbsFiles() { dirname := filepath.Base(file.Filename) err := internal.Copy(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(changes.Filename) err := internal.Copy(changes.Filename, dest+"/"+dirname) changes.Filename = dest + "/" + dirname return err } // Move the .changes file and all referenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .changes last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate Changes.Filename to match the new location. func (changes *Changes) Move(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .changes to a non-directory") } for _, file := range changes.AbsFiles() { dirname := filepath.Base(file.Filename) err := os.Rename(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(changes.Filename) err := os.Rename(changes.Filename, dest+"/"+dirname) changes.Filename = dest + "/" + dirname return err } // Remove the .changes file and any associated files. This function will // always remove the .changes last, in the event there are filesystem i/o errors // on removing associated files. func (changes *Changes) Remove() error { for _, file := range changes.AbsFiles() { err := os.Remove(file.Filename) if err != nil { return err } } return os.Remove(changes.Filename) } // vim: foldmethod=marker go-debian-0.16.0/control/changes_test.go000066400000000000000000000115611452017266000201100ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestChangesParse(t *testing.T) { // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`Format: 1.8 Date: Wed, 29 Apr 2015 21:29:13 -0400 Source: dput-ng Binary: dput-ng python-dput dput-ng-doc Architecture: source Version: 1.9 Distribution: unstable Urgency: medium Maintainer: dput-ng Maintainers Changed-By: Paul Tagliamonte Description: dput-ng - next generation Debian package upload tool dput-ng-doc - next generation Debian package upload tool (documentation) python-dput - next generation Debian package upload tool (Python library) Closes: 783746 Changes: dput-ng (1.9) unstable; urgency=medium . * The "< jessfraz> ya!!!! jessie FTW" release . [ Sebastian Ramacher ] * Remove obsolete conffile /etc/dput.d/profiles/backports.json. Thanks to Jakub Wilk for spotting the obsolete conffile. . [ Luca Falavigna ] * Add support for Deb-o-Matic binnmu command. . [ Tristan Seligmann ] * Add jessie-backports to list of Debian codenames (closes: #783746). Checksums-Sha1: cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc 77e97879793f57ee93cb3d59d8c5d16361f75b37 82504 dput-ng_1.9.tar.xz Checksums-Sha256: 2489ed1a2e052ccc4c321719a2394ac4b6958209f05b1531305d2a52173aa5c1 1131 dput-ng_1.9.dsc 5ef401d9b67b009443f249aa79b952839c69a2b5437fbe957832599b655e1df0 82504 dput-ng_1.9.tar.xz Files: a74c9e3e9fe05d480d24cd43b225ee0c 1131 devel extra dput-ng_1.9.dsc 67e67e85a267c0c8110001b1a6cfc293 82504 devel extra dput-ng_1.9.tar.xz `)) // }}} changes, err := control.ParseChanges(reader, "") isok(t, err) assert(t, changes.Format == "1.8") assert(t, changes.ChangedBy == "Paul Tagliamonte ") assert(t, len(changes.Binaries) == 3) assert(t, changes.Binaries[2] == "dput-ng-doc") assert(t, len(changes.Closes) == 1) assert(t, changes.Closes[0] == "783746") } func TestChangesParseFiles(t *testing.T) { // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`Format: 1.8 Date: Wed, 29 Apr 2015 21:29:13 -0400 Source: dput-ng Binary: dput-ng python-dput dput-ng-doc Architecture: source Version: 1.9 Distribution: unstable Urgency: medium Maintainer: dput-ng Maintainers Changed-By: Paul Tagliamonte Description: dput-ng - next generation Debian package upload tool dput-ng-doc - next generation Debian package upload tool (documentation) python-dput - next generation Debian package upload tool (Python library) Closes: 783746 Changes: dput-ng (1.9) unstable; urgency=medium . * The "< jessfraz> ya!!!! jessie FTW" release . [ Sebastian Ramacher ] * Remove obsolete conffile /etc/dput.d/profiles/backports.json. Thanks to Jakub Wilk for spotting the obsolete conffile. . [ Luca Falavigna ] * Add support for Deb-o-Matic binnmu command. . [ Tristan Seligmann ] * Add jessie-backports to list of Debian codenames (closes: #783746). Checksums-Sha1: cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc 77e97879793f57ee93cb3d59d8c5d16361f75b37 82504 dput-ng_1.9.tar.xz Checksums-Sha256: 2489ed1a2e052ccc4c321719a2394ac4b6958209f05b1531305d2a52173aa5c1 1131 dput-ng_1.9.dsc 5ef401d9b67b009443f249aa79b952839c69a2b5437fbe957832599b655e1df0 82504 dput-ng_1.9.tar.xz Files: a74c9e3e9fe05d480d24cd43b225ee0c 1131 devel extra dput-ng_1.9.dsc 67e67e85a267c0c8110001b1a6cfc293 82504 devel extra dput-ng_1.9.tar.xz `)) // }}} changes, err := control.ParseChanges(reader, "") isok(t, err) assert(t, len(changes.ChecksumsSha1) == 2) assert(t, len(changes.ChecksumsSha256) == 2) assert(t, len(changes.Files) == 2) } // vim: foldmethod=marker go-debian-0.16.0/control/control.go000066400000000000000000000116351452017266000171230ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bufio" "fmt" "os" "path/filepath" "pault.ag/go/debian/dependency" ) // Encapsulation for a debian/control file, which is a series of RFC2822-like // blocks, starting with a Source control paragraph, and then a series of // Binary control paragraphs. // // The debian/control file contains the most vital (and version-independent) // information about the source package and about the binary packages it // creates. // // The first paragraph of the control file contains information about the source // package in general. The subsequent sets each describe a binary package that // the source tree builds. type Control struct { Filename string Source SourceParagraph Binaries []BinaryParagraph } // Encapsulation for a debian/control Source entry. This contains information // that will wind up in the .dsc and friends. Really quite fun! type SourceParagraph struct { Paragraph Maintainer string Uploaders []string `delim:","` Source string Priority string Section string Description string BuildDepends dependency.Dependency `control:"Build-Depends"` BuildDependsIndep dependency.Dependency `control:"Build-Depends-Indep"` BuildConflicts dependency.Dependency `control:"Build-Conflicts"` BuildConflictsIndep dependency.Dependency `control:"Build-Conflicts-Indep"` } // Return a list of all entities that are responsible for the package's // well being. The 0th element is always the package's Maintainer, // with any Uploaders following. func (s *SourceParagraph) Maintainers() []string { return append([]string{s.Maintainer}, s.Uploaders...) } // Encapsulation for a debian/control Binary control entry. This contains // information that will be eventually put lovingly into the .deb file // after it's built on a given Arch. type BinaryParagraph struct { Paragraph Architectures []dependency.Arch `control:"Architecture"` Package string Priority string Section string Essential bool Description string Conffiles []MD5FileHash `delim:"\n" strip:"\n\r\t "` Depends dependency.Dependency Recommends dependency.Dependency Suggests dependency.Dependency Enhances dependency.Dependency PreDepends dependency.Dependency `control:"Pre-Depends"` Breaks dependency.Dependency Conflicts dependency.Dependency Replaces dependency.Dependency BuiltUsing dependency.Dependency `control:"Built-Using"` } func (para *Paragraph) getDependencyField(field string) (*dependency.Dependency, error) { if val, ok := para.Values[field]; ok { return dependency.Parse(val) } return nil, fmt.Errorf("Field `%s' Missing", field) } func (para *Paragraph) getOptionalDependencyField(field string) dependency.Dependency { val := para.Values[field] dep, err := dependency.Parse(val) if err != nil { return dependency.Dependency{} } return *dep } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new Control struct, unless error is set to a value // other than nil. func ParseControlFile(path string) (ret *Control, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() ret, err = ParseControl(bufio.NewReader(f), path) if err != nil { return nil, err } return ret, nil } // Given a bufio.Reader, consume the Reader, and return a Control object // for use. func ParseControl(reader *bufio.Reader, path string) (*Control, error) { ret := Control{ Filename: path, Binaries: []BinaryParagraph{}, Source: SourceParagraph{}, } if err := Unmarshal(&ret.Source, reader); err != nil { return nil, err } if err := Unmarshal(&ret.Binaries, reader); err != nil { return nil, err } return &ret, nil } // vim: foldmethod=marker go-debian-0.16.0/control/control_test.go000066400000000000000000000136771452017266000201720ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestDependencyControlParse(t *testing.T) { // Test Control {{{ reader := bufio.NewReader(strings.NewReader(`Source: fbautostart Section: misc Priority: optional Maintainer: Paul Tagliamonte Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Homepage: https://launchpad.net/fbautostart Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Package: fbautostart Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. `)) // }}} c, err := control.ParseControl(reader, "") isok(t, err) assert(t, c != nil) assert(t, len(c.Binaries) == 1) assert(t, c.Source.Maintainer == "Paul Tagliamonte ") assert(t, c.Source.Source == "fbautostart") depends := c.Source.BuildDepends assert(t, len(c.Source.Maintainers()) == 1) assert(t, len(c.Source.Uploaders) == 0) assert(t, len(c.Source.BuildDepends.Relations) == 1) assert(t, len(c.Source.BuildDependsIndep.Relations) == 0) assert(t, len(c.Source.BuildConflicts.Relations) == 0) assert(t, len(c.Source.BuildConflictsIndep.Relations) == 0) assert(t, depends.Relations[0].Possibilities[0].Name == "debhelper") assert(t, depends.Relations[0].Possibilities[0].Version.Number == "9") assert(t, depends.Relations[0].Possibilities[0].Version.Operator == ">=") assert(t, len(c.Binaries[0].Architectures) == 1) assert(t, c.Binaries[0].Architectures[0].CPU == "any") assert(t, c.Binaries[0].Package == "fbautostart") } func TestMaintainersParse(t *testing.T) { // Test Control {{{ reader := bufio.NewReader(strings.NewReader(`Source: fbautostart Section: misc Priority: optional Maintainer: Paul Tagliamonte Uploaders: John Doe , Foo Bar Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Homepage: https://launchpad.net/fbautostart Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Package: fbautostart Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, test Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. Package: fbautostart-foo Architecture: amd64 sparc kfreebsd-any Depends: ${shlibs:Depends}, ${misc:Depends}, test Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. `)) // }}} c, err := control.ParseControl(reader, "") isok(t, err) assert(t, c != nil) assert(t, len(c.Binaries) == 2) assert(t, len(c.Source.Maintainers()) == 3) arches := c.Binaries[1].Architectures assert(t, len(arches) == 3) } func TestConffilesControlParse(t *testing.T) { // Test Control {{{ reader := bufio.NewReader(strings.NewReader(`Source: fbautostart Section: misc Priority: optional Maintainer: Paul Tagliamonte Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Homepage: https://launchpad.net/fbautostart Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Package: fbautostart Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Conffiles: /etc/misc/foo c95db2aeebde025e0fa7f60a25587efe /etc/misc/bar db44b9cbb80456bc68c1225ee9e38fcb Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. `)) // }}} c, err := control.ParseControl(reader, "") isok(t, err) assert(t, c != nil) assert(t, len(c.Binaries) == 1) assert(t, len(c.Binaries[0].Conffiles) == 2) assert(t, c.Binaries[0].Conffiles[0].Algorithm == "md5") assert(t, c.Binaries[0].Conffiles[0].Filename == "/etc/misc/foo") assert(t, c.Binaries[0].Conffiles[0].Hash == "c95db2aeebde025e0fa7f60a25587efe") assert(t, c.Binaries[0].Conffiles[1].Algorithm == "md5") assert(t, c.Binaries[0].Conffiles[1].Filename == "/etc/misc/bar") assert(t, c.Binaries[0].Conffiles[1].Hash == "db44b9cbb80456bc68c1225ee9e38fcb") } // vim: foldmethod=marker go-debian-0.16.0/control/decode.go000066400000000000000000000211571452017266000166660ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "fmt" "io" "reflect" "strconv" "strings" "golang.org/x/crypto/openpgp" ) // Unmarshallable {{{ // The Unmarshallable interface defines the interface that Unmarshal will use // to do custom unpacks into Structs. // // The argument passed in will be a string that contains the value of the // RFC822 key this object relates to. type Unmarshallable interface { UnmarshalControl(data string) error } // }}} // Unmarshal {{{ // Given a struct (or list of structs), read the io.Reader RFC822-alike // Debian control-file stream into the struct, unpacking keys into the // struct as needed. If a list of structs is given, unpack all RFC822 // Paragraphs into the structs. // // This code will attempt to unpack it into the struct based on the // literal name of the key, compared byte-for-byte. If this is not // OK, the struct tag `control:""` can be used to define the key to use // in the RFC822 stream. // // If you're unpacking into a list of strings, you have the option of defining // a string to split tokens on (`delim:", "`), and things to strip off each // element (`strip:"\n\r\t "`). // // If you're unpacking into a struct, the struct will be walked according to // the rules above. If you wish to override how this writes to the nested // struct, objects that implement the Unmarshallable interface will be // Unmarshaled via that method call only. // // Structs that contain Paragraph as an Anonymous member will have that // member populated with the parsed RFC822 block, to allow access to the // .Values and .Order members. func Unmarshal(data interface{}, reader io.Reader) error { decoder, err := NewDecoder(reader, nil) if err != nil { return err } return decoder.Decode(data) } // }}} // Decoder {{{ type Decoder struct { paragraphReader ParagraphReader } // NewDecoder {{{ func NewDecoder(reader io.Reader, keyring *openpgp.EntityList) (*Decoder, error) { ret := Decoder{} pr, err := NewParagraphReader(reader, keyring) if err != nil { return nil, err } ret.paragraphReader = *pr return &ret, nil } // }}} // Decode {{{ func (d *Decoder) Decode(into interface{}) error { return decode(&d.paragraphReader, reflect.ValueOf(into)) } // Top-level decode dispatch {{{ func decode(p *ParagraphReader, into reflect.Value) error { if into.Type().Kind() != reflect.Ptr { return fmt.Errorf("Decode can only decode into a pointer!") } switch into.Elem().Type().Kind() { case reflect.Struct: paragraph, err := p.Next() if err != nil { return err } return decodeStruct(*paragraph, into) case reflect.Slice: return decodeSlice(p, into) default: return fmt.Errorf("Can't Decode into a %s", into.Elem().Type().Name()) } return nil } // }}} // Top-level struct dispatch {{{ func decodeStruct(p Paragraph, into reflect.Value) error { /* If we have a pointer, let's follow it */ if into.Type().Kind() == reflect.Ptr { return decodeStruct(p, into.Elem()) } /* Store the Paragraph type for later use when checking Anonymous * values. */ paragraphType := reflect.TypeOf(Paragraph{}) /* Right, now, we're going to decode a Paragraph into the struct */ for i := 0; i < into.NumField(); i++ { field := into.Field(i) fieldType := into.Type().Field(i) if field.Type().Kind() == reflect.Struct { err := decodeStruct(p, field) if err != nil { return err } } /* First, let's get the name of the field as we'd index into the * map[string]string. */ paragraphKey := fieldType.Name if it := fieldType.Tag.Get("control"); it != "" { paragraphKey = it } if paragraphKey == "-" { /* If the key is "-", lets go ahead and skip it */ continue } /* Now, if we have an Anonymous field, we're either going to * set it to the Paragraph if it's the Paragraph Anonymous member, * or, more likely, continue through */ if fieldType.Anonymous { if fieldType.Type == paragraphType { /* Neat! Let's give the struct this data */ field.Set(reflect.ValueOf(p)) } else { /* Otherwise, we're going to avoid doing more maths on it */ continue } } if value, ok := p.Values[paragraphKey]; ok { if err := decodeStructValue(field, fieldType, value); err != nil { return err } continue } else { if fieldType.Tag.Get("required") == "true" { return fmt.Errorf( "Required field '%s' is missing!", fieldType.Name, ) } continue } } return nil } // }}} // set a struct field value {{{ func decodeStructValue(field reflect.Value, fieldType reflect.StructField, value string) error { switch field.Type().Kind() { case reflect.String: field.SetString(value) return nil case reflect.Int: if value == "" { field.SetInt(0) return nil } value, err := strconv.Atoi(value) if err != nil { return err } field.SetInt(int64(value)) return nil case reflect.Slice: return decodeStructValueSlice(field, fieldType, value) case reflect.Struct: return decodeStructValueStruct(field, fieldType, value) case reflect.Bool: field.SetBool(value == "yes") return nil } return fmt.Errorf("Unknown type of field: %s", field.Type()) } // }}} // set a struct field value of type struct {{{ func decodeStructValueStruct(incoming reflect.Value, incomingField reflect.StructField, data string) error { /* Right, so, we've got a type we don't know what to do with. We should * grab the method, or throw a shitfit. */ elem := incoming.Addr() if unmarshal, ok := elem.Interface().(Unmarshallable); ok { return unmarshal.UnmarshalControl(data) } return fmt.Errorf( "Type '%s' does not implement control.Unmarshallable", incomingField.Type.Name(), ) } // }}} // set a struct field value of type slice {{{ func decodeStructValueSlice(field reflect.Value, fieldType reflect.StructField, value string) error { underlyingType := field.Type().Elem() var delim = " " if it := fieldType.Tag.Get("delim"); it != "" { delim = it } var strip = "" if it := fieldType.Tag.Get("strip"); it != "" { strip = it } value = strings.Trim(value, strip) for _, el := range strings.Split(value, delim) { el = strings.Trim(el, strip) targetValue := reflect.New(underlyingType) err := decodeStructValue(targetValue.Elem(), fieldType, el) if err != nil { return err } field.Set(reflect.Append(field, targetValue.Elem())) } return nil } // }}} // Top-level slice dispatch {{{ func decodeSlice(p *ParagraphReader, into reflect.Value) error { flavor := into.Elem().Type().Elem() for { targetValue := reflect.New(flavor) /* Get a Paragraph */ para, err := p.Next() if err == io.EOF { break } else if err != nil { return err } if err := decodeStruct(*para, targetValue); err != nil { return err } into.Elem().Set(reflect.Append(into.Elem(), targetValue.Elem())) } return nil } // }}} // }}} // Signer {{{ func (d *Decoder) Signer() *openpgp.Entity { return d.paragraphReader.Signer() } // }}} // }}} // UnpackFromParagraph {{{ // Unpack a Paragraph into a Struct, as if that data had been unpacked into // that struct to begin with. The normal rules from running the Unmarshal // API directly apply when unpacking a Paragraph using UnpackFromParagraph. // // In most cases, the Unmarshal API should be sufficient. Use of this API // is mildly discouraged. func UnpackFromParagraph(para Paragraph, incoming interface{}) error { data := reflect.ValueOf(incoming) if data.Type().Kind() != reflect.Ptr { return fmt.Errorf("Can only Decode a pointer to a Struct") } return decodeStruct(para, data.Elem()) } // }}} // vim: foldmethod=marker go-debian-0.16.0/control/decode_test.go000066400000000000000000000062021452017266000177170ustar00rootroot00000000000000package control_test import ( "strings" "testing" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) type TestStruct struct { Value string `required:"true"` ValueTwo string `control:"Value-Two"` ValueThree []string Depends dependency.Dependency Version version.Version Arch dependency.Arch Arches []dependency.Arch Fnord struct { FooBar string `control:"Fnord-Foo-Bar"` } ExtraSourceOnly bool `control:"Extra-Source-Only"` } func TestBasicUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Foo-Bar: baz `))) assert(t, foo.Value == "foo") } func TestBasicArrayUnmarshal(t *testing.T) { foo := []TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Foo-Bar: baz Value: Bar Value: Baz `))) assert(t, len(foo) == 3) assert(t, foo[0].Value == "foo") } func TestTagUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Value-Two: baz `))) assert(t, foo.Value == "foo") assert(t, foo.ValueTwo == "baz") } func TestDependsUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Depends: foo, bar `))) assert(t, foo.Value == "foo") assert(t, foo.Depends.Relations[0].Possibilities[0].Name == "foo") /* Actually invalid below */ notok(t, control.Unmarshal(&foo, strings.NewReader(`Depends: foo (>= 1.0) (<= 1.0) `))) } func TestVersionUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Version: 1.0-1 `))) assert(t, foo.Value == "foo") assert(t, foo.Version.Revision == "1") } func TestArchUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Arch: amd64 `))) assert(t, foo.Value == "foo") assert(t, foo.Arch.CPU == "amd64") foo = TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Arches: amd64 sparc any `))) assert(t, foo.Value == "foo") assert(t, foo.Arches[0].CPU == "amd64") assert(t, foo.Arches[1].CPU == "sparc") assert(t, foo.Arches[2].CPU == "any") } func TestNestedUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Fnord-Foo-Bar: Thing `))) assert(t, foo.Value == "foo") assert(t, foo.Fnord.FooBar == "Thing") } func TestListUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo ValueThree: foo bar baz `))) assert(t, foo.Value == "foo") assert(t, foo.ValueThree[0] == "foo") } func TestRequiredUnmarshal(t *testing.T) { foo := TestStruct{} notok(t, control.Unmarshal(&foo, strings.NewReader(`Foo-Bar: baz `))) } func TestBoolUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo `))) assert(t, !foo.ExtraSourceOnly) isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Extra-Source-Only: no `))) assert(t, !foo.ExtraSourceOnly) isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Extra-Source-Only: yes `))) assert(t, foo.ExtraSourceOnly) } go-debian-0.16.0/control/doc.go000066400000000000000000000001451452017266000162020ustar00rootroot00000000000000/* Parse the Debian control file format. */ package control // import "pault.ag/go/debian/control" go-debian-0.16.0/control/dsc.go000066400000000000000000000203021452017266000162030ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bufio" "fmt" "os" "path" "path/filepath" "strings" "pault.ag/go/debian/dependency" "pault.ag/go/debian/internal" "pault.ag/go/debian/version" "pault.ag/go/topsort" ) // A DSC is the encapsulation of a Debian .dsc control file. This contains // information about the source package, and is general handy. // // The Debian source control file is generated by dpkg-source when it builds // the source archive, from other files in the source package. // When unpacking, it is checked against the files and directories in the // other parts of the source package. type DSC struct { Paragraph Filename string Format string Source string Binaries []string `control:"Binary" delim:","` Architectures []dependency.Arch `control:"Architecture"` Version version.Version Origin string Maintainer string Uploaders []string Homepage string StandardsVersion string `control:"Standards-Version"` BuildDepends dependency.Dependency `control:"Build-Depends"` BuildDependsArch dependency.Dependency `control:"Build-Depends-Arch"` BuildDependsIndep dependency.Dependency `control:"Build-Depends-Indep"` ChecksumsSha1 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Files []MD5FileHash `control:"Files" delim:"\n" strip:"\n\r\t "` /* TODO: Package-List */ } // Given a bunch of DSC objects, sort the packages topologically by // build order by looking at the relationship between the Build-Depends // field. func OrderDSCForBuild(dscs []DSC, arch dependency.Arch) ([]DSC, error) { sourceMapping := map[string]string{} network := topsort.NewNetwork() ret := []DSC{} /* * - Create binary -> source mapping. * - Error if two sources provide the same binary * - Create a node for each source * - Create an edge from the source -> source * - return sorted list of dsc files */ for _, dsc := range dscs { for _, binary := range dsc.Binaries { sourceMapping[binary] = dsc.Source } network.AddNode(dsc.Source, dsc) } for _, dsc := range dscs { concreteBuildDepends := []dependency.Possibility{} concreteBuildDepends = append(concreteBuildDepends, dsc.BuildDepends.GetPossibilities(arch)...) concreteBuildDepends = append(concreteBuildDepends, dsc.BuildDependsArch.GetPossibilities(arch)...) concreteBuildDepends = append(concreteBuildDepends, dsc.BuildDependsIndep.GetPossibilities(arch)...) for _, relation := range concreteBuildDepends { if val, ok := sourceMapping[relation.Name]; ok { err := network.AddEdge(val, dsc.Source) if err != nil { return nil, err } } } } nodes, err := network.Sort() if err != nil { return nil, err } for _, node := range nodes { ret = append(ret, node.Value.(DSC)) } return ret, nil } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new DSC struct, unless error is set to a value // other than nil. func ParseDscFile(path string) (ret *DSC, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() ret, err = ParseDsc(bufio.NewReader(f), path) if err != nil { return nil, err } return ret, nil } // Given a bufio.Reader, consume the Reader, and return a DSC object // for use. func ParseDsc(reader *bufio.Reader, path string) (*DSC, error) { ret := DSC{Filename: path} err := Unmarshal(&ret, reader) if err != nil { return nil, err } return &ret, nil } // Check to see if this .dsc contains any arch:all binary packages along // with any arch dependent packages. func (d *DSC) HasArchAll() bool { for _, arch := range d.Architectures { if arch.CPU == "all" && arch.OS == "all" && arch.ABI == "all" { return true } } return false } // Return a list of all entities that are responsible for the package's // well being. The 0th element is always the package's Maintainer, // with any Uploaders following. func (d *DSC) Maintainers() []string { return append([]string{d.Maintainer}, d.Uploaders...) } // Return a list of MD5FileHash entries from the `dsc.Files` // entry, with the exception that each `Filename` will be joined to the root // directory of the DSC file. func (d *DSC) AbsFiles() []MD5FileHash { ret := []MD5FileHash{} baseDir := filepath.Dir(d.Filename) for _, hash := range d.Files { hash.Filename = path.Join(baseDir, hash.Filename) ret = append(ret, hash) } return ret } // Copy the .dsc file and all referenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .dsc last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate DSC.Filename to match the new location. func (d *DSC) Copy(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .dsc to a non-directory") } for _, file := range d.AbsFiles() { dirname := filepath.Base(file.Filename) err := internal.Copy(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(d.Filename) err := internal.Copy(d.Filename, dest+"/"+dirname) d.Filename = dest + "/" + dirname return err } // Move the .dsc file and all referenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .dsc last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate DSC.Filename to match the new location. func (d *DSC) Move(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .dsc to a non-directory") } for _, file := range d.AbsFiles() { dirname := filepath.Base(file.Filename) err := os.Rename(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(d.Filename) err := os.Rename(d.Filename, dest+"/"+dirname) d.Filename = dest + "/" + dirname return err } // Remove the .dsc file and any associated files. This function will // always remove the .dsc last, in the event there are filesystem i/o errors // on removing associated files. func (d *DSC) Remove() error { for _, file := range d.AbsFiles() { err := os.Remove(file.Filename) if err != nil { return err } } return os.Remove(d.Filename) } // Return the name of the Debian source. This is assumed to be the first file // that contains ".debian." in its name. func (d *DSC) DebianSource() (string, error) { for _, file := range d.Files { if strings.Contains(file.Filename, ".debian.") { return file.Filename, nil } } return "", fmt.Errorf("Could not find the Debian source") } // vim: foldmethod=marker go-debian-0.16.0/control/dsc_test.go000066400000000000000000000175531452017266000172600ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestDSCParse(t *testing.T) { // Test DSC {{{ reader := bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, c != nil) assert(t, c.Format == "3.0 (quilt)") assert(t, c.Source == "fbautostart") assert(t, len(c.Maintainers()) == 1) assert(t, c.Maintainers()[0] == "Paul Tagliamonte ") assert(t, c.Maintainer == "Paul Tagliamonte ") assert(t, c.Version.Version == "2.718281828") assert(t, c.Version.Revision == "1") assert(t, c.StandardsVersion == "3.9.3") assert(t, c.Homepage == "https://launchpad.net/fbautostart") debianSource, err := c.DebianSource() assert(t, err == nil) assert(t, debianSource == "fbautostart_2.718281828-1.debian.tar.xz") } func TestOpenPGPDSCParse(t *testing.T) { // Test OpenPGP DSC {{{ reader := bufio.NewReader(strings.NewReader(`-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz af4f1950dd8ed5bb7bd8952c8c00ffdd42eadb46 2396 fbautostart_2.718281828-1.debian.tar.gz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz 49f402ff3a72653e63542037be9f4da56e318e412d26d4154f9336fb88df3519 2396 fbautostart_2.718281828-1.debian.tar.gz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz 3b0e6dd201d5036f6d1b80f0ac4e1e7d 2396 fbautostart_2.718281828-1.debian.tar.gz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIcBAEBCAAGBQJP3sSmAAoJEJcyXdj5/dUG+0MQAMg7Naio+BQpssqth2p+4j7L Z87vdCd1fzRszRRptyBRbmTzTAzWPCNn15u5R+edCy7tyXi1TTty5QO/gU6p11mK wJ4EewJuagYcqZpiL6/6sGyDoLBdz5O0+TwmOp8M+mwKrHzU3kyyuKEtAyvhnsSv M6PaXU5hFaImwg+EZ1kJnzzxgM9UoJZ1Muib5mwS13FuH2uYU47t4h1vzgEsWDMm BaZoPJZhpmNksT+phPo1nlYaGg2M3bSyFrtFmWi9U+qih3l6hQ3N+6gNOTithccY lLf0PoEQiOu1ymbShsKtKkH2B7unkGIuyp3ElHt+XdRIhcoIrSu76RdLA2g8GLya eaoif0U/n3FbHRwE61uGiP6ykFRr6Lsih0Um61XLtGFHmtc/78+TknzoK2p98rDv vopNzdx0vrNOuqqJIQ+cjt9lCw493TxEo0uz+Ll0bhknLCByDgcA0AzciYoSpR+B 4KRQJwJAesFXhNueHCkYq8dsxSsIugKaEvBf7Z94bWam+AyVzakNMUF9ehp6KYhF jKL78j4v9XEo994zqhqTIuzA9xUoAXikzRrb97UbK5oqde4/G95/SotM50NgfQ2k 0DAgtrREBQGzSPSMCbWBJxbjEETsEBlxRkpceNwuE+kZ5HYkpPEzBWuugGsDThwx 5Q/HJMGB+4wV0I0IbEuI =RGXD -----END PGP SIGNATURE----- `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, c != nil) assert(t, c.Format == "3.0 (quilt)") assert(t, c.Source == "fbautostart") assert(t, len(c.Maintainers()) == 1) assert(t, c.Maintainers()[0] == "Paul Tagliamonte ") assert(t, c.Maintainer == "Paul Tagliamonte ") assert(t, c.Version.Version == "2.718281828") assert(t, c.Version.Revision == "1") assert(t, c.StandardsVersion == "3.9.3") assert(t, c.Homepage == "https://launchpad.net/fbautostart") } func TestDSCArchAllParse(t *testing.T) { // Test DSC (arch: any) {{{ reader := bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, !c.HasArchAll()) // Test DSC (arch: any all) {{{ reader = bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any all Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err = control.ParseDsc(reader, "") isok(t, err) assert(t, c.HasArchAll()) } // vim: foldmethod=marker go-debian-0.16.0/control/encode.go000066400000000000000000000220251452017266000166730ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "fmt" "io" "reflect" "strconv" "strings" ) // Marshallable {{{ // The Marshallable interface defines the interface that Marshal will use // to do custom dehydration of the Struct back into the Debian 822 format. type Marshallable interface { MarshalControl() (string, error) } // }}} // ConvertToParagraph {{{ // Given a Struct, convert that Struct back into a control.Paragraph. // This is not exactly useful as part of the external API, but may be // useful in some funny circumstances where you need to treat a Struct // you Unmarshaled into as a control.Paragraph again. // // In most cases, the Marshal API should be sufficient. Use of this API // is mildly discouraged. func ConvertToParagraph(incoming interface{}) (*Paragraph, error) { data := reflect.ValueOf(incoming) if data.Type().Kind() != reflect.Ptr { return nil, fmt.Errorf("Can only Decode a pointer to a Struct") } return convertToParagraph(data.Elem()) } // Top-level conversion dispatch {{{ func convertToParagraph(data reflect.Value) (*Paragraph, error) { order := []string{} values := map[string]string{} if data.Type().Kind() != reflect.Struct { return nil, fmt.Errorf("Can only Decode a Struct") } paragraphType := reflect.TypeOf(Paragraph{}) var foundParagraph Paragraph = Paragraph{} for i := 0; i < data.NumField(); i++ { field := data.Field(i) fieldType := data.Type().Field(i) if fieldType.Anonymous { if fieldType.Type == paragraphType { foundParagraph = field.Interface().(Paragraph) } continue } paragraphKey := fieldType.Name if it := fieldType.Tag.Get("control"); it != "" { paragraphKey = it } if paragraphKey == "-" { /* If the key is "-", lets go ahead and skip it */ continue } data, err := marshalStructValue(field, fieldType) if err != nil { return nil, err } required := fieldType.Tag.Get("required") == "true" if data == "" && !required { continue } if fieldType.Tag.Get("multiline") == "true" { data = "\n" + data } order = append(order, paragraphKey) values[paragraphKey] = data } para := foundParagraph.Update(Paragraph{Order: order, Values: values}) return ¶, nil } // }}} // convert a struct value {{{ func marshalStructValue(field reflect.Value, fieldType reflect.StructField) (string, error) { switch field.Type().Kind() { case reflect.String: return field.String(), nil case reflect.Uint: return strconv.Itoa(int(field.Uint())), nil case reflect.Int: return strconv.Itoa(int(field.Int())), nil case reflect.Ptr: return marshalStructValue(field.Elem(), fieldType) case reflect.Slice: return marshalStructValueSlice(field, fieldType) case reflect.Struct: return marshalStructValueStruct(field, fieldType) case reflect.Bool: if field.Bool() { return "yes", nil } return "no", nil } return "", fmt.Errorf("Unknown type: %s", field.Type().Kind()) } // }}} // convert a struct value of type struct {{{ func marshalStructValueStruct(field reflect.Value, fieldType reflect.StructField) (string, error) { /* Right, so, we've got a type we don't know what to do with. We should * grab the method, or throw a shitfit. */ if marshal, ok := field.Interface().(Marshallable); ok { return marshal.MarshalControl() } return "", fmt.Errorf( "Type '%s' does not implement control.Marshallable", field.Type().Name(), ) } // }}} // convert a struct value of type slice {{{ func marshalStructValueSlice(field reflect.Value, fieldType reflect.StructField) (string, error) { var delim = " " if it := fieldType.Tag.Get("delim"); it != "" { delim = it } data := []string{} for i := 0; i < field.Len(); i++ { elem := field.Index(i) if stringification, err := marshalStructValue(elem, fieldType); err != nil { return "", err } else { data = append(data, stringification) } } return strings.Join(data, delim), nil } // }}} // }}} // Marshal {{{ // Marshal is a one-off interface to serialize a single object to a writer. // // Most notably, this will *not* separate Paragraphs with a newline as is // expected upon repeated calls, please use the Encoder streaming interface // for that. // // It's also worth noting that this *will* also write out elements that // were Unmarshaled into a Struct without a member of that name if (and only // if) the target Struct contains a `control.Paragraph` anonymous member. // // This is handy if the Unmarshaler was given any `X-*` keys that were not // present on your Struct. // // Given a struct (or list of structs), write to the io.Writer stream // in the RFC822-alike Debian control-file format // // This code will attempt to unpack it into the struct based on the // literal name of the key, This can be overridden by the struct tag // `control:""`. // // If you're dehydrating a list of strings, you have the option of defining // a string to join the tokens with (`delim:", "`). // // In order to Marshal a custom Struct, you are required to implement the // Marshallable interface. It's highly encouraged to put this interface on // the struct without a pointer receiver, so that pass-by-value works // when you call Marshal. func Marshal(writer io.Writer, data interface{}) error { encoder, err := NewEncoder(writer) if err != nil { return err } return encoder.Encode(data) } // }}} // Encoder {{{ // Encoder is a struct that allows for the streaming Encoding of data // back out to an `io.Writer`. Most notably, this will separate // subsequent `Encode` calls of a Struct with a newline. // // It's also worth noting that this *will* also write out elements that // were Unmarshaled into a Struct without a member of that name if (and only // if) the target Struct contains a `control.Paragraph` anonymous member. // // This is handy if the Unmarshaler was given any `X-*` keys that were not // present on your Struct. // // Given a struct (or list of structs), write to the io.Writer stream // in the RFC822-alike Debian control-file format // // This code will attempt to unpack it into the struct based on the // literal name of the key, This can be overridden by the struct tag // `control:""`. // // If you're dehydrating a list of strings, you have the option of defining // a string to join the tokens with (`delim:", "`). // // In order to Marshal a custom Struct, you are required to implement the // Marshallable interface. It's highly encouraged to put this interface on // the struct without a pointer receiver, so that pass-by-value works // when you call Marshal. type Encoder struct { writer io.Writer alreadyWritten bool } // NewEncoder {{{ // Create a new Encoder, which is configured to write to the given `io.Writer`. func NewEncoder(writer io.Writer) (*Encoder, error) { return &Encoder{ writer: writer, alreadyWritten: false, }, nil } // }}} // Encode {{{ // Take a Struct, Encode it into a Paragraph, and write that out to the // io.Writer set up when the Encoder was configured. func (e *Encoder) Encode(incoming interface{}) error { data := reflect.ValueOf(incoming) return e.encode(data) } // Top-level Encode reflect dispatch {{{ func (e *Encoder) encode(data reflect.Value) error { if data.Type().Kind() == reflect.Ptr { return e.encode(data.Elem()) } switch data.Type().Kind() { case reflect.Slice: return e.encodeSlice(data) case reflect.Struct: return e.encodeStruct(data) } return fmt.Errorf("Unknown type") } // }}} // Encode a Slice {{{ func (e *Encoder) encodeSlice(data reflect.Value) error { for i := 0; i < data.Len(); i++ { if err := e.encodeStruct(data.Index(i)); err != nil { return err } } return nil } // }}} // Encode a Struct {{{ func (e *Encoder) encodeStruct(data reflect.Value) error { if e.alreadyWritten { _, err := e.writer.Write([]byte("\n")) if err != nil { return err } } paragraph, err := convertToParagraph(data) if err != nil { return err } e.alreadyWritten = true return paragraph.WriteTo(e.writer) } // }}} // }}} // }}} // vim: foldmethod=marker go-debian-0.16.0/control/encode_test.go000066400000000000000000000046301452017266000177340ustar00rootroot00000000000000package control_test import ( "bytes" "strings" "testing" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) type TestMarshalStruct struct { Foo string } type SomeComplexStruct struct { control.Paragraph Version version.Version Dependency dependency.Dependency } type TestParaMarshalStruct struct { control.Paragraph Foo string } func TestExtraMarshal(t *testing.T) { el := TestParaMarshalStruct{} isok(t, control.Unmarshal(&el, strings.NewReader(`Foo: test X-A-Test: Foo `))) assert(t, el.Foo == "test") writer := bytes.Buffer{} isok(t, control.Marshal(&writer, el)) assert(t, writer.String() == `Foo: test X-A-Test: Foo `) } func TestBasicMarshal(t *testing.T) { testStruct := TestMarshalStruct{Foo: "Hello"} writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, writer.String() == `Foo: Hello `) writer = bytes.Buffer{} err = control.Marshal(&writer, []TestMarshalStruct{ testStruct, }) isok(t, err) assert(t, writer.String() == `Foo: Hello `) writer = bytes.Buffer{} err = control.Marshal(&writer, []TestMarshalStruct{ testStruct, testStruct, }) isok(t, err) assert(t, writer.String() == `Foo: Hello Foo: Hello `) } func TestExternalMarshal(t *testing.T) { testStruct := SomeComplexStruct{} isok(t, control.Unmarshal(&testStruct, strings.NewReader(`Version: 1.0-1 Dependency: foo, bar X-Foo: bar `))) writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, testStruct.Dependency.Relations[0].Possibilities[0].Name == "foo") assert(t, writer.String() == `Version: 1.0-1 Dependency: foo, bar X-Foo: bar `) } func TestMultilineMarshal(t *testing.T) { testStruct := TestMarshalStruct{Foo: `Hello This Is A Test`} writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, writer.String() == `Foo: Hello This Is . A Test `) } type boolStruct struct { ExtraSourceOnly bool `control:"Extra-Source-Only"` } func TestBoolMarshal(t *testing.T) { bs := boolStruct{ExtraSourceOnly: true} writer := bytes.Buffer{} err := control.Marshal(&writer, bs) isok(t, err) assert(t, writer.String() == `Extra-Source-Only: yes `) bs = boolStruct{ExtraSourceOnly: false} writer = bytes.Buffer{} err = control.Marshal(&writer, bs) isok(t, err) assert(t, writer.String() == `Extra-Source-Only: no `) } // vim: foldmethod=marker go-debian-0.16.0/control/filehash.go000066400000000000000000000120051452017266000172160ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bytes" "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "hash" "io" "log" "path/filepath" "strconv" "strings" "pault.ag/go/debian/hashio" ) // A FileHash is an entry as found in the Files, Checksum-Sha1, and // Checksum-Sha256 entry for the .dsc or .changes files. type FileHash struct { // cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc Algorithm string Hash string Size int64 Filename string ByHash string } func FileHashFromHasher(path string, hasher hashio.Hasher) FileHash { return FileHash{ Algorithm: hasher.Name(), Hash: fmt.Sprintf("%x", hasher.Sum(nil)), Size: hasher.Size(), Filename: path, } } type FileHashes []FileHash type verifier struct { h hash.Hash want []byte closed bool } func (v *verifier) Write(p []byte) (n int, err error) { return v.h.Write(p) } func (v *verifier) Close() error { if v.closed { return nil } v.closed = true got := v.h.Sum(nil) if !bytes.Equal(got, v.want) { return fmt.Errorf("invalid hash: got %x, want %x", got, v.want) } return nil } // Verifier returns an io.WriteCloser which verifies the hash of the data being // written to it and fails Close() upon hash mismatch. // // Example: // verifier := fh.Verifier() // r = io.TeeReader(r, verifier) // if _, err := io.Copy(f, r); err != nil { // return err // } // if err := verifier.Close(); err != nil { // return err // } func (c *FileHash) Verifier() (io.WriteCloser, error) { var h hash.Hash switch c.Algorithm { case "sha256": h = sha256.New() case "sha512": h = sha512.New() default: log.Fatalf("BUG: FileHash.Verifier not updated after release.Indices()") } sum, err := hex.DecodeString(c.Hash) if err != nil { return nil, err } return &verifier{h: h, want: sum}, nil } // {{{ Hash File implementations // ByHashPath returns the corresponding /by-hash// path. // This function must only be used if the release supports AcquireByHash. func (c *FileHash) ByHashPath(path string) string { return filepath.Dir(path) + "/by-hash/" + c.ByHash + "/" + c.Hash } func (c *FileHash) marshalControl() (string, error) { return fmt.Sprintf("%s %d %s", c.Hash, c.Size, c.Filename), nil } func (c *FileHash) unmarshalControl(algorithm, data string) error { var err error c.Algorithm = algorithm vals := strings.Fields(data) switch len(vals) { case 3: c.Hash = vals[0] c.Size, err = strconv.ParseInt(vals[1], 10, 64) if err != nil { return err } c.Filename = vals[2] switch algorithm { case "sha256": c.ByHash = "SHA256" case "sha512": c.ByHash = "SHA512" } return nil case 2: c.Filename = vals[0] c.Hash = vals[1] return nil default: return fmt.Errorf("Error: Unknown Debian Hash line: '%s'", data) } } // {{{ MD5 FileHash type MD5FileHash struct{ FileHash } func (c *MD5FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("md5", data) } func (c MD5FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA1 FileHash type SHA1FileHash struct{ FileHash } func (c *SHA1FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha1", data) } func (c SHA1FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA256 FileHash type SHA256FileHash struct{ FileHash } func (c *SHA256FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha256", data) } func (c SHA256FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA512 FileHash type SHA512FileHash struct{ FileHash } func (c *SHA512FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha512", data) } func (c SHA512FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // }}} // vim: foldmethod=marker go-debian-0.16.0/control/filehash_test.go000066400000000000000000000034441452017266000202640ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) func TestCrashWithoutDelim(t *testing.T) { type release struct { MD5Sum []control.MD5FileHash SHA1 []control.SHA1FileHash SHA256 []control.SHA256FileHash } // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`MD5Sum: b096313254606a2f984f6fc38e4769c8 1219120 contrib/Contents-amd64 SHA256: 0f6dc8c4865a433d321a3af0d4812030fdb14231cd1f93bd7570f1518a46faad 1219120 contrib/Contents-amd64 `)) // }}} var r release if err := control.Unmarshal(&r, reader); err == nil { t.Errorf("control.Unmarshal unexpectedly succeeded on struct without delim") } } go-debian-0.16.0/control/index.go000066400000000000000000000161571452017266000165560ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bufio" "strings" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) // The BinaryIndex struct represents the exported APT Binary package index // file, as seen on Debian (and Debian derived) mirrors, as well as the // cached version in /var/lib/apt/lists/. // // This can be used to examine Binary packages contained in the Archive, // to examine things like Built-Using, Depends, Tags or Binary packages // present on an Architecture. type BinaryIndex struct { Paragraph Package string Source string Version version.Version InstalledSize int `control:"Installed-Size"` Maintainer string Architecture dependency.Arch MultiArch string `control:"Multi-Arch"` Description string Homepage string DescriptionMD5 string `control:"Description-md5"` Tags []string `delim:", "` Section string Priority string Filename string Size int MD5sum string SHA1 string SHA256 string DebugBuildIds []string `control:"Build-Ids" delim:" "` } // Parse the Conflicts Dependency relation on this package. func (index *BinaryIndex) GetConflicts() dependency.Dependency { return index.getOptionalDependencyField("Conflicts") } // Parse the Depends Dependency relation on this package. func (index *BinaryIndex) GetDepends() dependency.Dependency { return index.getOptionalDependencyField("Depends") } // Parse the Depends Suggests relation on this package. func (index *BinaryIndex) GetSuggests() dependency.Dependency { return index.getOptionalDependencyField("Suggests") } // Parse the Depends Breaks relation on this package. func (index *BinaryIndex) GetBreaks() dependency.Dependency { return index.getOptionalDependencyField("Breaks") } // Parse the Depends Replaces relation on this package. func (index *BinaryIndex) GetReplaces() dependency.Dependency { return index.getOptionalDependencyField("Replaces") } // Parse the Depends Pre-Depends relation on this package. func (index *BinaryIndex) GetPreDepends() dependency.Dependency { return index.getOptionalDependencyField("Pre-Depends") } // Parse the Built-Depends relation on this package. func (index *BinaryIndex) GetBuiltUsing() dependency.Dependency { return index.getOptionalDependencyField("Built-Using") } // SourcePackage returns the Debian source package name from which this binary // Package was built, coping with the special cases Source == Package (skipped // for efficiency) and binNMUs (Source contains version number). func (index *BinaryIndex) SourcePackage() string { if index.Source == "" { return index.Package } if !strings.Contains(index.Source, " ") { return index.Source } return strings.Split(index.Source, " ")[0] } // BestChecksums can be included in a struct instead of e.g. ChecksumsSha256. // // BestChecksums uses cryptographically secure checksums, so that application // code does not need to worry about that. // // The struct fields of BestChecksums need to be exported for the unmarshaling // process but most not be used directly. Use the Checksums() accessor instead. type BestChecksums struct { ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` ChecksumsSha512 []SHA256FileHash `control:"Checksums-Sha512" delim:"\n" strip:"\n\r\t "` } // Checksums returns FileHashes of a cryptographically secure kind. func (b *BestChecksums) Checksums() []FileHash { if len(b.ChecksumsSha256) > 0 { res := make([]FileHash, len(b.ChecksumsSha256)) for i, c := range b.ChecksumsSha256 { res[i] = c.FileHash } return res } if len(b.ChecksumsSha512) > 0 { res := make([]FileHash, len(b.ChecksumsSha512)) for i, c := range b.ChecksumsSha512 { res[i] = c.FileHash } return res } return nil } // The SourceIndex struct represents the exported APT Source index // file, as seen on Debian (and Debian derived) mirrors, as well as the // cached version in /var/lib/apt/lists/. // // This can be used to examine Source packages, to examine things like // Binary packages built by Source packages, who maintains a package, // or where to find the VCS repo for that package. type SourceIndex struct { Paragraph Package string Binaries []string `control:"Binary" delim:","` Version version.Version Maintainer string Uploaders string `delim:","` Architecture []dependency.Arch StandardsVersion string Format string Files []MD5FileHash `delim:"\n" strip:"\n\r\t "` VcsBrowser string `control:"Vcs-Browser"` VcsGit string `control:"Vcs-Git"` VcsSvn string `control:"Vcs-Svn"` VcsBzr string `control:"Vcs-Bzr"` ChecksumsSha1 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Homepage string Directory string Priority string Section string } // Parse the Depends Build-Depends relation on this package. func (index *SourceIndex) GetBuildDepends() dependency.Dependency { return index.getOptionalDependencyField("Build-Depends") } // Parse the Depends Build-Depends-Arch relation on this package. func (index *SourceIndex) GetBuildDependsArch() dependency.Dependency { return index.getOptionalDependencyField("Build-Depends-Arch") } // Parse the Depends Build-Depends-Indep relation on this package. func (index *SourceIndex) GetBuildDependsIndep() dependency.Dependency { return index.getOptionalDependencyField("Build-Depends-Indep") } // Given a reader, parse out a list of BinaryIndex structs. func ParseBinaryIndex(reader *bufio.Reader) (ret []BinaryIndex, err error) { ret = []BinaryIndex{} err = Unmarshal(&ret, reader) return ret, err } // Given a reader, parse out a list of SourceIndex structs. func ParseSourceIndex(reader *bufio.Reader) (ret []SourceIndex, err error) { ret = []SourceIndex{} err = Unmarshal(&ret, reader) return ret, err } // vim: foldmethod=marker go-debian-0.16.0/control/index_test.go000066400000000000000000000271261452017266000176130ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" ) func TestSourceIndexParse(t *testing.T) { // Test Source Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: fbasics Binary: r-cran-fbasics Version: 3011.87-2 Maintainer: Dirk Eddelbuettel Build-Depends: debhelper (>= 7.0.0), r-base-dev (>= 3.2.0), cdbs, r-cran-mass, r-cran-timedate, r-cran-timeseries (>= 2100.84), r-cran-stabledist, xvfb, xauth, xfonts-base, r-cran-gss Architecture: any Standards-Version: 3.9.6 Format: 1.0 Files: 8bb6eda1e01be26c5446d21c64420e7f 1818 fbasics_3011.87-2.dsc f9f6e7f84bff1ce90cdc5890b9a3f6b5 932125 fbasics_3011.87.orig.tar.gz afc2e90feddb30baf96babfc767dff60 3818 fbasics_3011.87-2.diff.gz Checksums-Sha1: 45137d257a8bf2ed01b1809add5641bea7353072 1818 fbasics_3011.87-2.dsc cb0d17a055b7eaea72b14938e2948e603011b548 932125 fbasics_3011.87.orig.tar.gz 03c215003ddca5a9651d315902d2d3ee67e8c37a 3818 fbasics_3011.87-2.diff.gz Checksums-Sha256: 0a4f8cc793903e366a84379a651bf1a4542d50823b4bd4e038efcdb85a1af95e 1818 fbasics_3011.87-2.dsc f0a79bb3931cd145677c947d8cd87cf60869f604933e685e74225bb01ad992f4 932125 fbasics_3011.87.orig.tar.gz e087596fc0ac2bca6cf9ad531afc4329e75b2d7b26f0a0334be8dcb29d94f4ee 3818 fbasics_3011.87-2.diff.gz Homepage: http://www.Rmetrics.org Package-List: r-cran-fbasics deb gnu-r optional arch=any Directory: pool/main/f/fbasics Priority: source Section: gnu-r Package: fbautostart Binary: fbautostart Version: 2.718281828-1 Maintainer: Paul Tagliamonte Build-Depends: debhelper (>= 9) Architecture: any Standards-Version: 3.9.3 Format: 3.0 (quilt) Files: 9d610c30f96623cff07bd880e5cca12f 1899 fbautostart_2.718281828-1.dsc 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz 3b0e6dd201d5036f6d1b80f0ac4e1e7d 2396 fbautostart_2.718281828-1.debian.tar.gz Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Checksums-Sha1: 3e0dcbe5549f47f35eb7f8960c0ada33bbc3f48b 1899 fbautostart_2.718281828-1.dsc bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz af4f1950dd8ed5bb7bd8952c8c00ffdd42eadb46 2396 fbautostart_2.718281828-1.debian.tar.gz Checksums-Sha256: 0adda8e19e217dd2fa69d0842dcef0fa250bd428b1e43e78723d76909e5f51cc 1899 fbautostart_2.718281828-1.dsc bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz 49f402ff3a72653e63542037be9f4da56e318e412d26d4154f9336fb88df3519 2396 fbautostart_2.718281828-1.debian.tar.gz Homepage: https://launchpad.net/fbautostart Package-List: fbautostart deb misc optional Directory: pool/main/f/fbautostart Priority: source Section: misc `)) // }}} sources, err := control.ParseSourceIndex(reader) isok(t, err) assert(t, len(sources) == 2) fbautostart := sources[1] assert(t, fbautostart.Maintainer == "Paul Tagliamonte ") assert(t, fbautostart.VcsGit == "git://git.debian.org/collab-maint/fbautostart.git") assert(t, len(fbautostart.Files) == 3) assert(t, fbautostart.Files[0].Algorithm == "md5") assert(t, fbautostart.Files[0].Hash == "9d610c30f96623cff07bd880e5cca12f") assert(t, fbautostart.Files[0].Filename == "fbautostart_2.718281828-1.dsc") assert(t, fbautostart.Files[0].Size == 1899) assert(t, fbautostart.Files[0].ByHash == "") assert(t, len(fbautostart.ChecksumsSha1) == 3) assert(t, fbautostart.ChecksumsSha1[1].Algorithm == "sha1") assert(t, fbautostart.ChecksumsSha1[1].Hash == "bc36310c15edc9acf48f0a1daf548bcc6f861372") assert(t, fbautostart.ChecksumsSha1[1].Filename == "fbautostart_2.718281828.orig.tar.gz") assert(t, fbautostart.ChecksumsSha1[1].Size == 92748) assert(t, fbautostart.ChecksumsSha1[1].ByHash == "") assert(t, len(fbautostart.ChecksumsSha256) == 3) assert(t, fbautostart.ChecksumsSha256[2].Algorithm == "sha256") assert(t, fbautostart.ChecksumsSha256[2].Hash == "49f402ff3a72653e63542037be9f4da56e318e412d26d4154f9336fb88df3519") assert(t, fbautostart.ChecksumsSha256[2].Filename == "fbautostart_2.718281828-1.debian.tar.gz") assert(t, fbautostart.ChecksumsSha256[2].Size == 2396) assert(t, fbautostart.ChecksumsSha256[2].ByHash == "SHA256") } func TestBinaryIndexParse(t *testing.T) { // Test Binary Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: android-tools-fastboot Source: android-tools Version: 4.2.2+git20130529-5.1 Installed-Size: 184 Maintainer: Android tools Maintainer Architecture: amd64 Depends: libc6 (>= 2.14), libselinux1 (>= 2.0.65), zlib1g (>= 1:1.2.3.4) Description: Android Fastboot protocol CLI tool Homepage: http://developer.android.com/guide/developing/tools/adb.html Description-md5: 56b9309fa4fb2f92a313a815c7d7b5d3 Section: devel Priority: extra Filename: pool/main/a/android-tools/android-tools-fastboot_4.2.2+git20130529-5.1_amd64.deb Size: 56272 MD5sum: cd858b3257b250747822ebeea6c69f4a SHA1: 9d45825f07b2bc52edc787ba78966db0d4a48e69 SHA256: c094b7e53eb030957cdfab865f68c817d65bf6a1345b10d2982af38d042c3e84 Package: android-tools-fsutils Source: android-tools Version: 4.2.2+git20130529-5.1 Installed-Size: 504 Maintainer: Android tools Maintainer Architecture: amd64 Depends: python:any, libc6 (>= 2.14), libselinux1 (>= 2.0.65), zlib1g (>= 1:1.2.3.4) Description: Android ext4 utilities with sparse support Homepage: http://developer.android.com/guide/developing/tools/adb.html Description-md5: 23135bc652e7b302961741f9bcff8397 Section: devel Priority: extra Filename: pool/main/a/android-tools/android-tools-fsutils_4.2.2+git20130529-5.1_amd64.deb Size: 71900 MD5sum: 996732fc455acdcf4682de4f80a2dc95 SHA1: 5c2320913cc7cc46305390d8b3a7ef51f0a174ef SHA256: 270ad759d1fef9cedf894c42b5f559d7386aa1ec4de4cc3880eb44fe8c53c833 Package: androidsdk-ddms Source: androidsdk-tools Version: 22.2+git20130830~92d25d6-1 Installed-Size: 211 Maintainer: Debian Java Maintainers Architecture: all Depends: libandroidsdk-swtmenubar-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmlib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmuilib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-sdkstats-java (= 22.2+git20130830~92d25d6-1), eclipse-rcp Description: Graphical debugging tool for Android Homepage: http://developer.android.com/tools/help/index.html Description-md5: a2f559d2abf6ebb1d25bc3929d5aa2b0 Section: java Priority: extra Filename: pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb Size: 132048 MD5sum: fde05f3552457e91a415c99ab2a2a514 SHA1: 82b05c97163ccfbbb10a52a5514882412a13ee43 SHA256: fa53e4f50349c5c9b564b8dc1da86c503b0baf56ab95a4ef6e204b6f77bfe70c `)) // }}} sources, err := control.ParseBinaryIndex(reader) isok(t, err) assert(t, len(sources) == 3) assert(t, sources[2].Package == "androidsdk-ddms") assert(t, sources[2].Source == "androidsdk-tools") assert(t, sources[2].Version.String() == "22.2+git20130830~92d25d6-1") assert(t, sources[2].InstalledSize == 211) assert(t, sources[2].Maintainer == "Debian Java Maintainers ") assert(t, sources[2].Architecture == dependency.All) assert(t, sources[2].Description == "Graphical debugging tool for Android") assert(t, sources[2].Homepage == "http://developer.android.com/tools/help/index.html") assert(t, sources[2].Section == "java") assert(t, sources[2].Priority == "extra") assert(t, sources[2].Filename == "pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb") assert(t, sources[2].Size == 132048) assert(t, sources[2].MD5sum == "fde05f3552457e91a415c99ab2a2a514") assert(t, sources[2].SHA256 == "fa53e4f50349c5c9b564b8dc1da86c503b0baf56ab95a4ef6e204b6f77bfe70c") } func TestBinaryIndexDependsParse(t *testing.T) { // Test Binary Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: androidsdk-ddms Source: androidsdk-tools Version: 22.2+git20130830~92d25d6-1 Installed-Size: 211 Maintainer: Debian Java Maintainers Architecture: all Depends: libandroidsdk-swtmenubar-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmlib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmuilib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-sdkstats-java (= 22.2+git20130830~92d25d6-1), eclipse-rcp Description: Graphical debugging tool for Android Homepage: http://developer.android.com/tools/help/index.html Description-md5: a2f559d2abf6ebb1d25bc3929d5aa2b0 Section: java Priority: extra Filename: pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb Size: 132048 MD5sum: fde05f3552457e91a415c99ab2a2a514 SHA1: 82b05c97163ccfbbb10a52a5514882412a13ee43 SHA256: fa53e4f50349c5c9b564b8dc1da86c503b0baf56ab95a4ef6e204b6f77bfe70c `)) // }}} sources, err := control.ParseBinaryIndex(reader) isok(t, err) assert(t, len(sources) == 1) ddms := sources[0] ddmsDepends := ddms.GetDepends() assert(t, ddmsDepends.GetAllPossibilities()[0].Version.Number == "22.2+git20130830~92d25d6-1") } func TestBinaryIndexConflictsParse(t *testing.T) { // Test Binary Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: zvmcloudconnector-common Architecture: all Version: 2.0.0~b1~git2019062011.4fc9142.really.1.4.1-0ubuntu3 Priority: optional Section: python Source: zvmcloudconnector Origin: Ubuntu Maintainer: Ubuntu Developers Original-Maintainer: Corey Bryant Bugs: https://bugs.launchpad.net/ubuntu/+filebug Installed-Size: 56 Depends: adduser Conflicts: somepackage (>= 5.0) Filename: pool/main/z/zvmcloudconnector/zvmcloudconnector-common_2.0.0~b1~git2019062011.4fc9142.really.1.4.1-0ubuntu3_all.deb Size: 11948 MD5sum: b6356b883d475e5552eb61f3d3dac051 SHA1: f411b822cdf581e7f83f9bbf59371f91a00ff9bb SHA256: f55ba72804b82daa77fd10afe07ac75fd5ce24740bafd26bc587be34850b96c5 Homepage: https://github.com/mfcloud/python-zvm-sdk Description: z/VM Development SDK for managing z/VM - Common Files Description-md5: d05e72aec9e53ee776fc0735e135889b `)) // }}} sources, err := control.ParseBinaryIndex(reader) isok(t, err) assert(t, len(sources) == 1) pkgconflicts := sources[0].GetConflicts() conflicts := pkgconflicts.GetAllPossibilities() assert(t, len(conflicts) == 1) assert(t, conflicts[0].Name == "somepackage") assert(t, conflicts[0].Version.Number == "5.0") assert(t, conflicts[0].Version.Operator == ">=") } // vim: foldmethod=marker go-debian-0.16.0/control/parse.go000066400000000000000000000222401452017266000165470ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control // import "pault.ag/go/debian/control" import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "strings" "unicode" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/clearsign" ) // A Paragraph is a block of RFC2822-like key value pairs. This struct contains // two methods to fetch values, a Map called Values, and a Slice called // Order, which maintains the ordering as defined in the RFC2822-like block type Paragraph struct { Values map[string]string Order []string } // Paragraph Helpers {{{ func (p *Paragraph) Set(key, value string) { if _, found := p.Values[key]; found { /* We've got the key */ p.Values[key] = value return } /* Otherwise, go ahead and set it in the order and dict, * and call it a day */ p.Order = append(p.Order, key) p.Values[key] = value } func (p *Paragraph) WriteTo(out io.Writer) error { for _, key := range p.Order { value := p.Values[key] value = strings.Replace(value, "\n", "\n ", -1) value = strings.Replace(value, "\n \n", "\n .\n", -1) if _, err := out.Write( []byte(fmt.Sprintf("%s: %s\n", key, value)), ); err != nil { return err } } return nil } func (p *Paragraph) Update(other Paragraph) Paragraph { ret := Paragraph{ Order: []string{}, Values: map[string]string{}, } seen := map[string]bool{} for _, el := range p.Order { ret.Order = append(ret.Order, el) ret.Values[el] = p.Values[el] seen[el] = true } for _, el := range other.Order { if _, ok := seen[el]; !ok { ret.Order = append(ret.Order, el) seen[el] = true } ret.Values[el] = other.Values[el] } return ret } // }}} // ParagraphReader {{{ // Wrapper to allow iteration on a set of Paragraphs without consuming them // all into memory at one time. This is also the level in which data is // signed, so information such as the entity that signed these documents // can be read by calling the `.Signer` method on this struct. The next // unread Paragraph can be returned by calling the `.Next` method on this // struct. type ParagraphReader struct { reader *bufio.Reader signer *openpgp.Entity } // {{{ NewParagraphReader // Create a new ParagraphReader from the given `io.Reader`, and `keyring`. // if `keyring` is set to `nil`, this will result in all OpenPGP signature // checking being disabled. *including* that the contents match! // // Also keep in mind, `reader` may be consumed 100% in memory due to // the underlying OpenPGP API being hella fiddly. func NewParagraphReader(reader io.Reader, keyring *openpgp.EntityList) (*ParagraphReader, error) { bufioReader := bufio.NewReader(reader) ret := ParagraphReader{ reader: bufioReader, signer: nil, } // OK. We have a document. Now, let's peek ahead and see if we've got an // OpenPGP Clearsigned set of Paragraphs. If we do, we're going to go ahead // and do the decode dance. line, _ := bufioReader.Peek(15) if string(line) != "-----BEGIN PGP " { return &ret, nil } if err := ret.decodeClearsig(keyring); err != nil { return nil, err } return &ret, nil } // }}} // Signer {{{ // Return the Entity (if one exists) that signed this set of Paragraphs. func (p *ParagraphReader) Signer() *openpgp.Entity { return p.signer } // }}} // All {{{ func (p *ParagraphReader) All() ([]Paragraph, error) { ret := []Paragraph{} for { paragraph, err := p.Next() if err == io.EOF { return ret, nil } else if err != nil { return []Paragraph{}, err } ret = append(ret, *paragraph) } } // }}} // Next {{{ // Consume the io.Reader and return the next parsed Paragraph, modulo // garbage lines causing us to return an error. func (p *ParagraphReader) Next() (*Paragraph, error) { paragraph := Paragraph{ Order: []string{}, Values: map[string]string{}, } var lastKey string for { line, err := p.reader.ReadString('\n') if err == io.EOF && line != "" { err = nil line = line + "\n" /* We'll clean up the last of the buffer. */ } if err == io.EOF { /* Let's return the parsed paragraph if we have it */ if len(paragraph.Order) > 0 { return ¶graph, nil } /* Else, let's go ahead and drop the EOF out raw */ return nil, err } else if err != nil { return nil, err } if line == "\n" || line == "\r\n" { if len(paragraph.Order) == 0 { /* Skip over any number of blank lines between paragraphs. */ continue } /* Lines are ended by a blank line; so we're able to go ahead * and return this guy as-is. All set. Done. Finished. */ return ¶graph, nil } if strings.HasPrefix(line, "#") { continue // skip comments } /* Right, so we have a line in one of the following formats: * * "Key: Value" * " Foobar" * * Foobar is seen as a continuation of the last line, and the * Key line is a Key/Value mapping. */ if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { /* This is a continuation line; so we're going to go ahead and * clean it up, and throw it into the list. We're going to remove * the first character (which we now know is whitespace), and if * it's a line that only has a dot on it, we'll remove that too * (since " .\n" is actually "\n"). We only trim off space on the * right hand, because indentation under the whitespace is up to * the data format. Not us. */ /* TrimFunc(line[1:], unicode.IsSpace) is identical to calling * TrimSpace. */ line = strings.TrimRightFunc(line[1:], unicode.IsSpace) if line == "." { line = "" } if paragraph.Values[lastKey] == "" { paragraph.Values[lastKey] = line + "\n" } else { if !strings.HasSuffix(paragraph.Values[lastKey], "\n") { paragraph.Values[lastKey] = paragraph.Values[lastKey] + "\n" } paragraph.Values[lastKey] = paragraph.Values[lastKey] + line + "\n" } continue } /* So, if we're here, we've got a key line. Let's go ahead and split * this on the first key, and set that guy */ els := strings.SplitN(line, ":", 2) if len(els) != 2 { return nil, fmt.Errorf("Bad line: '%s' has no ':'", line) } /* We'll go ahead and take off any leading spaces */ lastKey = strings.TrimSpace(els[0]) value := strings.TrimSpace(els[1]) paragraph.Order = append(paragraph.Order, lastKey) paragraph.Values[lastKey] = value } } // }}} // decodeClearsig {{{ // Internal method to read an OpenPGP Clearsigned document, store related // OpenPGP information onto the shell Struct, and return any errors that // we encounter along the way, such as an invalid signature, unknown // signer, or incomplete document. If `keyring` is `nil`, checking of the // signed data is *not* preformed. func (p *ParagraphReader) decodeClearsig(keyring *openpgp.EntityList) error { // One *massive* downside here is that the OpenPGP module in Go operates // on byte arrays in memory, and *not* on Readers and Writers. This is a // huge PITA because it doesn't need to be that way, and this forces // clearsigned documents into memory. Which fucking sucks. But here // we are. It's likely worth a bug or two on this. signedData, err := ioutil.ReadAll(p.reader) if err != nil { return err } block, _ := clearsign.Decode(signedData) /* We're only interested in the first block. This may change in the * future, in which case, we should likely set reader back to * the remainder, and return that out to put through another * ParagraphReader, since it may have a different signer. */ if block == nil { return fmt.Errorf("Invalid clearsigned input") } if keyring == nil { /* As a special case, if the keyring is nil, we can go ahead * and assume this data isn't intended to be checked against the * keyring. So, we'll just pass on through. */ p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes)) return nil } /* Now, we have to go ahead and check that the signature is valid and * relates to an entity we have in our keyring */ signer, err := openpgp.CheckDetachedSignature( keyring, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, ) if err != nil { return err } p.signer = signer p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes)) return nil } // }}} // }}} // vim: foldmethod=marker go-debian-0.16.0/control/parse_test.go000066400000000000000000000231431452017266000176110ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package control_test import ( "io" "log" "strings" "testing" "golang.org/x/crypto/openpgp" "pault.ag/go/debian/control" ) /* * */ // Signed Paragraph {{{ const signedParagraph = `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 1.8 Date: Mon, 16 Nov 2015 21:15:55 -0800 Source: hy Binary: hy python-hy python3-hy Architecture: source Version: 0.11.0-4 Distribution: unstable Urgency: medium Maintainer: Tianon Gravi Changed-By: Tianon Gravi Description: hy - Lisp (s-expression) based frontend to Python (metapackage) python-hy - Lisp (s-expression) based frontend to Python python3-hy - Lisp (s-expression) based frontend to Python 3 Closes: 805204 Changes: hy (0.11.0-4) unstable; urgency=medium . * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). Checksums-Sha1: cbb00b96ba8ad4f27f8f6f6ceb626f0857c1d985 2170 hy_0.11.0-4.dsc 802ebce0dc09000a243cf809c922d5f0e1af90f0 7536 hy_0.11.0-4.debian.tar.xz Checksums-Sha256: 2c91c414f8c7a0556372c94301b8786801a05b29aafeceb2e308e037d47d5ddc 2170 hy_0.11.0-4.dsc 27610d4e31645bc888c633881082270917aedd3443e36031a0030d3dae6f7380 7536 hy_0.11.0-4.debian.tar.xz Files: 42d61a06f37db6f2d2fc6c35b2d4e683 2170 python optional hy_0.11.0-4.dsc aa8bfae41ef33a85e0f08f21e0a5e67b 7536 python optional hy_0.11.0-4.debian.tar.xz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJWSrgkAAoJEANqnCW/NX3UEjQP/ikiMZWvhco6jz9ObT/Q1FbK fjoaZbOBLAoP/kBD4m9s/GiBNb0mHAtn3186uh5ZbXw7NEz9hfQeNAL2qHOqYsT3 7Ha31kwjV9ZfSLbu6giCImoBPBV6kqn904QVjHiSJ/d08PdEgDPcOrDYUBPYH+O0 BmHDlWb9mBRIMIjXl6HtZIMvJ1adU613h3T6C4VMExV1YRGaD4UlUxUkfdKMZ0kx GUNsyARKxXbMMr9uqnmDp3K85U6BynOiZ8aLnzWjVKDpXPQOK/3n4sfN6iNJF+9K nl26axBfgoj/kvNLXGOR+rpGz0IBE7QpecVh7eFUb87i4En+lZi9pl0+hC/2n+Xy vShNXphZZQ444P5CMX5s8OK0IbWl1wVe0OCjcQhW9juOIGdn3bxWJ68HaIbw5Y+V TZcmHxjJJbO+D+ng3OHqCg1tooy1dAeMZlRwsgYyDtx0Rhd7gZKzx5NkuWlBOdSH P+FikrKFHW6rvvO0esWqgm7GuBDrMrsPgU9T4UZ1sOOwsBdWTgp9QWceFIrrptX0 C/hiBXZkJP/cXueZQ38GDny6ahuR5HDmNwHhvV/EuZ28GOdKzCxcOCyhoNI2xgOg A8Ija2WnFdScMVRuMxDxK8yMdy1/BtZQKV6uzSt7ebHfPcUopBM4yARM8C90EbJD /FevdZ9cGw/0bCyun86t =dtCZ -----END PGP SIGNATURE----- ` // }}} func isok(t *testing.T, err error) { if err != nil && err != io.EOF { log.Printf("Error! Error is not nil! - %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ func TestBasicParagraphReader(t *testing.T) { // Reader {{{ reader, err := control.NewParagraphReader(strings.NewReader(`Para: one Para: two Para: three `), nil) // }}} isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, len(blocks) == 3) } func TestMultipleNewlines(t *testing.T) { // Reader {{{ reader, err := control.NewParagraphReader(strings.NewReader(`Para: one Para: two Para: three `), nil) // }}} isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, len(blocks) == 3) } func TestParagraphSet(t *testing.T) { para := control.Paragraph{ Order: nil, Values: map[string]string{}, } // Setting a key:value updates both order and values. para.Set("yankee", "doodle") assert(t, len(para.Order) == 1) assert(t, para.Order[0] == "yankee") assert(t, para.Values["yankee"] == "doodle") // Adding a second key:value updates both order and values. para.Set("british", "redcoat") assert(t, len(para.Order) == 2) assert(t, para.Order[0] == "yankee") assert(t, para.Values["yankee"] == "doodle") assert(t, para.Order[1] == "british") assert(t, para.Values["british"] == "redcoat") // Updating a previously existing key leaves order untouched. para.Set("yankee", "candle") assert(t, len(para.Order) == 2) assert(t, para.Order[0] == "yankee") assert(t, para.Values["yankee"] == "candle") assert(t, para.Order[1] == "british") assert(t, para.Values["british"] == "redcoat") } func TestWhitespacePrefixedLines(t *testing.T) { // Reader {{{ reader, err := control.NewParagraphReader(strings.NewReader(`Key1: one continuation Key2: two tabbed continuation `), nil) // }}} isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, blocks[0].Values["Key1"] == "one\n continuation\n") assert(t, blocks[0].Values["Key2"] == "two\ntabbed continuation\n") } func TestCommentLines(t *testing.T) { // Reader {{{ reader, err := control.NewParagraphReader(strings.NewReader(`Key1: one # comment Key2: two `), nil) // }}} isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, blocks[0].Values["Key1"] == "one") assert(t, blocks[0].Values["Key2"] == "two") } func TestTrailingTwoCharacterNewlines(t *testing.T) { // Reader {{{ reader, err := control.NewDecoder(strings.NewReader("Key1: one\r\nKey2: two\r\n\r\n"), nil) // }}} isok(t, err) type TestStruct struct { Key1 string Key2 string } testStruct := TestStruct{} err = reader.Decode(&testStruct) isok(t, err) assert(t, testStruct.Key1 == "one") assert(t, testStruct.Key2 == "two") } func TestOpenPGPParagraphReader(t *testing.T) { reader, err := control.NewParagraphReader(strings.NewReader(signedParagraph), nil) isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, len(blocks) == 1) } func TestEmptyKeyringOpenPGPParagraphReader(t *testing.T) { keyring := openpgp.EntityList{} // Reader {{{ _, err := control.NewParagraphReader(strings.NewReader(`-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 1.8 Date: Mon, 16 Nov 2015 21:15:55 -0800 Source: hy Binary: hy python-hy python3-hy Architecture: source Version: 0.11.0-4 Distribution: unstable Urgency: medium Maintainer: Tianon Gravi Changed-By: Tianon Gravi Description: hy - Lisp (s-expression) based frontend to Python (metapackage) python-hy - Lisp (s-expression) based frontend to Python python3-hy - Lisp (s-expression) based frontend to Python 3 Closes: 805204 Changes: hy (0.11.0-4) unstable; urgency=medium . * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). Checksums-Sha1: cbb00b96ba8ad4f27f8f6f6ceb626f0857c1d985 2170 hy_0.11.0-4.dsc 802ebce0dc09000a243cf809c922d5f0e1af90f0 7536 hy_0.11.0-4.debian.tar.xz Checksums-Sha256: 2c91c414f8c7a0556372c94301b8786801a05b29aafeceb2e308e037d47d5ddc 2170 hy_0.11.0-4.dsc 27610d4e31645bc888c633881082270917aedd3443e36031a0030d3dae6f7380 7536 hy_0.11.0-4.debian.tar.xz Files: 42d61a06f37db6f2d2fc6c35b2d4e683 2170 python optional hy_0.11.0-4.dsc aa8bfae41ef33a85e0f08f21e0a5e67b 7536 python optional hy_0.11.0-4.debian.tar.xz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJWSrgkAAoJEANqnCW/NX3UEjQP/ikiMZWvhco6jz9ObT/Q1FbK fjoaZbOBLAoP/kBD4m9s/GiBNb0mHAtn3186uh5ZbXw7NEz9hfQeNAL2qHOqYsT3 7Ha31kwjV9ZfSLbu6giCImoBPBV6kqn904QVjHiSJ/d08PdEgDPcOrDYUBPYH+O0 BmHDlWb9mBRIMIjXl6HtZIMvJ1adU613h3T6C4VMExV1YRGaD4UlUxUkfdKMZ0kx GUNsyARKxXbMMr9uqnmDp3K85U6BynOiZ8aLnzWjVKDpXPQOK/3n4sfN6iNJF+9K nl26axBfgoj/kvNLXGOR+rpGz0IBE7QpecVh7eFUb87i4En+lZi9pl0+hC/2n+Xy vShNXphZZQ444P5CMX5s8OK0IbWl1wVe0OCjcQhW9juOIGdn3bxWJ68HaIbw5Y+V TZcmHxjJJbO+D+ng3OHqCg1tooy1dAeMZlRwsgYyDtx0Rhd7gZKzx5NkuWlBOdSH P+FikrKFHW6rvvO0esWqgm7GuBDrMrsPgU9T4UZ1sOOwsBdWTgp9QWceFIrrptX0 C/hiBXZkJP/cXueZQ38GDny6ahuR5HDmNwHhvV/EuZ28GOdKzCxcOCyhoNI2xgOg A8Ija2WnFdScMVRuMxDxK8yMdy1/BtZQKV6uzSt7ebHfPcUopBM4yARM8C90EbJD /FevdZ9cGw/0bCyun86t =dtCZ -----END PGP SIGNATURE----- `), &keyring) // }}} notok(t, err) } func TestLineWrapping(t *testing.T) { reader, err := control.NewParagraphReader(strings.NewReader(signedParagraph), nil) isok(t, err) el, err := reader.Next() isok(t, err) assert(t, el.Values["Changes"] == `hy (0.11.0-4) unstable; urgency=medium * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). `) } // vim: foldmethod=marker go-debian-0.16.0/deb/000077500000000000000000000000001452017266000141605ustar00rootroot00000000000000go-debian-0.16.0/deb/ar.go000066400000000000000000000114751452017266000151210ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package deb // import "pault.ag/go/debian/deb" import ( "fmt" "io" "strconv" "strings" ) // ArEntry {{{ // Container type to access the different parts of a Debian `ar(1)` Archive. // // The most interesting parts of this are the `Name` attribute, Data // `io.Reader`, and the Tarfile helpers. This will allow the developer to // programmatically inspect the information inside without forcing her to // unpack the .deb to the filesystem. type ArEntry struct { Name string Timestamp int64 OwnerID int64 GroupID int64 FileMode string Size int64 Data *io.SectionReader } // }}} // Ar {{{ // This struct encapsulates a Debian .deb flavored `ar(1)` archive. type Ar struct { in io.ReaderAt offset int64 } // LoadAr {{{ // Load an Ar archive reader from an io.ReaderAt func LoadAr(in io.ReaderAt) (*Ar, error) { offset, err := checkAr(in) if err != nil { return nil, err } debFile := Ar{in: in, offset: offset} return &debFile, nil } // }}} // Next {{{ // Function to jump to the next file in the Debian `ar(1)` archive, and // return the next member. func (d *Ar) Next() (*ArEntry, error) { line := make([]byte, 60) count, err := d.in.ReadAt(line, d.offset) if err != nil { return nil, err } if count == 1 && line[0] == '\n' { return nil, io.EOF } if count != 60 { return nil, fmt.Errorf("Caught a short read at the end") } entry, err := parseArEntry(line) if err != nil { return nil, err } entry.Data = io.NewSectionReader(d.in, d.offset+int64(count), entry.Size) d.offset += int64(count) + entry.Size + (entry.Size % 2) return entry, nil } // }}} // toDecimal {{{ // Take a byte array, and return an int64 func toDecimal(input []byte) (int64, error) { stream := strings.TrimSpace(string(input)) out, err := strconv.Atoi(stream) return int64(out), err } // }}} // }}} // AR Format Hackery {{{ // parseArEntry {{{ // Take the AR format line, and create an ArEntry (without .Data set) // to be returned to the user later. // // +------------------------------------------------------- // | Offset Length Name Format // +------------------------------------------------------- // | 0 16 File name ASCII // | 16 12 File modification timestamp Decimal // | 28 6 Owner ID Decimal // | 34 6 Group ID Decimal // | 40 8 File mode Octal // | 48 10 File size in bytes Decimal // | 58 2 File magic 0x60 0x0A // func parseArEntry(line []byte) (*ArEntry, error) { if len(line) != 60 { return nil, fmt.Errorf("Malformed file entry line length") } if line[58] != 0x60 && line[59] != 0x0A { return nil, fmt.Errorf("Malformed file entry line endings") } entry := ArEntry{ Name: strings.TrimSuffix(strings.TrimSpace(string(line[0:16])), "/"), FileMode: strings.TrimSpace(string(line[48:58])), } for target, value := range map[*int64][]byte{ &entry.Timestamp: line[16:28], &entry.OwnerID: line[28:34], &entry.GroupID: line[34:40], &entry.Size: line[48:58], } { intValue, err := toDecimal(value) if err != nil { return nil, err } *target = intValue } return &entry, nil } // }}} // checkAr {{{ // Given a brand spank'n new os.File entry, go ahead and make sure it looks // like an `ar(1)` archive, and not some random file. func checkAr(reader io.ReaderAt) (int64, error) { header := make([]byte, 8) if _, err := reader.ReadAt(header, 0); err != nil { return 0, err } if string(header) != "!\n" { return 0, fmt.Errorf("Header doesn't look right!") } return int64(len(header)), nil } // }}} // }}} // vim: foldmethod=marker go-debian-0.16.0/deb/ar_test.go000066400000000000000000000031721452017266000161530ustar00rootroot00000000000000package deb_test import ( "io" "io/ioutil" "log" "os" "testing" "pault.ag/go/debian/deb" ) /* * */ func isok(t *testing.T, err error) { if err != nil && err != io.EOF { log.Printf("Error! Error is not nil! %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ // As discussed in testdata/README.md, `multi_archive.a` is taken from Blake // Smith's ar project. Some of the data below follows. func TestAr(t *testing.T) { file, err := os.Open("testdata/multi_archive.a") isok(t, err) ar, err := deb.LoadAr(file) isok(t, err) firstEntry, err := ar.Next() isok(t, err) assert(t, firstEntry.Name == `hello.txt`) assert(t, firstEntry.Timestamp == 1361157466) assert(t, firstEntry.OwnerID == 501) assert(t, firstEntry.GroupID == 20) firstContent, err := ioutil.ReadAll(firstEntry.Data) isok(t, err) assert(t, firstEntry.Size == int64(len(firstContent))) assert(t, string(firstContent) == "Hello world!\n") secondEntry, err := ar.Next() isok(t, err) secondContent, err := ioutil.ReadAll(secondEntry.Data) isok(t, err) assert(t, secondEntry.Size == int64(len(secondContent))) assert(t, string(secondContent) == "I love lamp.\n") // Now, test that we can rewind and reread the first file even after // reading the second one. _, err = firstEntry.Data.Seek(0, 0) isok(t, err) firstRereadContent, err := ioutil.ReadAll(firstEntry.Data) isok(t, err) assert(t, string(firstContent) == string(firstRereadContent)) } go-debian-0.16.0/deb/deb.go000066400000000000000000000157011452017266000152450ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package deb // import "pault.ag/go/debian/deb" import ( "archive/tar" "bufio" "fmt" "io" "os" "path" "strings" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) // Control {{{ // Binary Control format, as exists in the Control section of the `.deb` // archive, as defined in Debian Policy, section 5.3, entitled "Binary // package control files -- DEBIAN/control". type Control struct { control.Paragraph Package string `required:"true"` Source string Version version.Version `required:"true"` Architecture dependency.Arch `required:"true"` Maintainer string `required:"true"` InstalledSize int `control:"Installed-Size"` MultiArch string `control:"Multi-Arch"` Depends dependency.Dependency Recommends dependency.Dependency Suggests dependency.Dependency Breaks dependency.Dependency Replaces dependency.Dependency BuiltUsing dependency.Dependency `control:"Built-Using"` Section string Priority string Homepage string Description string `required:"true"` } func (c Control) SourceName() string { if c.Source == "" { return c.Package } return c.Source } // }}} // Deb {{{ // Container struct to encapsulate a `.deb` file on disk. This contains // information about what exactly we're looking at. When loaded. information // regarding the Control file is read from the control section of the .deb, // and Unmarshaled into the `Control` member of the Struct. type Deb struct { Control Control Path string Data *tar.Reader Closer io.Closer ControlExt string DataExt string ArContent map[string]*ArEntry } func (deb *Deb) Close() error { if deb.Closer != nil { return deb.Closer.Close() } return nil } // Load {{{ // Load {{{ // Given a reader, and the file path to the file (for use in the Deb later) // create a deb.Deb object, and populate the Control and Data members. // It is the caller's responsibility to call Close() when done. func Load(in io.ReaderAt, pathname string) (*Deb, error) { ar, err := LoadAr(in) if err != nil { return nil, err } deb, err := loadDeb(ar) if err != nil { return nil, err } deb.Path = pathname return deb, nil } // }}} // LoadFile {{{ type Closer func() error type closerAdapter struct { closeFunc Closer } func (c *closerAdapter) Close() error { return c.closeFunc() } func LoadFile(path string) (*Deb, Closer, error) { fd, err := os.Open(path) if err != nil { return nil, nil, err } debFile, err := Load(fd, path) if err != nil { fd.Close() return nil, nil, err } dataCloser := debFile.Closer // Replace debFile.Closer function with another one that also closes fd. // We do this to preserve backwards compatibility, ensuring users already invoking the returned // closeFunc do not need to also call Close() on debFile. Earlier versions of this library did // not require Close() to be invoked on debFile. closeFunc := func() error { err1 := dataCloser.Close() err2 := fd.Close() if err1 != nil { return err1 } return err2 } debFile.Closer = &closerAdapter{closeFunc} return debFile, closeFunc, nil } // }}} // Debian .deb Loader Internals {{{ // Top-level .deb loader dispatch on Version {{{ // Look for the debian-binary member and figure out which version to read // it as. Return the newly created .deb struct. func loadDeb(archive *Ar) (*Deb, error) { contents := make(map[string]*ArEntry) for { member, err := archive.Next() if err == io.EOF { break } if err != nil { return nil, err } contents[member.Name] = member } member, ok := contents["debian-binary"] if !ok { return nil, fmt.Errorf("Archive contains no binary version member!") } reader := bufio.NewReader(member.Data) version, err := reader.ReadString('\n') if err != nil { return nil, err } switch version { case "2.0\n": return loadDeb2(contents) default: return nil, fmt.Errorf("Unknown binary version: '%s'", version) } } // }}} // Debian .deb format 2.0 {{{ // Top-level .deb loader dispatch for 2.0 {{{ // Load a Debian 2.x series .deb - track down the control and data members. func loadDeb2(archive map[string]*ArEntry) (*Deb, error) { ret := Deb{ArContent: archive} if err := loadDeb2Control(archive, &ret); err != nil { return nil, err } if err := loadDeb2Data(archive, &ret); err != nil { return nil, err } return &ret, nil } // }}} // Decode .deb 2.0 control data into the struct {{{ // Load a Debian 2.x series .deb control file and write it out to // the deb.Deb.Control member. func loadDeb2Control(archive map[string]*ArEntry, deb *Deb) error { for _, member := range archive { if strings.HasPrefix(member.Name, "control.") { archive, closer, err := member.Tarfile() if err != nil { return err } deb.ControlExt = member.Name[8:len(member.Name)] for { member, err := archive.Next() if err != nil { closer.Close() return err } if path.Clean(member.Name) == "control" { err1 := control.Unmarshal(&deb.Control, archive) err2 := closer.Close() if err1 != nil { return err1 } return err2 } } closer.Close() } } return fmt.Errorf("Missing or out of order .deb member 'control'") } // }}} // Decode .deb 2.0 package data into the struct {{{ // Load a Debian 2.x series .deb data file and write it out to // the deb.Deb.Data member. func loadDeb2Data(archive map[string]*ArEntry, deb *Deb) error { for _, member := range archive { if strings.HasPrefix(member.Name, "data.") { archive, closer, err := member.Tarfile() if err != nil { return err } deb.DataExt = member.Name[5:len(member.Name)] deb.Data = archive deb.Closer = closer return nil } } return fmt.Errorf("Missing or out of order .deb member 'data'") } // }}} // }}} }}} }}} }}} // vim: foldmethod=marker go-debian-0.16.0/deb/doc.go000066400000000000000000000015601452017266000152560ustar00rootroot00000000000000/* This module provides an API to access and programmatically process Debian `.deb` archives on disk. Debian files, at a high level, are `ar(1)` archives, which contain a few sections, most notably the `control` member, which contains information about the Debian package itself, and the `data` member, which contains the actual contents of the files, as they should be written out on disk. Here's a trivial example, which will print out the Package name for a `.deb` archive given on the command line: package main import ( "log" "os" "pault.ag/go/debian/deb" ) func main() { path := os.Args[1] fd, err := os.Open(path) if err != nil { panic(err) } defer fd.Close() debFile, err := deb.Load(fd, path) if err != nil { panic(err) } log.Printf("Package: %s\n", debFile.Control.Package) } */ package deb // import "pault.ag/go/debian/deb" go-debian-0.16.0/deb/sigcheck.go000066400000000000000000000022431452017266000162700ustar00rootroot00000000000000package deb // import "pault.ag/go/debian/deb" import ( "fmt" "io" "strings" "golang.org/x/crypto/openpgp" ) const ( SigTypeArchive = `archive` SigTypeMaint = `maint` SigTypeOrigin = `origin` ) func (deb *Deb) CheckDebsig(validKeys openpgp.EntityList, sigType string) (signer *openpgp.Entity, err error) { sig, ok := deb.ArContent[`_gpg`+sigType] if !ok { return nil, fmt.Errorf("no signature of type %v present", sigType) } binaryFlag, ok := deb.ArContent[`debian-binary`] if !ok { return nil, fmt.Errorf("archive does not contain a debian-binary flag") } var control, data *ArEntry for _, member := range deb.ArContent { if strings.HasPrefix(member.Name, "control.") { control = member if data != nil { break } } else if strings.HasPrefix(member.Name, "data.") { data = member if control != nil { break } } } if control == nil || data == nil { return nil, fmt.Errorf("unable to find signed data") } binaryFlag.Data.Seek(0, 0) control.Data.Seek(0, 0) data.Data.Seek(0, 0) signedData := io.MultiReader(binaryFlag.Data, control.Data, data.Data) return openpgp.CheckDetachedSignature(validKeys, signedData, sig.Data) } go-debian-0.16.0/deb/tarfile.go000066400000000000000000000074601452017266000161440ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package deb // import "pault.ag/go/debian/deb" import ( "fmt" "io" "path/filepath" "strings" "archive/tar" "compress/bzip2" "compress/gzip" "github.com/kjk/lzma" "github.com/klauspost/compress/zstd" "github.com/xi2/xz" ) // known compression types {{{ type DecompressorFunc func(io.Reader) (io.ReadCloser, error) func gzipNewReader(r io.Reader) (io.ReadCloser, error) { return gzip.NewReader(r) } func xzNewReader(r io.Reader) (io.ReadCloser, error) { reader, err := xz.NewReader(r, 0) if err != nil { return nil, err } return io.NopCloser(reader), nil } func lzmaNewReader(r io.Reader) (io.ReadCloser, error) { return lzma.NewReader(r), nil } func bzipNewReader(r io.Reader) (io.ReadCloser, error) { return io.NopCloser(bzip2.NewReader(r)), nil } func zstdNewReader(r io.Reader) (io.ReadCloser, error) { reader, err := zstd.NewReader(r) if err != nil { return nil, err } return io.NopCloser(reader), err } // For the authoritative list of supported file formats, see // https://manpages.debian.org/unstable/dpkg-dev/deb.5 // zstd-compressed packages are not yet (08-2021) officially supported by Debian, but they // are used by Ubuntu. var knownCompressionAlgorithms = map[string]DecompressorFunc{ ".gz": gzipNewReader, ".bz2": bzipNewReader, ".xz": xzNewReader, ".lzma": lzmaNewReader, ".zst": zstdNewReader, } // DecompressorFn returns a decompressing reader for the specified reader and its // corresponding file extension ext. func DecompressorFor(ext string) DecompressorFunc { if fn, ok := knownCompressionAlgorithms[ext]; ok { return fn } return func(r io.Reader) (io.ReadCloser, error) { return io.NopCloser(r), nil } // uncompressed file or unknown compression scheme } // }}} // IsTarfile {{{ // Check to see if the given ArEntry is, in fact, a Tarfile. This method // will return `true` for files that have `.tar.*` or `.tar` suffix. // // This will return `false` for the `debian-binary` file. If this method // returns `true`, the `.Tarfile()` method will be around to give you a // tar.Reader back. func (e *ArEntry) IsTarfile() bool { ext := filepath.Ext(e.Name) return ext == ".tar" || filepath.Ext(strings.TrimSuffix(e.Name, ext)) == ".tar" } // }}} // Tarfile {{{ // `.Tarfile()` will return a `tar.Reader` created from the ArEntry member // to allow further inspection of the contents of the `.deb`. func (e *ArEntry) Tarfile() (*tar.Reader, io.Closer, error) { if !e.IsTarfile() { return nil, nil, fmt.Errorf("%s appears to not be a tarfile", e.Name) } readCloser, err := DecompressorFor(filepath.Ext(e.Name))(e.Data) if err != nil { return nil, nil, err } return tar.NewReader(readCloser), readCloser, nil } // }}} // vim: foldmethod=marker go-debian-0.16.0/deb/testdata/000077500000000000000000000000001452017266000157715ustar00rootroot00000000000000go-debian-0.16.0/deb/testdata/README.md000066400000000000000000000010761452017266000172540ustar00rootroot00000000000000Test data for ar parsing and debsig-style signature checking are taken from projects with which we are attempting to remain compatible. - Test data for debsig-type signature checking are taken from the GPLv2-licensed `debsig-verify` project, last modified as of 2020-12-24. - Test data for ar parsing are taken from the MIT-licensed ar library by Blake Smith, at https://github.com/blakesmith/ar, last modified as of 2019-02-19. None of this data is included in compiled binaries, so the licensing terms for binaries compiled with or from go-debian are not modified. go-debian-0.16.0/deb/testdata/keyrings/000077500000000000000000000000001452017266000176245ustar00rootroot00000000000000go-debian-0.16.0/deb/testdata/keyrings/7CD73F641E04EC2D/000077500000000000000000000000001452017266000215635ustar00rootroot00000000000000go-debian-0.16.0/deb/testdata/keyrings/7CD73F641E04EC2D/debian-debsig.gpg000066400000000000000000000040501452017266000247360ustar00rootroot000000000000007+˃ V}\O`&0uiAXkt8x6^Lcv!R3Lj5zsJ?1uZ@>$_4 J"~ѻlq:MW-NkhO>;w)lT F:s2Y߳Bqi۠7)5 ]ybdf(#Ď3כ$8ޙll~A !Ben Collins U7  |?d-*.jq}lW42Qqzn/> #-a#Ben Collins U7X  |?d-t oZӾq40;nH*Dm<҈F8_ Ћ,m *{BƮJzkPQLmKNBen Collins U7  |?d-%\nƌr=w&6ZpR·\ӈF8_ Ћ,m k$1V9pb  |?d-C1|ٟzJ[x#L;֨/&Ben Collins F8_ Ћ,m F8_ Ћ,m @wt5Ԩ&y(W&DOe&6NkC޹;x]7;*=ZAf aOgEV#~]S5 dD\, Ci:@pǎ - 62=YwRϭ 0)Ə ޖ 3]t\ȍ! \{pF9F[ sTCNex/S;}b=#ޟ2q9YDQsRU7  |?d-^yej´YjEӬYm:,7F8^ o JZIϞ͙pFtuaKT?1Ykq2fݲ 7j/PlfwO:3~~邒eeJ#:f鷑bSF|F=@0p@3YWUfS&PjdllI^sR>afWD7`QˌY6QtfD_Ovw/<"Wo-[ˉvkv 03qq@K1&G:X-\;>k0_+NF q؆4 ӭf[O#RvF7j |?d-W4Z6n.[\Bg]LLhe ]go-debian-0.16.0/deb/testdata/keyrings/FAD46790DE88C7E2/000077500000000000000000000000001452017266000216025ustar00rootroot00000000000000go-debian-0.16.0/deb/testdata/keyrings/FAD46790DE88C7E2/pubring.gpg000066400000000000000000000023351452017266000237520ustar00rootroot00000000000000 ThoWѻNgRbO #/5 G!!{#n].vY_R\@ SYeLZB2G7k/r֯H3ٷdO/U6Hs|X-=U@"}*2|YctBȒoz[npк%D\)@?Ѽv@4pP B>d~ 6kXiGg4 M VDebsig Integration Test Key (This key is for *testing* only) 8"Th   gވ9;)M/;fģ4ߝS=iߥ*@FeVqcS&ɉ/B!nRIV`' %iknB'[$#&'dEV,։CHz88!G[N qf5 2coמo)M. c8}s!<9?p@w~ 4ڮޗ ntJ Z̙5[(q4S0W Thǁˬ7YưxfH OGq8 I"fՋTu+9׆!^E&$ VTϯ Cjrq-qR@ ~c.Xuƺ+HF53~:a)>Zgf%V_:qfWeqO8ص˓_9ILt:[WǁS,>q#vUGE$wk"8rωfX̼ߕ9JJv Th gވ*x(#5ThYĩQ+ÿDzmUp=.l%`N@̒^ڬ %XE |_&ߚ3%+h7fs.cNͽ-[vt"%+CUs4okAӨEnM)43)x*9yHۚZ#bqMEkgI B91NԷU |a$$9ըQE8D,zPR#2lw plgo-debian-0.16.0/deb/testdata/keyrings/FAD46790DE88C7E2/secring.gpg000066400000000000000000000047631452017266000237450ustar00rootroot00000000000000ThoWѻNgRbO #/5 G!!{#n].vY_R\@ SYeLZB2G7k/r֯H3ٷdO/U6Hs|X-=U@"}*2|YctBȒoz[npк%D\)@?Ѽv@4pP B>d~ 6kXiGg4 M HU}찷r ^](+dW$ޟ eY5LE0yZ/3WN%Bȃuj%?Vcj GC"gLaKe(B 1`)zDs۴BY7~b@Wó9$!adƳ``l(À)?\di5o<=:bCd߅!*lBuC"̠GSuJcuς/&d*"pa7ߪwjٳ/F*.v*-Ś4KNHuή>XA~6ǗS&Su'qaoQ;#IdDX~ +_iNaHQ2citJx[o7G*>jb`Qc@ND#[[|=kZר|V 6z—Pp4+4-?أ9xZWh Op?1:JCLW'QZE(GD8U7_Q%?%z[R04>%+){d` hIJ֡NKS& _~+nSࠂw90f&iU;*Eׇ.Rd[rBVDebsig Integration Test Key (This key is for *testing* only) 8"Th   gވ9;)M/;fģ4ߝS=iߥ*@FeVqcS&ɉ/B!nRIV`' %iknB'[$#&'dEV,։CHz88!G[N qf5 2coמo)M. c8}s!<9?p@w~ 4ڮޗ ntJ Z̙5[(q4S0WThǁˬ7YưxfH OGq8 I"fՋTu+9׆!^E&$ VTϯ Cjrq-qR@ ~c.Xuƺ+HF53~:a)>Zgf%V_:qfWeqO8ص˓_9ILt:[WǁS,>q#vUGE$wk"8rωfX̼ߕ9JJvaq_fSn-`RD)ן&˰A,N[߀¡g- 賻F͈E4GD_B{7 I}ǣ'l2L7 sNSt# pز8ŗ7S Œ|{I {In'x{H!y;S|V{A0_a UNC@ȏ^q&5CF)L·@,g2d)͏NW<5Qp&ԩ|k2.wnegt;U6|5AWyGzғ>{:<*Em]mw'P0 :Ww](?7NygU;S{5LfOvEfLyEˊAw>$KsQaYZ).%!ԀBѯ܉C8='=rU1TS~b*RkM6 dd2?c/? `ջ #IJy9*M EֆLŃw{7T `R{nDHH}ô|O8xEFGL6@.Ӈd_34?~XTnc\J& Th gވ*x(#5ThYĩQ+ÿDzmUp=.l%`N@̒^ڬ %XE |_&ߚ3%+h7fs.cNͽ-[vt"%+CUs4okAӨEnM)43)x*9yHۚZ#bqMEkgI B91NԷU |a$$9ըQE8D,zPR#2lw plgo-debian-0.16.0/deb/testdata/multi_archive.a000066400000000000000000000002341452017266000207650ustar00rootroot00000000000000! hello.txt 1361157466 501 20 100644 13 ` Hello world! lamp.txt 1361248906 501 20 100644 13 ` I love lamp. go-debian-0.16.0/dependency/000077500000000000000000000000001452017266000155445ustar00rootroot00000000000000go-debian-0.16.0/dependency/arch.go000066400000000000000000000101671452017266000170150ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" import ( "errors" "strings" ) /* */ type Arch struct { ABI string OS string CPU string } func ParseArchitectures(arch string) ([]Arch, error) { ret := []Arch{} arches := strings.Split(arch, " ") for _, el := range arches { el := strings.Trim(el, " \t\n\r") if el == "" { continue } arch, err := ParseArch(el) if err != nil { return nil, err } ret = append(ret, *arch) } return ret, nil } func (arch *Arch) UnmarshalControl(data string) error { return parseArchInto(arch, data) } func ParseArch(arch string) (*Arch, error) { ret := &Arch{ ABI: "any", OS: "any", CPU: "any", } return ret, parseArchInto(ret, arch) } /* */ func parseArchInto(ret *Arch, arch string) error { /* May be in the following form: * `any` (implicitly any-any-any) * kfreebsd-any (implicitly any-kfreebsd-any) * kfreebsd-amd64 (implicitly any-kfreebsd-any) * bsd-openbsd-i386 */ flavors := strings.SplitN(arch, "-", 3) switch len(flavors) { case 1: flavor := flavors[0] /* OK, we've got a single guy like `any` or `amd64` */ switch flavor { case "all", "any": ret.ABI = flavor ret.OS = flavor ret.CPU = flavor default: /* right, so we've got something like `amd64`, which is implicitly * gnu-linux-amd64. Confusing, I know. */ ret.ABI = "gnu" ret.OS = "linux" ret.CPU = flavor } case 2: /* Right, this is something like kfreebsd-amd64, which is implicitly * gnu-kfreebsd-amd64 */ ret.OS = flavors[0] ret.CPU = flavors[1] case 3: /* This is something like bsd-openbsd-amd64 */ ret.ABI = flavors[0] ret.OS = flavors[1] ret.CPU = flavors[2] default: return errors.New("Hurm, no idea what happened here") } return nil } /* */ func (set *ArchSet) Matches(other *Arch) bool { /* If [!amd64 sparc] matches gnu-linux-any */ if len(set.Architectures) == 0 { /* We're not a thing. Always true. */ return true } not := set.Not for _, el := range set.Architectures { if el.Is(other) { /* For each arch; check if it matches. If it does, then * return true (unless we're negated) */ return !not } } /* Otherwise, let's return false (unless we're negated) */ return not } /* */ func (arch *Arch) IsWildcard() bool { if arch.CPU == "all" { return false } if arch.ABI == "any" || arch.OS == "any" || arch.CPU == "any" { return true } return false } /* */ func (arch *Arch) Is(other *Arch) bool { if arch.IsWildcard() && other.IsWildcard() { /* We can't compare wildcards to other wildcards. That's just * insanity. We always need a concrete arch. Not even going to try. */ return false } else if arch.IsWildcard() { /* OK, so we're a wildcard. Let's defer to the other * struct to deal with this */ return other.Is(arch) } if (arch.CPU == other.CPU || (arch.CPU != "all" && other.CPU == "any")) && (arch.OS == other.OS || other.OS == "any") && (arch.ABI == other.ABI || other.ABI == "any") { return true } return false } // vim: foldmethod=marker go-debian-0.16.0/dependency/arch_test.go000066400000000000000000000055711452017266000200570ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" ) /* * */ func TestArchBasics(t *testing.T) { arch, err := dependency.ParseArch("amd64") isok(t, err) assert(t, arch.CPU == "amd64") assert(t, arch.ABI == "gnu") assert(t, arch.OS == "linux") } /* */ func TestArchCompareBasics(t *testing.T) { arch, err := dependency.ParseArch("amd64") isok(t, err) equivs := []string{ "gnu-linux-amd64", "linux-amd64", "linux-any", "any", "gnu-linux-any", } for _, el := range equivs { other, err := dependency.ParseArch(el) isok(t, err) assert(t, arch.Is(other)) assert(t, other.Is(arch)) } unequivs := []string{ "gnu-linux-all", "all", "gnuu-linux-amd64", "gnu-linuxx-amd64", "gnu-linux-amd644", } for _, el := range unequivs { other, err := dependency.ParseArch(el) isok(t, err) assert(t, !arch.Is(other)) assert(t, !other.Is(arch)) } } func TestArchCompareAllAny(t *testing.T) { all, err := dependency.ParseArch("all") if err != nil { t.Fatal(err) } any, err := dependency.ParseArch("any") if err != nil { t.Fatal(err) } if all.Is(any) { t.Fatalf("arch:all unexpectedly is arch:any") } if any.Is(all) { t.Fatalf("arch:all unexpectedly is arch:any") } } /* */ func TestArchSetCompare(t *testing.T) { dep, err := dependency.Parse("foo [amd64], bar [!sparc]") isok(t, err) iAm, err := dependency.ParseArch("amd64") isok(t, err) fooArch := dep.Relations[0].Possibilities[0].Architectures barArch := dep.Relations[1].Possibilities[0].Architectures assert(t, fooArch.Matches(iAm)) assert(t, barArch.Matches(iAm)) iAmNot, err := dependency.ParseArch("armhf") isok(t, err) assert(t, !fooArch.Matches(iAmNot)) assert(t, barArch.Matches(iAmNot)) } // vim: foldmethod=marker go-debian-0.16.0/dependency/consts.go000066400000000000000000000024761452017266000174150ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" var ( Any = Arch{ABI: "any", OS: "any", CPU: "any"} All = Arch{ABI: "all", OS: "all", CPU: "all"} ) // vim: foldmethod=marker go-debian-0.16.0/dependency/dependency.go000066400000000000000000000051021452017266000202070ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" import ( "pault.ag/go/debian/version" ) // func (dep *Dependency) GetPossibilities(arch Arch) []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { continue } if possibility.Architectures.Matches(&arch) { possies = append(possies, possibility) break } } } return possies } // func (dep *Dependency) GetAllPossibilities() []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { continue } possies = append(possies, possibility) } } return possies } // func (dep *Dependency) GetSubstvars() []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { possies = append(possies, possibility) } } } return possies } func (v VersionRelation) SatisfiedBy(ver version.Version) bool { vVer, err := version.Parse(v.Number) if err != nil { return false } q := version.Compare(ver, vVer) switch v.Operator { case ">=": return q >= 0 case "<=": return q <= 0 case ">>": return q > 0 case "<<": return q < 0 case "=": return q == 0 } // XXX: WHAT THE SHIT return false } // vim: foldmethod=marker go-debian-0.16.0/dependency/dependency_test.go000066400000000000000000000064711452017266000212600ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) /* * */ func TestSliceParse(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) arch, err := dependency.ParseArch("amd64") isok(t, err) els := dep.GetPossibilities(*arch) assert(t, len(els) == 2) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") } func TestArchSliceParse(t *testing.T) { dep, err := dependency.Parse("foo, bar [sparc] | baz") isok(t, err) arch, err := dependency.ParseArch("amd64") isok(t, err) els := dep.GetPossibilities(*arch) assert(t, len(els) == 2) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "baz") } func TestSliceAllParse(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) els := dep.GetAllPossibilities() assert(t, len(els) == 3) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") assert(t, els[2].Name == "baz") } func TestSliceSubParse(t *testing.T) { dep, err := dependency.Parse("${foo:Depends}, foo, bar | baz, ${bar:Depends}") isok(t, err) els := dep.GetAllPossibilities() assert(t, len(els) == 3) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") assert(t, els[2].Name == "baz") els = dep.GetSubstvars() assert(t, len(els) == 2) assert(t, els[0].Name == "foo:Depends") assert(t, els[1].Name == "bar:Depends") } func TestVersionRelationSatisfiedBy(t *testing.T) { for _, test := range []struct { Operator string Number string Version string Match bool }{ {"=", "1.0.0", "1.0.0", true}, {"=", "1.0.1", "1.0.0", false}, {"=", "1.0.0", "1.0.1", false}, {"<<", "2.0", "1.0", true}, {">>", "2.0", "1.0", false}, {">>", "2.0", "3.0", true}, {"<<", "2.0", "3.0", false}, {">=", "1.0~", "1.0", true}, {">=", "1.0~", "1.0.2", true}, {">=", "1.0~", "1.0.2.3", true}, {"<=", "1.0~", "1.0", false}, {"<=", "1.0~", "1.0.2", false}, {"<=", "1.0~", "1.0.2.3", false}, } { vr := dependency.VersionRelation{ Operator: test.Operator, Number: test.Number, } v, err := version.Parse(test.Version) assert(t, err == nil) assert(t, vr.SatisfiedBy(v) == test.Match) } } // vim: foldmethod=marker go-debian-0.16.0/dependency/doc.go000066400000000000000000000011251452017266000166370ustar00rootroot00000000000000/* The Dependency module provides an interface to parse and inspect Debian Dependency relationships. Dependency | foo, bar (>= 1.0) [amd64] | baz -> Relations | -> Relation bar (>= 1.0) [amd64] | baz -> Possibilities | -> Possibility bar (>= 1.0) [amd64] | Name | -> Name bar | Version | -> Version (>= 1.0) | Architectures | -> Arch amd64 | Stages | */ package dependency // import "pault.ag/go/debian/dependency" go-debian-0.16.0/dependency/models.go000066400000000000000000000062661452017266000173700ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" // Possibilities {{{ // Arch models an architecture dependency restriction, commonly used to // restrict the relation to one some architectures. This is also usually // used in a string of many possibilities. type ArchSet struct { Not bool Architectures []Arch } // VersionRelation models a version restriction on a possibility, such as // greater than version 1.0, or less than 2.0. The values that are valid // in the Operator field are defined by section 7.1 of Debian policy. // // The relations allowed are <<, <=, =, >= and >> for strictly earlier, // earlier or equal, exactly equal, later or equal and strictly later, // respectively. // type VersionRelation struct { Number string Operator string } type Stage struct { Not bool Name string } type StageSet struct { Stages []Stage } // Possibility models a concrete Possibility that may be satisfied in order // to satisfy the Dependency Relation. Given the Dependency line: // // Depends: foo, bar | baz // // All of foo, bar and baz are Possibilities. Possibilities may come with // further restrictions, such as restrictions on Version, Architecture, or // Build Stage. // type Possibility struct { Name string Arch *Arch Architectures *ArchSet StageSets []StageSet Version *VersionRelation Substvar bool } // }}} // A Relation is a set of Possibilities that must be satisfied. Given the // Dependency line: // // Depends: foo, bar | baz // // There are two Relations, one composed of foo, and another composed of // bar and baz. type Relation struct { Possibilities []Possibility } // A Dependency is the top level type that models a full Dependency relation. type Dependency struct { Relations []Relation } func (dep *Dependency) UnmarshalControl(data string) error { ibuf := input{Index: 0, Data: data} dep.Relations = []Relation{} err := parseDependency(&ibuf, dep) return err } func (dep Dependency) MarshalControl() (string, error) { return dep.String(), nil } // vim: foldmethod=marker go-debian-0.16.0/dependency/parser.go000066400000000000000000000231271452017266000173740ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" import ( "errors" "fmt" ) // Parse a string into a Dependency object. The input should look something // like "foo, bar | baz". func Parse(in string) (*Dependency, error) { ibuf := input{Index: 0, Data: in} dep := &Dependency{Relations: []Relation{}} err := parseDependency(&ibuf, dep) if err != nil { return nil, err } return dep, nil } // input Model {{{ /* */ type input struct { Data string Index int } /* */ func (i *input) Peek() byte { if (i.Index) >= len(i.Data) { return 0 } return i.Data[i.Index] } /* */ func (i *input) Next() byte { chr := i.Peek() i.Index++ return chr } // }}} // Parse Helpers {{{ /* */ func eatWhitespace(input *input) { for { peek := input.Peek() switch peek { case '\r', '\n', ' ', '\t': input.Next() continue } break } } // }}} // Dependency Parser {{{ /* */ func parseDependency(input *input, ret *Dependency) error { eatWhitespace(input) for { peek := input.Peek() switch peek { case 0: /* EOF, yay */ return nil case ',': /* Next relation set */ input.Next() eatWhitespace(input) continue } err := parseRelation(input, ret) if err != nil { return err } } } // }}} // Relation Parser {{{ /* */ func parseRelation(input *input, dependency *Dependency) error { eatWhitespace(input) /* Clean out leading whitespace */ ret := &Relation{Possibilities: []Possibility{}} for { peek := input.Peek() switch peek { case 0, ',': /* EOF, or done with this relation! yay */ dependency.Relations = append(dependency.Relations, *ret) return nil case '|': /* Next Possi */ input.Next() eatWhitespace(input) continue } err := parsePossibility(input, ret) if err != nil { return err } } } // }}} // Possibility Parser {{{ /* */ func parsePossibility(input *input, relation *Relation) error { eatWhitespace(input) /* Clean out leading whitespace */ peek := input.Peek() if peek == '$' { /* OK, nice. So, we've got a substvar. Let's eat it. */ return parseSubstvar(input, relation) } /* Otherwise, let's punt and build it up ourselves. */ ret := &Possibility{ Name: "", Version: nil, Architectures: &ArchSet{Architectures: []Arch{}}, StageSets: []StageSet{}, Substvar: false, } for { peek := input.Peek() switch peek { case ':': err := parseMultiarch(input, ret) if err != nil { return err } continue case ' ', '(': err := parsePossibilityControllers(input, ret) if err != nil { return err } continue case ',', '|', 0: /* I'm out! */ if ret.Name == "" { return nil // e.g. trailing comma in Build-Depends } relation.Possibilities = append(relation.Possibilities, *ret) return nil } /* Not a control, let's append */ ret.Name += string(input.Next()) } } func parseSubstvar(input *input, relation *Relation) error { eatWhitespace(input) input.Next() /* Assert ch == '$' */ input.Next() /* Assert ch == '{' */ ret := &Possibility{ Name: "", Version: nil, Substvar: true, } for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before substvar finished") case '}': input.Next() relation.Possibilities = append(relation.Possibilities, *ret) return nil } ret.Name += string(input.Next()) } } /* */ func parseMultiarch(input *input, possi *Possibility) error { input.Next() /* mandated to be a : */ name := "" for { peek := input.Peek() switch peek { case ',', '|', 0, ' ', '(', '[', '<': arch, err := ParseArch(name) if err != nil { return err } possi.Arch = arch return nil default: name += string(input.Next()) } } return nil } /* */ func parsePossibilityControllers(input *input, possi *Possibility) error { for { eatWhitespace(input) /* Clean out leading whitespace */ peek := input.Peek() switch peek { case ',', '|', 0: return nil case '(': if possi.Version != nil { return errors.New( "Only one Version relation per Possibility, please!", ) } err := parsePossibilityVersion(input, possi) if err != nil { return err } continue case '[': if len(possi.Architectures.Architectures) != 0 { return errors.New( "Only one Arch relation per Possibility, please!", ) } err := parsePossibilityArchs(input, possi) if err != nil { return err } continue case '<': err := parsePossibilityStageSet(input, possi) if err != nil { return err } continue } return fmt.Errorf("Trailing garbage in a Possibility: %c", peek) } return nil } /* */ func parsePossibilityVersion(input *input, possi *Possibility) error { eatWhitespace(input) input.Next() /* mandated to be ( */ // assert ch == '(' version := VersionRelation{} err := parsePossibilityOperator(input, &version) if err != nil { return err } err = parsePossibilityNumber(input, &version) if err != nil { return err } input.Next() /* OK, let's tidy up */ // assert ch == ')' possi.Version = &version return nil } /* */ func parsePossibilityOperator(input *input, version *VersionRelation) error { eatWhitespace(input) leader := input.Next() /* may be 0 */ if leader == '=' { /* Great, good enough. */ version.Operator = "=" return nil } /* This is always one of: * >=, <=, <<, >> */ secondary := input.Next() if leader == 0 || secondary == 0 { return errors.New("Oh no. Reached EOF before Operator finished") } operator := string([]rune{rune(leader), rune(secondary)}) switch operator { case ">=", "<=", "<<", ">>": version.Operator = operator return nil } return fmt.Errorf( "Unknown Operator in Possibility Version modifier: %s", operator, ) } /* */ func parsePossibilityNumber(input *input, version *VersionRelation) error { eatWhitespace(input) for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Number finished") case ')': return nil } version.Number += string(input.Next()) } } /* */ func parsePossibilityArchs(input *input, possi *Possibility) error { eatWhitespace(input) input.Next() /* Assert ch == '[' */ for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Arch list finished") case ']': input.Next() return nil } err := parsePossibilityArch(input, possi) if err != nil { return err } } } /* */ func parsePossibilityArch(input *input, possi *Possibility) error { eatWhitespace(input) arch := "" // Exclamation marks may be prepended to each of the names. (It is not // permitted for some names to be prepended with exclamation marks while // others aren't.) hasNot := input.Peek() == '!' if hasNot { input.Next() // '!' } if len(possi.Architectures.Architectures) == 0 { possi.Architectures.Not = hasNot } else if possi.Architectures.Not != hasNot { return errors.New("Either the entire arch list needs negations, or none of it does -- no mix and match :/") } for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Arch list finished") case '!': return errors.New("You can only negate whole blocks :(") case ']', ' ': /* Let our parent deal with both of these */ archObj, err := ParseArch(arch) if err != nil { return err } possi.Architectures.Architectures = append( possi.Architectures.Architectures, *archObj, ) return nil } arch += string(input.Next()) } } /* */ func parsePossibilityStageSet(input *input, possi *Possibility) error { eatWhitespace(input) input.Next() /* Assert ch == '<' */ stageSet := StageSet{} for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before StageSet finished") case '>': input.Next() possi.StageSets = append(possi.StageSets, stageSet) return nil } err := parsePossibilityStage(input, &stageSet) if err != nil { return err } } } /* */ func parsePossibilityStage(input *input, stageSet *StageSet) error { eatWhitespace(input) stage := Stage{} for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Stage finished") case '!': input.Next() if stage.Not { return errors.New("Double-negation (!!) of a single Stage is not permitted :(") } stage.Not = !stage.Not case '>', ' ': /* Let our parent deal with both of these */ stageSet.Stages = append(stageSet.Stages, stage) return nil } stage.Name += string(input.Next()) } } // }}} // vim: foldmethod=marker go-debian-0.16.0/dependency/parser_test.go000066400000000000000000000203411452017266000204260ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency_test import ( "log" "runtime/debug" "testing" "pault.ag/go/debian/dependency" ) /* * */ func isok(t *testing.T, err error) { if err != nil { log.Printf("Error! Error is not nil! %s\n", err) debug.PrintStack() t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") debug.PrintStack() t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") debug.PrintStack() t.FailNow() } } /* * */ func TestSingleParse(t *testing.T) { dep, err := dependency.Parse("foo") isok(t, err) if dep.Relations[0].Possibilities[0].Name != "foo" { t.Fail() } } func TestMultiarchParse(t *testing.T) { dep, err := dependency.Parse("foo:amd64") isok(t, err) assert(t, dep.Relations[0].Possibilities[0].Name == "foo") assert(t, dep.Relations[0].Possibilities[0].Arch.CPU == "amd64") dep, err = dependency.Parse("foo:amd64 [amd64 sparc]") isok(t, err) assert(t, dep.Relations[0].Possibilities[0].Name == "foo") assert(t, dep.Relations[0].Possibilities[0].Arch.CPU == "amd64") assert(t, dep.Relations[0].Possibilities[0].Architectures.Architectures[0].CPU == "amd64") assert(t, dep.Relations[0].Possibilities[0].Architectures.Architectures[1].CPU == "sparc") } func TestTwoRelations(t *testing.T) { dep, err := dependency.Parse("foo, bar") isok(t, err) assert(t, len(dep.Relations) == 2) } func TestTwoPossibilities(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) assert(t, len(dep.Relations) == 2) possi := dep.Relations[1].Possibilities assert(t, len(possi) == 2) assert(t, possi[0].Name == "bar") assert(t, possi[1].Name == "baz") } func TestVersioning(t *testing.T) { dep, err := dependency.Parse("foo (>= 1.0)") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] version := possi.Version assert(t, version.Operator == ">=") assert(t, version.Number == "1.0") } func TestVersioningSkippedSpace(t *testing.T) { dep, err := dependency.Parse("foo(>= 1.0)") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] version := possi.Version assert(t, version.Operator == ">=") assert(t, version.Number == "1.0") } func TestSingleArch(t *testing.T) { dep, err := dependency.Parse("foo [arch]") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, len(arches) == 1) assert(t, arches[0].CPU == "arch") } func TestSingleNotArch(t *testing.T) { dep, err := dependency.Parse("foo [!arch]") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, len(arches) == 1) assert(t, arches[0].CPU == "arch") assert(t, possi.Architectures.Not) } func TestDoubleInvalidNotArch(t *testing.T) { _, err := dependency.Parse("foo [arch !foo]") notok(t, err) _, err = dependency.Parse("foo [!arch foo]") notok(t, err) _, err = dependency.Parse("foo [arch!foo]") notok(t, err) _, err = dependency.Parse("foo [arch!]") notok(t, err) } func TestDoubleArch(t *testing.T) { for depStr, not := range map[string]bool{ "foo [arch arch2]": false, "foo [!arch !arch2]": true, } { dep, err := dependency.Parse(depStr) isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, possi.Architectures.Not == not) assert(t, len(arches) == 2) assert(t, arches[0].CPU == "arch") assert(t, arches[1].CPU == "arch2") } } func TestVersioningOperators(t *testing.T) { opers := map[string]string{ ">=": "foo (>= 1.0)", "<=": "foo (<= 1.0)", ">>": "foo (>> 1.0)", "<<": "foo (<< 1.0)", "=": "foo (= 1.0)", } for operator, vstring := range opers { dep, err := dependency.Parse(vstring) isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] version := possi.Version assert(t, version.Operator == operator) assert(t, version.Number == "1.0") } } func TestNoComma(t *testing.T) { _, err := dependency.Parse("foo bar") notok(t, err) } func TestTwoVersions(t *testing.T) { _, err := dependency.Parse("foo (>= 1.0) (<= 2.0)") notok(t, err) } func TestTwoArchitectures(t *testing.T) { _, err := dependency.Parse("foo [amd64] [sparc]") notok(t, err) } func TestTwoStages(t *testing.T) { dep, err := dependency.Parse("foo ") isok(t, err) possi := dep.Relations[0].Possibilities[0] assert(t, len(possi.StageSets) == 2) // assert(t, len(possi.StageSets[0].Stages) == 2) assert(t, !possi.StageSets[0].Stages[0].Not) assert(t, possi.StageSets[0].Stages[0].Name == "stage1") assert(t, possi.StageSets[0].Stages[1].Not) assert(t, possi.StageSets[0].Stages[1].Name == "cross") // assert(t, len(possi.StageSets[1].Stages) == 2) assert(t, possi.StageSets[1].Stages[0].Not) assert(t, possi.StageSets[1].Stages[0].Name == "stage1") assert(t, !possi.StageSets[1].Stages[1].Not) assert(t, possi.StageSets[1].Stages[1].Name == "cross") } func TestBadVersion(t *testing.T) { vers := []string{ "foo (>= 1.0", "foo (>= 1", "foo (>= ", "foo (>=", "foo (>", "foo (", } for _, ver := range vers { _, err := dependency.Parse(ver) notok(t, err) } } func TestBadArch(t *testing.T) { vers := []string{ "foo [amd64", "foo [amd6", "foo [amd", "foo [am", "foo [a", "foo [", } for _, ver := range vers { _, err := dependency.Parse(ver) notok(t, err) } } func TestBadStages(t *testing.T) { vers := []string{ "foo <", "foo [amd64 i386] (>= 1.2:3.4~5.6-7.8~9.0) ") isok(t, err) assert(t, dep.String() == "foo:armhf [amd64 i386] (>= 1.2:3.4~5.6-7.8~9.0) ") rtDep, err := dependency.Parse(dep.String()) isok(t, err) assert(t, dep.String() == rtDep.String()) dep.Relations[0].Possibilities[0].Architectures.Not = true assert(t, dep.String() == "foo:armhf [!amd64 !i386] (>= 1.2:3.4~5.6-7.8~9.0) ") rtDep, err = dependency.Parse(dep.String()) isok(t, err) assert(t, dep.String() == rtDep.String()) } // vim: foldmethod=marker go-debian-0.16.0/dependency/string.go000066400000000000000000000063251452017266000174070ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency // import "pault.ag/go/debian/dependency" import ( "strings" ) func (a Arch) MarshalControl() (string, error) { return a.String(), nil } func (a Arch) String() string { /* ABI-OS-CPU -- gnu-linux-amd64 */ els := []string{} if a.ABI != "any" && a.ABI != "all" && a.ABI != "gnu" && a.ABI != "" { els = append(els, a.ABI) } if a.OS != "any" && a.OS != "all" && a.OS != "linux" { els = append(els, a.OS) } els = append(els, a.CPU) return strings.Join(els, "-") } func (set ArchSet) String() string { if len(set.Architectures) == 0 { return "" } not := "" if set.Not { not = "!" } arches := []string{} for _, arch := range set.Architectures { arches = append(arches, not+arch.String()) } return "[" + strings.Join(arches, " ") + "]" } func (version VersionRelation) String() string { return "(" + version.Operator + " " + version.Number + ")" } func (stage Stage) String() string { if stage.Not { return "!" + stage.Name } return stage.Name } func (stageSet StageSet) String() string { if len(stageSet.Stages) == 0 { return "" } stages := []string{} for _, stage := range stageSet.Stages { stages = append(stages, stage.String()) } return "<" + strings.Join(stages, " ") + ">" } func (possi Possibility) String() string { str := possi.Name if possi.Arch != nil { str += ":" + possi.Arch.String() } if possi.Architectures != nil { if arch := possi.Architectures.String(); arch != "" { str += " " + arch } } if possi.Version != nil { str += " " + possi.Version.String() } for _, stageSet := range possi.StageSets { if stages := stageSet.String(); stages != "" { str += " " + stages } } return str } func (relation Relation) String() string { possis := []string{} for _, possi := range relation.Possibilities { possis = append(possis, possi.String()) } return strings.Join(possis, " | ") } func (dependency Dependency) String() string { relations := []string{} for _, relation := range dependency.Relations { relations = append(relations, relation.String()) } return strings.Join(relations, ", ") } // vim: foldmethod=marker go-debian-0.16.0/dependency/string_test.go000066400000000000000000000031261452017266000204420ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" ) func TestArchString(t *testing.T) { equivs := map[string]string{ "all": "all", "any": "all", "amd64": "amd64", "gnu-linux-amd64": "amd64", "bsd-windows-i386": "bsd-windows-i386", } for _, el := range equivs { arch, err := dependency.ParseArch(el) isok(t, err) assert(t, arch.String() == equivs[el]) } } // vim: foldmethod=marker go-debian-0.16.0/go.mod000066400000000000000000000003751452017266000145410ustar00rootroot00000000000000module pault.ag/go/debian go 1.19 require ( github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d github.com/klauspost/compress v1.16.5 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 golang.org/x/crypto v0.9.0 pault.ag/go/topsort v0.1.1 ) go-debian-0.16.0/go.sum000066400000000000000000000115531452017266000145660ustar00rootroot00000000000000github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM= github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a h1:WwS7vlB5H2AtwKj1jsGwp2ZLud1x6WXRXh2fXsRqrcA= pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a/go.mod h1:INqx0ClF7kmPAMk2zVTX8DRnhZ/yaA/Mg52g8KFKE7k= pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4= pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4= go-debian-0.16.0/hashio/000077500000000000000000000000001452017266000147015ustar00rootroot00000000000000go-debian-0.16.0/hashio/compressors.go000066400000000000000000000010021452017266000176000ustar00rootroot00000000000000package hashio // import "pault.ag/go/debian/hashio" import ( "fmt" "io" "compress/gzip" ) type Compressor func(io.Writer) (io.WriteCloser, error) func gzipCompressor(in io.Writer) (io.WriteCloser, error) { return gzip.NewWriter(in), nil } var knownCompressors = map[string]Compressor{ "gz": gzipCompressor, } func GetCompressor(name string) (Compressor, error) { if compressor, ok := knownCompressors[name]; ok { return compressor, nil } return nil, fmt.Errorf("No such compressor: '%s'", name) } go-debian-0.16.0/hashio/construct.go000066400000000000000000000024661452017266000172640ustar00rootroot00000000000000package hashio // import "pault.ag/go/debian/hashio" import ( "io" ) func NewHasherWriter(hash string, target io.Writer) (io.Writer, *Hasher, error) { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } endWriter := io.MultiWriter(target, hw) return endWriter, hw, nil } func NewHasherWriters(hashes []string, target io.Writer) (io.Writer, []*Hasher, error) { hashers := []*Hasher{} writers := []io.Writer{} for _, hash := range hashes { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } hashers = append(hashers, hw) writers = append(writers, hw) } endWriter := io.MultiWriter(append(writers, target)...) return endWriter, hashers, nil } func NewHasherReader(hash string, target io.Reader) (io.Reader, *Hasher, error) { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } endReader := io.TeeReader(target, hw) return endReader, hw, nil } func NewHasherReaders(hashes []string, target io.Reader) (io.Reader, []*Hasher, error) { hashers := []*Hasher{} writers := []io.Writer{} for _, hash := range hashes { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } hashers = append(hashers, hw) writers = append(writers, hw) } endReader := io.TeeReader(target, io.MultiWriter(writers...)) return endReader, hashers, nil } go-debian-0.16.0/hashio/hash.go000066400000000000000000000017311452017266000161550ustar00rootroot00000000000000package hashio // import "pault.ag/go/debian/hashio" import ( "fmt" "hash" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" ) func GetHash(name string) (hash.Hash, error) { switch name { case "md5": return md5.New(), nil case "sha1": return sha1.New(), nil case "sha256": return sha256.New(), nil case "sha512": return sha512.New(), nil default: return nil, fmt.Errorf("Unknown algorithm: %s", name) } } func NewHasher(name string) (*Hasher, error) { hash, err := GetHash(name) if err != nil { return nil, err } hw := Hasher{ name: name, hash: hash, size: 0, } return &hw, nil } type Hasher struct { name string hash hash.Hash size int64 } func (dh *Hasher) Name() string { return dh.name } func (dh *Hasher) Write(p []byte) (int, error) { n, err := dh.hash.Write(p) dh.size += int64(n) return n, err } func (dh *Hasher) Size() int64 { return dh.size } func (dh *Hasher) Sum(b []byte) []byte { return dh.hash.Sum(b) } go-debian-0.16.0/internal/000077500000000000000000000000001452017266000152425ustar00rootroot00000000000000go-debian-0.16.0/internal/copy.go000066400000000000000000000005221452017266000165420ustar00rootroot00000000000000package internal import ( "io" "os" ) func Copy(source, dest string) error { in, err := os.Open(source) if err != nil { return err } defer in.Close() out, err := os.Create(dest) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) cerr := out.Close() if err != nil { return err } return cerr } go-debian-0.16.0/version/000077500000000000000000000000001452017266000151135ustar00rootroot00000000000000go-debian-0.16.0/version/version.go000066400000000000000000000145071452017266000171360ustar00rootroot00000000000000/* {{{ Copyright © 2012 Michael Stapelberg and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Michael Stapelberg nor the * names of contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }}} */ // version is a pure-go implementation of dpkg version string functions // (parsing, comparison) which is compatible with dpkg(1). package version // import "pault.ag/go/debian/version" import ( "encoding/json" "fmt" "strconv" "strings" "unicode" ) // Slice is a slice versions, satisfying sort.Interface type Slice []Version func (a Slice) Len() int { return len(a) } func (a Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a Slice) Less(i, j int) bool { return Compare(a[i], a[j]) < 0 } type Version struct { Epoch uint Version string Revision string } func (v *Version) Empty() bool { return v.Epoch == 0 && v.Version == "" && v.Revision == "" } func (v *Version) IsNative() bool { return len(v.Revision) == 0 } func (version *Version) MarshalText() ([]byte, error) { return json.Marshal(version.String()) } func (version *Version) UnmarshalText(text []byte) error { var err error *version, err = Parse(string(text)) if err != nil { return err } return nil } func (version *Version) UnmarshalControl(data string) error { return parseInto(version, data) } func (version Version) MarshalControl() (string, error) { return version.String(), nil } func (v Version) StringWithoutEpoch() string { result := v.Version if len(v.Revision) > 0 { result += "-" + v.Revision } return result } func (v Version) String() string { if v.Epoch > 0 { return fmt.Sprintf("%d:%s", v.Epoch, v.StringWithoutEpoch()) } return v.StringWithoutEpoch() } func cisdigit(r rune) bool { return r >= '0' && r <= '9' } func cisalpha(r rune) bool { return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') } func order(r rune) int { if cisdigit(r) { return 0 } if cisalpha(r) { return int(r) } if r == '~' { return -1 } if int(r) != 0 { return int(r) + 256 } return 0 } func verrevcmp(a string, b string) int { i := 0 j := 0 for i < len(a) || j < len(b) { var first_diff int for (i < len(a) && !cisdigit(rune(a[i]))) || (j < len(b) && !cisdigit(rune(b[j]))) { ac := 0 if i < len(a) { ac = order(rune(a[i])) } bc := 0 if j < len(b) { bc = order(rune(b[j])) } if ac != bc { return ac - bc } i++ j++ } for i < len(a) && a[i] == '0' { i++ } for j < len(b) && b[j] == '0' { j++ } for i < len(a) && cisdigit(rune(a[i])) && j < len(b) && cisdigit(rune(b[j])) { if first_diff == 0 { first_diff = int(rune(a[i]) - rune(b[j])) } i++ j++ } if i < len(a) && cisdigit(rune(a[i])) { return 1 } if j < len(b) && cisdigit(rune(b[j])) { return -1 } if first_diff != 0 { return first_diff } } return 0 } // Compare compares the two provided Debian versions. It returns 0 if a and b // are equal, a value < 0 if a is smaller than b and a value > 0 if a is // greater than b. func Compare(a Version, b Version) int { if a.Epoch > b.Epoch { return 1 } if a.Epoch < b.Epoch { return -1 } rc := verrevcmp(a.Version, b.Version) if rc != 0 { return rc } return verrevcmp(a.Revision, b.Revision) } // Parse returns a Version struct filled with the epoch, version and revision // specified in input. It verifies the version string as a whole, just like // dpkg(1), and even returns roughly the same error messages. func Parse(input string) (Version, error) { result := Version{} return result, parseInto(&result, input) } func parseInto(result *Version, input string) error { trimmed := strings.TrimSpace(input) if trimmed == "" { return fmt.Errorf("version string is empty") } if strings.IndexFunc(trimmed, unicode.IsSpace) != -1 { return fmt.Errorf("version string has embedded spaces") } colon := strings.Index(trimmed, ":") if colon != -1 { epoch, err := strconv.ParseInt(trimmed[:colon], 10, 64) if err != nil { return fmt.Errorf("epoch: %v", err) } if epoch < 0 { return fmt.Errorf("epoch in version is negative") } result.Epoch = uint(epoch) } result.Version = trimmed[colon+1:] if len(result.Version) == 0 { return fmt.Errorf("nothing after colon in version number") } if hyphen := strings.LastIndex(result.Version, "-"); hyphen != -1 { result.Revision = result.Version[hyphen+1:] result.Version = result.Version[:hyphen] } if len(result.Version) > 0 && !unicode.IsDigit(rune(result.Version[0])) { return fmt.Errorf("version number does not start with digit") } if strings.IndexFunc(result.Version, func(c rune) bool { return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '-' && c != '+' && c != '~' && c != ':' }) != -1 { return fmt.Errorf("invalid character in version number") } if strings.IndexFunc(result.Revision, func(c rune) bool { return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '+' && c != '~' }) != -1 { return fmt.Errorf("invalid character in revision number") } return nil } // vim:ts=4:sw=4:noexpandtab foldmethod=marker go-debian-0.16.0/version/version_test.go000066400000000000000000000245171452017266000201770ustar00rootroot00000000000000/* {{{ Copyright © 2012 Michael Stapelberg and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Michael Stapelberg nor the * names of contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }}} */ package version // import "pault.ag/go/debian/version" import ( "strings" "testing" ) // Abbreviation for creating a new Version object. func v(epoch uint, version string, revision string) Version { return Version{Epoch: epoch, Version: version, Revision: revision} } func TestEpoch(t *testing.T) { if Compare(Version{Epoch: 1}, Version{Epoch: 2}) == 0 { t.Errorf("epoch=1, epoch=2") } if a, b := v(0, "1", "1"), v(0, "2", "1"); Compare(a, b) == 0 { t.Errorf("a, b") } if a, b := v(0, "1", "1"), v(0, "1", "2"); Compare(a, b) == 0 { t.Errorf("a, b") } } func TestEquality(t *testing.T) { if a, b := v(0, "0", "0"), v(0, "0", "0"); Compare(a, b) != 0 { t.Errorf("a, b") } if a, b := v(0, "0", "00"), v(0, "00", "0"); Compare(a, b) != 0 { t.Errorf("a, b") } if a, b := v(1, "2", "3"), v(1, "2", "3"); Compare(a, b) != 0 { t.Errorf("a, b") } } func TestEpochDifference(t *testing.T) { a := v(0, "0", "0") b := v(1, "0", "0") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestVersionDifference(t *testing.T) { a := v(0, "a", "0") b := v(0, "b", "0") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestRevisionDifference(t *testing.T) { a := v(0, "0", "a") b := v(0, "0", "b") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestCompareCodesearch(t *testing.T) { a := v(0, "1.8.6", "2") b := v(0, "1.8.6", "2.1") if Compare(a, b) >= 0 { t.Errorf("a, b") } } func TestParseZeroVersions(t *testing.T) { var a Version var err error b := v(0, "0", "") if a, err = Parse("0"); err != nil { t.Errorf("Parsing %q failed: %v", "0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0-"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0", "0") if a, err = Parse("0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0.0", "0.0") if a, err = Parse("0:0.0-0.0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0.0-0.0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseEpochedVersions(t *testing.T) { var a Version var err error b := v(1, "0", "") if a, err = Parse("1:0"); err != nil { t.Errorf("Parsing %q failed: %v", "1:0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(5, "1", "") if a, err = Parse("5:1"); err != nil { t.Errorf("Parsing %q failed: %v", "5:1", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleHyphens(t *testing.T) { var a Version var err error b := v(0, "0-0", "0") if a, err = Parse("0:0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0-0-0", "0") if a, err = Parse("0:0-0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleColons(t *testing.T) { var a Version var err error b := v(0, "0:0", "0") if a, err = Parse("0:0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0:0:0", "0") if a, err = Parse("0:0:0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleHyphensAndColons(t *testing.T) { var a Version var err error b := v(0, "0:0-0", "0") if a, err = Parse("0:0:0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0-0:0", "0") if a, err = Parse("0:0-0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseValidUpstreamVersionCharacters(t *testing.T) { var a Version var err error b := v(0, "09azAZ.-+~:", "0") if a, err = Parse("0:09azAZ.-+~:-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:09azAZ.-+~:-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseValidRevisionCharacters(t *testing.T) { var a Version var err error b := v(0, "0", "azAZ09.+~") if a, err = Parse("0:0-azAZ09.+~"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-azAZ09.+~", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseLeadingTrailingSpaces(t *testing.T) { var a Version var err error b := v(0, "0", "1") if a, err = Parse(" 0:0-1"); err != nil { t.Errorf("Parsing %q failed: %v", " 0:0-1", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0-1 "); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-1 ", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse(" 0:0-1 "); err != nil { t.Errorf("Parsing %q failed: %v", " 0:0-1 ", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseEmptyVersion(t *testing.T) { if _, err := Parse(""); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "") } if _, err := Parse(" "); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", " ") } } func TestParseEmptyUpstreamVersionAfterEpoch(t *testing.T) { if _, err := Parse("0:"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:") } } func TestParseVersionWithEmbeddedSpaces(t *testing.T) { if _, err := Parse("0:0 0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:0 0-1") } } func TestParseVersionWithNegativeEpoch(t *testing.T) { if _, err := Parse("-1:0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "-1:0-1") } } func TestParseVersionWithHugeEpoch(t *testing.T) { if _, err := Parse("999999999999999999999999:0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "999999999999999999999999:0-1") } } func TestParseInvalidCharactersInEpoch(t *testing.T) { if _, err := Parse("a:0-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "a:0-0") } if _, err := Parse("A:0-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "A:0-0") } } func TestParseUpstreamVersionNotStartingWithADigit(t *testing.T) { if _, err := Parse("0:abc3-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:abc3-0") } } func TestParseInvalidCharactersInUpstreamVersion(t *testing.T) { chars := "!#@$%&/|\\<>()[]{};,_=*^'" for i := 0; i < len(chars); i++ { verstr := "0:0" + chars[i:i+1] + "-0" if _, err := Parse(verstr); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", verstr) } } } func TestParseInvalidCharactersInRevision(t *testing.T) { if _, err := Parse("0:0-0:0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:0-0:0") } chars := "!#@$%&/|\\<>()[]{}:;,_=*^'" for i := 0; i < len(chars); i++ { verstr := "0:0-" + chars[i:i+1] if _, err := Parse(verstr); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", verstr) } } } func TestString(t *testing.T) { if strings.Compare("1.0-1", Version{ Version: "1.0", Revision: "1", }.String()) != 0 { t.Errorf("String() returned malformed Version") } if strings.Compare("1:1.0-1", Version{ Epoch: 1, Version: "1.0", Revision: "1", }.String()) != 0 { t.Errorf("String() returned malformed Version with Epoch") } if strings.Compare("1.0-1", Version{ Epoch: 1, Version: "1.0", Revision: "1", }.StringWithoutEpoch()) != 0 { t.Errorf("StringWithoutEpoch() returned malformed Version with Epoch") } } // vim:ts=4:sw=4:noexpandtab foldmethod=marker