diff --git a/README.md b/README.md index 9ce20e2..ef2ab04 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ licensecheck is tool to detect license of OSS. -It supports java/ruby/python/nodejs/go/rust/github. +It supports java/php/ruby/python/nodejs/go/rust/github. # feature @@ -72,6 +72,7 @@ Information of License will be fetched Data Sources below. | target | data source | | ------ | --------------------------------- | | Java | https://repo1.maven.org | +| PHP | https://packagist.org | | Ruby | https://rubygems.org | | Python | https://pypi.org | | Nodejs | https://registry.npmjs.org | diff --git a/cmd/main.go b/cmd/main.go index 58d8d97..8b4db3e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,6 +40,8 @@ func main() { switch c.String("type") { case "java": typ = licensecheck.Java + case "php": + typ = licensecheck.PHP case "ruby": typ = licensecheck.Ruby case "python": @@ -53,7 +55,7 @@ func main() { case "github": typ = licensecheck.GitHub default: - return errors.New("please specify option -type in java/ruby/python/nodejs/go/rust/github") + return errors.New("please specify option -type in java/php/ruby/python/nodejs/go/rust/github") } result, confidence, err := new(licensecheck.Scanner).Scan(name, version, typ) if err != nil { diff --git a/core/php/scanner.go b/core/php/scanner.go new file mode 100644 index 0000000..140e0cb --- /dev/null +++ b/core/php/scanner.go @@ -0,0 +1,68 @@ +package php + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/vulsio/licensecheck/shared" +) + +const ref = "https://packagist.org/packages/%s.json" + +// Scanner is struct to scan license info +// Crawler is exported to modify or make it easy to test by mock +type Scanner struct { + Crawler shared.Crawler +} + +// ScanLicense returns result of fetch https://pypi.org +// version is not required (if version is given, the result will be more rigorous) +func (s *Scanner) ScanLicense(name, version string) (string, float64, error) { + if s.Crawler == nil { + s.Crawler = &shared.DefaultCrawler{} + } + b, err := s.Crawler.Crawl(fmt.Sprintf(ref, name)) + if err != nil { + return "unknown", 0, err + } + result, confidence, err := parseResponce(b, version) + if err != nil { + return "unknown", 0, err + } + return result, confidence, nil +} + +func parseResponce(b []byte, version string) (string, float64, error) { + license := struct { + Package struct { + Versions map[string]struct { + License []string `json:"license"` + } `json:"versions"` + } `json:"package"` + }{} + if err := json.Unmarshal(b, &license); err != nil { + return "", 0, shared.ErrNotFound + } + if version == "" { + if pkg, ok := license.Package.Versions["dev-main"]; ok { + return joinedResult(pkg.License) + } + if pkg, ok := license.Package.Versions["dev-master"]; ok { + return joinedResult(pkg.License) + } + } else { + if pkg, ok := license.Package.Versions[version]; ok { + return joinedResult(pkg.License) + } + } + return "", 0, shared.ErrNotFound +} + +func joinedResult(licenses []string) (string, float64, error) { + s := strings.Join(licenses, ",") + if s == "" { + return "", 0, shared.ErrNotFound + } + return s, 1, nil +} diff --git a/core/php/scanner_test.go b/core/php/scanner_test.go new file mode 100644 index 0000000..873dd87 --- /dev/null +++ b/core/php/scanner_test.go @@ -0,0 +1,90 @@ +package php + +import ( + "errors" + "io/ioutil" + "math" + "testing" + + "github.com/golang/mock/gomock" + "github.com/vulsio/licensecheck/shared" + "github.com/vulsio/licensecheck/shared/mock" +) + +func TestScanLicense(t *testing.T) { + ctrl := gomock.NewController(t) + tests := []struct { + name string + in string + version string + result string + confidence float64 + wantErr error + }{ + { + name: "success", + in: "../../testdata/php/input1.json", + result: "MIT", + confidence: 1, + }, + { + name: "no license info", + in: "../../testdata/php/input2.json", + result: "unknown", + confidence: 0, + wantErr: shared.ErrNotFound, + }, + { + name: "package that default is dev-master", + in: "../../testdata/php/input3.json", + result: "MIT", + confidence: 1, + }, + { + name: "success with version", + in: "../../testdata/php/input1.json", + version: "1.0.0", + result: "MIT", + confidence: 1, + }, + { + name: "no license info with version", + in: "../../testdata/php/input2.json", + version: "1.0.0", + result: "unknown", + confidence: 0, + wantErr: shared.ErrNotFound, + }, + { + name: "not exist version", + in: "../../testdata/php/input1.json", + version: "999", + result: "unknown", + confidence: 0, + wantErr: shared.ErrNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := ioutil.ReadFile(tt.in) + if err != nil { + t.Fatal(err) + } + sc := new(Scanner) + cl := mock.NewMockCrawler(ctrl) + cl.EXPECT().Crawl(gomock.Any()).Return(b, nil) + sc.Crawler = cl + + result, confidence, err := sc.ScanLicense("test", tt.version) + if err != nil && !errors.Is(err, tt.wantErr) { + t.Fatal(err) + } + if result != tt.result { + t.Errorf("want: %s, got: %s", tt.result, result) + } + if math.Abs(confidence-tt.confidence) >= 1e-6 { + t.Errorf("want: %f, got: %f", tt.confidence, confidence) + } + }) + } +} diff --git a/license.go b/license.go index acaf16e..0c9a2ad 100644 --- a/license.go +++ b/license.go @@ -7,6 +7,7 @@ import ( "github.com/vulsio/licensecheck/core/golicense" "github.com/vulsio/licensecheck/core/java" "github.com/vulsio/licensecheck/core/nodejs" + "github.com/vulsio/licensecheck/core/php" "github.com/vulsio/licensecheck/core/python" "github.com/vulsio/licensecheck/core/ruby" "github.com/vulsio/licensecheck/core/rust" @@ -15,6 +16,7 @@ import ( const ( Java = iota + PHP Ruby Python Nodejs @@ -38,6 +40,8 @@ func (s *Scanner) Scan(name, version string, scanType int) (string, float64, err switch scanType { case Java: sc = &java.Scanner{Crawler: s.Crawler} + case PHP: + sc = &php.Scanner{Crawler: s.Crawler} case Ruby: sc = &ruby.Scanner{Crawler: s.Crawler} case Python: diff --git a/testdata/php/input1.json b/testdata/php/input1.json new file mode 100644 index 0000000..3fa47d4 --- /dev/null +++ b/testdata/php/input1.json @@ -0,0 +1,16 @@ +{ + "package": { + "versions": { + "dev-main": { + "license": [ + "MIT" + ] + }, + "1.0.0": { + "license": [ + "MIT" + ] + } + } + } +} diff --git a/testdata/php/input2.json b/testdata/php/input2.json new file mode 100644 index 0000000..aba706d --- /dev/null +++ b/testdata/php/input2.json @@ -0,0 +1,12 @@ +{ + "package": { + "versions": { + "dev-main": { + "license": [] + }, + "1.0.0": { + "license": [] + } + } + } +} diff --git a/testdata/php/input3.json b/testdata/php/input3.json new file mode 100644 index 0000000..00c69ad --- /dev/null +++ b/testdata/php/input3.json @@ -0,0 +1,16 @@ +{ + "package": { + "versions": { + "dev-master": { + "license": [ + "MIT" + ] + }, + "1.0.0": { + "license": [ + "MIT" + ] + } + } + } +}