diff --git a/imgbom/analyzer/bundler/analyzer.go b/imgbom/analyzer/bundler/analyzer.go index 7c4dceec1..6505fb84d 100644 --- a/imgbom/analyzer/bundler/analyzer.go +++ b/imgbom/analyzer/bundler/analyzer.go @@ -13,7 +13,7 @@ type Analyzer struct { func NewAnalyzer() *Analyzer { globParserDispatch := map[string]common.ParserFn{ - "*/Gemfile.lock": ParseGemfileLockEntries, + "*/Gemfile.lock": parseGemfileLockEntries, } return &Analyzer{ diff --git a/imgbom/analyzer/bundler/parse_gemfile_lock.go b/imgbom/analyzer/bundler/parse_gemfile_lock.go index 297d72959..9921c4261 100644 --- a/imgbom/analyzer/bundler/parse_gemfile_lock.go +++ b/imgbom/analyzer/bundler/parse_gemfile_lock.go @@ -11,7 +11,7 @@ import ( var sectionsOfInterest = internal.NewStringSetFromSlice([]string{"GEM"}) -func ParseGemfileLockEntries(reader io.Reader) ([]pkg.Package, error) { +func parseGemfileLockEntries(reader io.Reader) ([]pkg.Package, error) { pkgs := make([]pkg.Package, 0) scanner := bufio.NewScanner(reader) diff --git a/imgbom/analyzer/bundler/parse_gemfile_lock_test.go b/imgbom/analyzer/bundler/parse_gemfile_lock_test.go index cf360f47d..5eb10b73c 100644 --- a/imgbom/analyzer/bundler/parse_gemfile_lock_test.go +++ b/imgbom/analyzer/bundler/parse_gemfile_lock_test.go @@ -67,7 +67,7 @@ func TestParseGemfileLockEntries(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actual, err := ParseGemfileLockEntries(fixture) + actual, err := parseGemfileLockEntries(fixture) if err != nil { t.Fatalf("failed to parse gemfile lock: %+v", err) } diff --git a/imgbom/analyzer/common/generic_analyzer.go b/imgbom/analyzer/common/generic_analyzer.go index b9a61a385..c7d666ed0 100644 --- a/imgbom/analyzer/common/generic_analyzer.go +++ b/imgbom/analyzer/common/generic_analyzer.go @@ -9,6 +9,8 @@ import ( "github.com/anchore/stereoscope/pkg/tree" ) +// TODO: put under test... + type GenericAnalyzer struct { globParserDispatch map[string]ParserFn pathParserDispatch map[string]ParserFn diff --git a/imgbom/analyzer/controller.go b/imgbom/analyzer/controller.go index 9c445c34d..7defd110e 100644 --- a/imgbom/analyzer/controller.go +++ b/imgbom/analyzer/controller.go @@ -3,6 +3,7 @@ package analyzer import ( "github.com/anchore/imgbom/imgbom/analyzer/bundler" "github.com/anchore/imgbom/imgbom/analyzer/dpkg" + "github.com/anchore/imgbom/imgbom/analyzer/python" "github.com/anchore/imgbom/imgbom/pkg" "github.com/anchore/imgbom/imgbom/scope" "github.com/anchore/imgbom/internal/log" @@ -30,6 +31,7 @@ func newController() controller { } ctrlr.add(dpkg.NewAnalyzer()) ctrlr.add(bundler.NewAnalyzer()) + ctrlr.add(python.NewAnalyzer()) return ctrlr } diff --git a/imgbom/analyzer/dpkg/analyzer.go b/imgbom/analyzer/dpkg/analyzer.go index 7af4cc810..af6a91179 100644 --- a/imgbom/analyzer/dpkg/analyzer.go +++ b/imgbom/analyzer/dpkg/analyzer.go @@ -13,7 +13,7 @@ type Analyzer struct { func NewAnalyzer() *Analyzer { pathParserDispatch := map[string]common.ParserFn{ - "/var/lib/dpkg/status": ParseDpkgStatus, + "/var/lib/dpkg/status": parseDpkgStatus, } return &Analyzer{ diff --git a/imgbom/analyzer/dpkg/parse_dpkg_status.go b/imgbom/analyzer/dpkg/parse_dpkg_status.go index cee6e5b33..4414ef56b 100644 --- a/imgbom/analyzer/dpkg/parse_dpkg_status.go +++ b/imgbom/analyzer/dpkg/parse_dpkg_status.go @@ -12,7 +12,7 @@ import ( var errEndOfPackages = fmt.Errorf("no more packages to read") -func ParseDpkgStatus(reader io.Reader) ([]pkg.Package, error) { +func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) { buffedReader := bufio.NewReader(reader) var packages = make([]pkg.Package, 0) diff --git a/imgbom/analyzer/dpkg/parse_dpkg_status_test.go b/imgbom/analyzer/dpkg/parse_dpkg_status_test.go index 339ac980b..6a2abc542 100644 --- a/imgbom/analyzer/dpkg/parse_dpkg_status_test.go +++ b/imgbom/analyzer/dpkg/parse_dpkg_status_test.go @@ -90,7 +90,7 @@ func TestMultiplePackages(t *testing.T) { } }() - pkgs, err := ParseDpkgStatus(file) + pkgs, err := parseDpkgStatus(file) if err != nil { t.Fatal("Unable to read file contents: ", err) } diff --git a/imgbom/analyzer/python/analyzer.go b/imgbom/analyzer/python/analyzer.go new file mode 100644 index 000000000..5f4323fb4 --- /dev/null +++ b/imgbom/analyzer/python/analyzer.go @@ -0,0 +1,35 @@ +package python + +import ( + "github.com/anchore/imgbom/imgbom/analyzer/common" + "github.com/anchore/imgbom/imgbom/pkg" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/tree" +) + +type Analyzer struct { + analyzer common.GenericAnalyzer +} + +func NewAnalyzer() *Analyzer { + globParserDispatch := map[string]common.ParserFn{ + "*egg-info/PKG-INFO": parseEggMetadata, + "*dist-info/METADATA": parseWheelMetadata, + } + + return &Analyzer{ + analyzer: common.NewGenericAnalyzer(nil, globParserDispatch), + } +} + +func (a *Analyzer) Name() string { + return "python-analyzer" +} + +func (a *Analyzer) SelectFiles(trees []tree.FileTreeReader) []file.Reference { + return a.analyzer.SelectFiles(trees) +} + +func (a *Analyzer) Analyze(contents map[file.Reference]string) ([]pkg.Package, error) { + return a.analyzer.Analyze(contents, a.Name()) +} diff --git a/imgbom/analyzer/python/parse_wheel_egg.go b/imgbom/analyzer/python/parse_wheel_egg.go new file mode 100644 index 000000000..b2f311d64 --- /dev/null +++ b/imgbom/analyzer/python/parse_wheel_egg.go @@ -0,0 +1,90 @@ +package python + +import ( + "bufio" + "fmt" + "io" + "strings" + + "github.com/anchore/imgbom/imgbom/pkg" +) + +func parseWheelMetadata(reader io.Reader) ([]pkg.Package, error) { + packages, err := parseWheelOrEggMetadata(reader) + for idx := range packages { + packages[idx].Type = pkg.WheelPkg + } + return packages, err +} + +func parseEggMetadata(reader io.Reader) ([]pkg.Package, error) { + packages, err := parseWheelOrEggMetadata(reader) + for idx := range packages { + packages[idx].Type = pkg.EggPkg + } + return packages, err +} + +func parseWheelOrEggMetadata(reader io.Reader) ([]pkg.Package, error) { + fields := make(map[string]string) + var key string + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + + line = strings.TrimRight(line, "\n") + + // empty line indicates end of entry + if len(line) == 0 { + // if the entry has not started, keep parsing lines + if len(fields) == 0 { + continue + } + break + } + + switch { + case strings.HasPrefix(line, " "): + // a field-body continuation + if len(key) == 0 { + return nil, fmt.Errorf("no match for continuation: line: '%s'", line) + } + + val, ok := fields[key] + if !ok { + return nil, fmt.Errorf("no previous key exists, expecting: %s", key) + } + // concatenate onto previous value + val = fmt.Sprintf("%s\n %s", val, strings.TrimSpace(line)) + fields[key] = val + default: + // parse a new key (note, duplicate keys are overridden) + if i := strings.Index(line, ":"); i > 0 { + key = strings.TrimSpace(line[0:i]) + val := strings.TrimSpace(line[i+1:]) + + fields[key] = val + } else { + return nil, fmt.Errorf("cannot parse field from line: '%s'", line) + } + } + + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to parse python wheel/egg: %w", err) + } + + p := pkg.Package{ + Name: fields["Name"], + Version: fields["Version"], + Language: pkg.Python, + } + + if license, ok := fields["License"]; ok && license != "" { + p.Licenses = []string{license} + } + + return []pkg.Package{p}, nil +} diff --git a/imgbom/analyzer/python/parse_wheel_egg_test.go b/imgbom/analyzer/python/parse_wheel_egg_test.go new file mode 100644 index 000000000..20afa6725 --- /dev/null +++ b/imgbom/analyzer/python/parse_wheel_egg_test.go @@ -0,0 +1,91 @@ +package python + +import ( + "os" + "testing" + + "github.com/anchore/imgbom/imgbom/pkg" +) + +func assertPkgsEqual(t *testing.T, actual []pkg.Package, expected map[string]pkg.Package) { + t.Helper() + if len(actual) != 1 { + for _, a := range actual { + t.Log(" ", a) + } + t.Fatalf("unexpected package count: %d!=%d", len(actual), 1) + } + + for _, a := range actual { + expectedPkg, ok := expected[a.Name] + if !ok { + t.Errorf("unexpected package found: '%s'", a.Name) + } + + if expectedPkg.Version != a.Version { + t.Errorf("unexpected package version: '%s'", a.Version) + } + + if a.Language != expectedPkg.Language { + t.Errorf("bad language: '%+v'", a.Language) + } + + if a.Type != expectedPkg.Type { + t.Errorf("bad package type: %+v", a.Type) + } + + if len(a.Licenses) < 1 { + t.Errorf("bad package licenses count: '%+v'", a.Licenses) + } else if a.Licenses[0] != expectedPkg.Licenses[0] { + t.Errorf("bad package licenses: '%+v'", a.Licenses) + } + + } +} +func TestParseEggMetadata(t *testing.T) { + expected := map[string]pkg.Package{ + "requests": { + Name: "requests", + Version: "2.22.0", + Language: pkg.Python, + Type: pkg.EggPkg, + Licenses: []string{"Apache 2.0"}, + }, + } + fixture, err := os.Open("test-fixtures/egg-info/PKG-INFO") + if err != nil { + t.Fatalf("failed to open fixture: %+v", err) + } + + actual, err := parseEggMetadata(fixture) + if err != nil { + t.Fatalf("failed to parse egg-info: %+v", err) + } + + assertPkgsEqual(t, actual, expected) + +} + +func TestParseWheelMetadata(t *testing.T) { + expected := map[string]pkg.Package{ + "Pygments": { + Name: "Pygments", + Version: "2.6.1", + Language: pkg.Python, + Type: pkg.WheelPkg, + Licenses: []string{"BSD License"}, + }, + } + fixture, err := os.Open("test-fixtures/dist-info/METADATA") + if err != nil { + t.Fatalf("failed to open fixture: %+v", err) + } + + actual, err := parseWheelMetadata(fixture) + if err != nil { + t.Fatalf("failed to parse dist-info: %+v", err) + } + + assertPkgsEqual(t, actual, expected) + +} diff --git a/imgbom/analyzer/python/test-fixtures/dist-info/METADATA b/imgbom/analyzer/python/test-fixtures/dist-info/METADATA new file mode 100644 index 000000000..924780dfd --- /dev/null +++ b/imgbom/analyzer/python/test-fixtures/dist-info/METADATA @@ -0,0 +1,47 @@ +Metadata-Version: 2.1 +Name: Pygments +Version: 2.6.1 +Summary: Pygments is a syntax highlighting package written in Python. +Home-page: https://pygments.org/ +Author: Georg Brandl +Author-email: georg@python.org +License: BSD License +Keywords: syntax highlighting +Platform: any +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: System Administrators +Classifier: Development Status :: 6 - Mature +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Operating System :: OS Independent +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Utilities +Requires-Python: >=3.5 + + +Pygments +~~~~~~~~ + +Pygments is a syntax highlighting package written in Python. + +It is a generic syntax highlighter suitable for use in code hosting, forums, +wikis or other applications that need to prettify source code. Highlights +are: + +* a wide range of over 500 languages and other text formats is supported +* special attention is paid to details, increasing quality by a fair amount +* support for new languages and formats are added easily +* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences +* it is usable as a command-line tool and as a library + +:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS. +:license: BSD, see LICENSE for details. + diff --git a/imgbom/analyzer/python/test-fixtures/egg-info/PKG-INFO b/imgbom/analyzer/python/test-fixtures/egg-info/PKG-INFO new file mode 100644 index 000000000..a73770668 --- /dev/null +++ b/imgbom/analyzer/python/test-fixtures/egg-info/PKG-INFO @@ -0,0 +1,134 @@ +Metadata-Version: 2.1 +Name: requests +Version: 2.22.0 +Summary: Python HTTP for Humans. +Home-page: http://python-requests.org +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +License: Apache 2.0 +Description: Requests: HTTP for Humans™ + ========================== + + [![image](https://img.shields.io/pypi/v/requests.svg)](https://pypi.org/project/requests/) + [![image](https://img.shields.io/pypi/l/requests.svg)](https://pypi.org/project/requests/) + [![image](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests/) + [![codecov.io](https://codecov.io/github/requests/requests/coverage.svg?branch=master)](https://codecov.io/github/requests/requests) + [![image](https://img.shields.io/github/contributors/requests/requests.svg)](https://github.com/requests/requests/graphs/contributors) + [![image](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/kennethreitz) + + Requests is the only *Non-GMO* HTTP library for Python, safe for human + consumption. + + ![image](https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg) + + Behold, the power of Requests: + + ``` {.sourceCode .python} + >>> import requests + >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) + >>> r.status_code + 200 + >>> r.headers['content-type'] + 'application/json; charset=utf8' + >>> r.encoding + 'utf-8' + >>> r.text + u'{"type":"User"...' + >>> r.json() + {u'disk_usage': 368627, u'private_gists': 484, ...} + ``` + + See [the similar code, sans Requests](https://gist.github.com/973705). + + [![image](https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png)](http://docs.python-requests.org/) + + Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, + without the need for manual labor. There's no need to manually add query + strings to your URLs, or to form-encode your POST data. Keep-alive and + HTTP connection pooling are 100% automatic, thanks to + [urllib3](https://github.com/shazow/urllib3). + + Besides, all the cool kids are doing it. Requests is one of the most + downloaded Python packages of all time, pulling in over 11,000,000 + downloads every month. You don't want to be left out! + + Feature Support + --------------- + + Requests is ready for today's web. + + - International Domains and URLs + - Keep-Alive & Connection Pooling + - Sessions with Cookie Persistence + - Browser-style SSL Verification + - Basic/Digest Authentication + - Elegant Key/Value Cookies + - Automatic Decompression + - Automatic Content Decoding + - Unicode Response Bodies + - Multipart File Uploads + - HTTP(S) Proxy Support + - Connection Timeouts + - Streaming Downloads + - `.netrc` Support + - Chunked Requests + + Requests officially supports Python 2.7 & 3.4–3.7, and runs great on + PyPy. + + Installation + ------------ + + To install Requests, simply use [pipenv](http://pipenv.org/) (or pip, of + course): + + ``` {.sourceCode .bash} + $ pipenv install requests + ✨🍰✨ + ``` + + Satisfaction guaranteed. + + Documentation + ------------- + + Fantastic documentation is available at + , for a limited time only. + + How to Contribute + ----------------- + + 1. Become more familiar with the project by reading our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) and our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). + 2. Check for open issues or open a fresh issue to start a discussion + around a feature idea or a bug. There is a [Contributor + Friendly](https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open) + tag for issues that should be ideal for people who are not very + familiar with the codebase yet. + 3. Fork [the repository](https://github.com/requests/requests) on + GitHub to start making your changes to the **master** branch (or + branch off of it). + 4. Write a test which shows that the bug was fixed or that the feature + works as expected. + 5. Send a pull request and bug the maintainer until it gets merged and + published. :) Make sure to add yourself to + [AUTHORS](https://github.com/requests/requests/blob/master/AUTHORS.rst). + + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Description-Content-Type: text/markdown +Provides-Extra: security +Provides-Extra: socks \ No newline at end of file diff --git a/imgbom/pkg/type.go b/imgbom/pkg/type.go index 4573ce92a..ce35aa12c 100644 --- a/imgbom/pkg/type.go +++ b/imgbom/pkg/type.go @@ -5,8 +5,10 @@ const ( ApkPkg BundlerPkg DebPkg + EggPkg PacmanPkg RpmPkg + WheelPkg ) type Type uint @@ -14,18 +16,22 @@ type Type uint var typeStr = []string{ "UnknownPackage", "apk", - "bundler", + "bundle", "deb", + "egg", "pacman", "rpm", + "wheel", } var AllPkgs = []Type{ ApkPkg, BundlerPkg, DebPkg, + EggPkg, PacmanPkg, RpmPkg, + WheelPkg, } func (t Type) String() string { diff --git a/integration/fixture_image_bundler_test.go b/integration/fixture_image_language_pkgs_test.go similarity index 86% rename from integration/fixture_image_bundler_test.go rename to integration/fixture_image_language_pkgs_test.go index 5cc0ca354..15248215b 100644 --- a/integration/fixture_image_bundler_test.go +++ b/integration/fixture_image_language_pkgs_test.go @@ -11,7 +11,7 @@ import ( "github.com/anchore/imgbom/imgbom/scope" ) -func TestBundlerImage(t *testing.T) { +func TestLanguageImage(t *testing.T) { img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-language-pkgs") defer cleanup() @@ -26,6 +26,22 @@ func TestBundlerImage(t *testing.T) { pkgLanguage pkg.Language pkgInfo map[string]string }{ + { + name: "find python wheel packages", + pkgType: pkg.WheelPkg, + pkgLanguage: pkg.Python, + pkgInfo: map[string]string{ + "Pygments": "2.6.1", + }, + }, + { + name: "find python egg packages", + pkgType: pkg.EggPkg, + pkgLanguage: pkg.Python, + pkgInfo: map[string]string{ + "requests": "2.22.0", + }, + }, { name: "find bundler packages", pkgType: pkg.BundlerPkg, @@ -88,12 +104,7 @@ func TestBundlerImage(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - if catalog.PackageCount() != len(c.pkgInfo) { - for a := range catalog.Enumerate(c.pkgType) { - t.Log(" ", a) - } - t.Fatalf("unexpected package count: %d!=%d", catalog.PackageCount(), len(c.pkgInfo)) - } + pkgCount := 0 for a := range catalog.Enumerate(c.pkgType) { @@ -113,6 +124,14 @@ func TestBundlerImage(t *testing.T) { if a.Type != c.pkgType { t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type) } + pkgCount++ + } + + if pkgCount != len(c.pkgInfo) { + for a := range catalog.Enumerate(c.pkgType) { + t.Log(" ", a) + } + t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo)) } }) diff --git a/integration/test-fixtures/image-language-pkgs/Dockerfile b/integration/test-fixtures/image-language-pkgs/Dockerfile index 2a33c4540..770e60b56 100644 --- a/integration/test-fixtures/image-language-pkgs/Dockerfile +++ b/integration/test-fixtures/image-language-pkgs/Dockerfile @@ -1,2 +1,2 @@ FROM scratch -COPY Gemfile.lock . \ No newline at end of file +COPY . . \ No newline at end of file diff --git a/integration/test-fixtures/image-language-pkgs/dist-info/METADATA b/integration/test-fixtures/image-language-pkgs/dist-info/METADATA new file mode 100644 index 000000000..924780dfd --- /dev/null +++ b/integration/test-fixtures/image-language-pkgs/dist-info/METADATA @@ -0,0 +1,47 @@ +Metadata-Version: 2.1 +Name: Pygments +Version: 2.6.1 +Summary: Pygments is a syntax highlighting package written in Python. +Home-page: https://pygments.org/ +Author: Georg Brandl +Author-email: georg@python.org +License: BSD License +Keywords: syntax highlighting +Platform: any +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: System Administrators +Classifier: Development Status :: 6 - Mature +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Operating System :: OS Independent +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Utilities +Requires-Python: >=3.5 + + +Pygments +~~~~~~~~ + +Pygments is a syntax highlighting package written in Python. + +It is a generic syntax highlighter suitable for use in code hosting, forums, +wikis or other applications that need to prettify source code. Highlights +are: + +* a wide range of over 500 languages and other text formats is supported +* special attention is paid to details, increasing quality by a fair amount +* support for new languages and formats are added easily +* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences +* it is usable as a command-line tool and as a library + +:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS. +:license: BSD, see LICENSE for details. + diff --git a/integration/test-fixtures/image-language-pkgs/egg-info/PKG-INFO b/integration/test-fixtures/image-language-pkgs/egg-info/PKG-INFO new file mode 100644 index 000000000..a73770668 --- /dev/null +++ b/integration/test-fixtures/image-language-pkgs/egg-info/PKG-INFO @@ -0,0 +1,134 @@ +Metadata-Version: 2.1 +Name: requests +Version: 2.22.0 +Summary: Python HTTP for Humans. +Home-page: http://python-requests.org +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +License: Apache 2.0 +Description: Requests: HTTP for Humans™ + ========================== + + [![image](https://img.shields.io/pypi/v/requests.svg)](https://pypi.org/project/requests/) + [![image](https://img.shields.io/pypi/l/requests.svg)](https://pypi.org/project/requests/) + [![image](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests/) + [![codecov.io](https://codecov.io/github/requests/requests/coverage.svg?branch=master)](https://codecov.io/github/requests/requests) + [![image](https://img.shields.io/github/contributors/requests/requests.svg)](https://github.com/requests/requests/graphs/contributors) + [![image](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/kennethreitz) + + Requests is the only *Non-GMO* HTTP library for Python, safe for human + consumption. + + ![image](https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg) + + Behold, the power of Requests: + + ``` {.sourceCode .python} + >>> import requests + >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) + >>> r.status_code + 200 + >>> r.headers['content-type'] + 'application/json; charset=utf8' + >>> r.encoding + 'utf-8' + >>> r.text + u'{"type":"User"...' + >>> r.json() + {u'disk_usage': 368627, u'private_gists': 484, ...} + ``` + + See [the similar code, sans Requests](https://gist.github.com/973705). + + [![image](https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png)](http://docs.python-requests.org/) + + Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, + without the need for manual labor. There's no need to manually add query + strings to your URLs, or to form-encode your POST data. Keep-alive and + HTTP connection pooling are 100% automatic, thanks to + [urllib3](https://github.com/shazow/urllib3). + + Besides, all the cool kids are doing it. Requests is one of the most + downloaded Python packages of all time, pulling in over 11,000,000 + downloads every month. You don't want to be left out! + + Feature Support + --------------- + + Requests is ready for today's web. + + - International Domains and URLs + - Keep-Alive & Connection Pooling + - Sessions with Cookie Persistence + - Browser-style SSL Verification + - Basic/Digest Authentication + - Elegant Key/Value Cookies + - Automatic Decompression + - Automatic Content Decoding + - Unicode Response Bodies + - Multipart File Uploads + - HTTP(S) Proxy Support + - Connection Timeouts + - Streaming Downloads + - `.netrc` Support + - Chunked Requests + + Requests officially supports Python 2.7 & 3.4–3.7, and runs great on + PyPy. + + Installation + ------------ + + To install Requests, simply use [pipenv](http://pipenv.org/) (or pip, of + course): + + ``` {.sourceCode .bash} + $ pipenv install requests + ✨🍰✨ + ``` + + Satisfaction guaranteed. + + Documentation + ------------- + + Fantastic documentation is available at + , for a limited time only. + + How to Contribute + ----------------- + + 1. Become more familiar with the project by reading our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) and our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). + 2. Check for open issues or open a fresh issue to start a discussion + around a feature idea or a bug. There is a [Contributor + Friendly](https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open) + tag for issues that should be ideal for people who are not very + familiar with the codebase yet. + 3. Fork [the repository](https://github.com/requests/requests) on + GitHub to start making your changes to the **master** branch (or + branch off of it). + 4. Write a test which shows that the bug was fixed or that the feature + works as expected. + 5. Send a pull request and bug the maintainer until it gets merged and + published. :) Make sure to add yourself to + [AUTHORS](https://github.com/requests/requests/blob/master/AUTHORS.rst). + + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Description-Content-Type: text/markdown +Provides-Extra: security +Provides-Extra: socks \ No newline at end of file