From 8d706bb225cf605444c2194e1862e2cfb389e7de Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Mon, 19 Oct 2026 14:56:49 +0200 Subject: [PATCH] fix: accept only relative paths and HTTP(s) URLs for referenced files The check rejected only a lowercase "file:" prefix, so "FILE:///path" made the parser look up that path on the host. Every scheme other than HTTP(s) is now rejected, in any letter case. --- fields.go | 19 ++++++- fields_test.go | 46 ++++++++++++++++ ...egal_authorsFile_file_scheme_uppercase.yml | 53 ++++++++++++++++++ testdata/v0/invalid/logo_data_scheme.yml | 54 +++++++++++++++++++ .../invalid/logo_file_scheme_mixed_case.yml | 54 +++++++++++++++++++ .../v0/invalid/logo_file_scheme_uppercase.yml | 54 +++++++++++++++++++ testdata/v0/invalid/logo_ftp_scheme.yml | 54 +++++++++++++++++++ ...egal_authorsFile_file_scheme_uppercase.yml | 53 ++++++++++++++++++ testdata/v1/invalid/logo_data_scheme.yml | 54 +++++++++++++++++++ .../invalid/logo_file_scheme_mixed_case.yml | 54 +++++++++++++++++++ .../v1/invalid/logo_file_scheme_uppercase.yml | 54 +++++++++++++++++++ testdata/v1/invalid/logo_ftp_scheme.yml | 54 +++++++++++++++++++ v0_test.go | 28 ++++++++++ 13 files changed, 629 insertions(+), 2 deletions(-) create mode 100644 fields_test.go create mode 100644 testdata/v0/invalid/legal_authorsFile_file_scheme_uppercase.yml create mode 100644 testdata/v0/invalid/logo_data_scheme.yml create mode 100644 testdata/v0/invalid/logo_file_scheme_mixed_case.yml create mode 100644 testdata/v0/invalid/logo_file_scheme_uppercase.yml create mode 100644 testdata/v0/invalid/logo_ftp_scheme.yml create mode 100644 testdata/v1/invalid/legal_authorsFile_file_scheme_uppercase.yml create mode 100644 testdata/v1/invalid/logo_data_scheme.yml create mode 100644 testdata/v1/invalid/logo_file_scheme_mixed_case.yml create mode 100644 testdata/v1/invalid/logo_file_scheme_uppercase.yml create mode 100644 testdata/v1/invalid/logo_ftp_scheme.yml diff --git a/fields.go b/fields.go index 4dc8d6a..f9f180e 100644 --- a/fields.go +++ b/fields.go @@ -3,12 +3,16 @@ package publiccode import ( "fmt" "net/url" + "regexp" "strings" "github.com/alranel/go-vcsurl/v2" urlutil "github.com/publiccodeyml/libpubliccode/v5/internal" ) +// RFC 3986 scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ). +var urlSchemeRegexp = regexp.MustCompile(`^([A-Za-z][A-Za-z0-9+.-]*):`) + type validateFn func(publiccode PublicCode, parser *Parser, network bool, baseURL *url.URL) error // validateFieldsV0 validates publiccode.yml with additional rules not validatable @@ -453,8 +457,19 @@ func isRelativePathOrURL(content string, keyName string) (bool, error) { return false, newValidationError(keyName, "is an absolute path. Only relative paths or HTTP(s) URLs allowed") } - if strings.HasPrefix(content, "file:") { - return false, newValidationError(keyName, "is a file:// URL. Only relative paths or HTTP(s) URLs allowed") + // Not url.Parse: it fails on values like "file:///%zz", which would then + // pass as relative paths. URL schemes are case-insensitive (RFC 3986). + if m := urlSchemeRegexp.FindStringSubmatch(content); m != nil { + switch scheme := strings.ToLower(m[1]); scheme { + case "http", "https": + return true, nil + case "file": + return false, newValidationError(keyName, "is a file:// URL. Only relative paths or HTTP(s) URLs allowed") + default: + return false, newValidationErrorf( + keyName, "has an unsupported URL scheme '%s'. Only relative paths or HTTP(s) URLs allowed", scheme, + ) + } } return true, nil diff --git a/fields_test.go b/fields_test.go new file mode 100644 index 0000000..9ea90a6 --- /dev/null +++ b/fields_test.go @@ -0,0 +1,46 @@ +package publiccode + +import ( + "errors" + "testing" +) + +func TestIsRelativePathOrURL(t *testing.T) { + tests := []struct { + content string + err string + }{ + {"logo.png", ""}, + {"img/logo.png", ""}, + {"img/logo:1.png", ""}, + {"logo%.png", ""}, + {"https://example.org/logo.png", ""}, + {"http://example.org/logo.png", ""}, + {"HTTPS://example.org/logo.png", ""}, + {"/logo.png", "is an absolute path. Only relative paths or HTTP(s) URLs allowed"}, + {"file:///logo.png", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed"}, + {"FILE:///logo.png", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed"}, + {"File:///%zz.png", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed"}, + {"ftp://example.org/logo.png", "has an unsupported URL scheme 'ftp'. Only relative paths or HTTP(s) URLs allowed"}, + {"data:image/png;base64,iVBORw0KGgo=", "has an unsupported URL scheme 'data'. Only relative paths or HTTP(s) URLs allowed"}, + } + + for _, tt := range tests { + t.Run(tt.content, func(t *testing.T) { + ok, err := isRelativePathOrURL(tt.content, "logo") + + if tt.err == "" { + if !ok || err != nil { + t.Errorf("expected %q to be accepted, got %v", tt.content, err) + } + + return + } + + var ve ValidationError + if ok || !errors.As(err, &ve) || ve.Key != "logo" || ve.Description != tt.err { + t.Errorf("expected error %q for %q, got %v", tt.err, tt.content, err) + } + }) + } +} diff --git a/testdata/v0/invalid/legal_authorsFile_file_scheme_uppercase.yml b/testdata/v0/invalid/legal_authorsFile_file_scheme_uppercase.yml new file mode 100644 index 0000000..0f9bc83 --- /dev/null +++ b/testdata/v0/invalid/legal_authorsFile_file_scheme_uppercase.yml @@ -0,0 +1,53 @@ +publiccodeYmlVersion: "0" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + # Should NOT validate: it has a "FILE:" scheme + authorsFile: 'FILE:///no_such_authors_file.txt' + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v0/invalid/logo_data_scheme.yml b/testdata/v0/invalid/logo_data_scheme.yml new file mode 100644 index 0000000..61db004 --- /dev/null +++ b/testdata/v0/invalid/logo_data_scheme.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "0" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "data:" scheme. logo must be either an HTTP URL or a relative path +logo: "data:image/png;base64,iVBORw0KGgo=" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v0/invalid/logo_file_scheme_mixed_case.yml b/testdata/v0/invalid/logo_file_scheme_mixed_case.yml new file mode 100644 index 0000000..7e398ec --- /dev/null +++ b/testdata/v0/invalid/logo_file_scheme_mixed_case.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "0" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "file:" scheme. logo must be either an HTTP URL or a relative path +logo: "File:///logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v0/invalid/logo_file_scheme_uppercase.yml b/testdata/v0/invalid/logo_file_scheme_uppercase.yml new file mode 100644 index 0000000..91e9d05 --- /dev/null +++ b/testdata/v0/invalid/logo_file_scheme_uppercase.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "0" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "file:" scheme. logo must be either an HTTP URL or a relative path +logo: "FILE:///logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v0/invalid/logo_ftp_scheme.yml b/testdata/v0/invalid/logo_ftp_scheme.yml new file mode 100644 index 0000000..abfed82 --- /dev/null +++ b/testdata/v0/invalid/logo_ftp_scheme.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "0" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has an "ftp:" scheme. logo must be either an HTTP URL or a relative path +logo: "ftp://example.org/logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v1/invalid/legal_authorsFile_file_scheme_uppercase.yml b/testdata/v1/invalid/legal_authorsFile_file_scheme_uppercase.yml new file mode 100644 index 0000000..a50773a --- /dev/null +++ b/testdata/v1/invalid/legal_authorsFile_file_scheme_uppercase.yml @@ -0,0 +1,53 @@ +publiccodeYmlVersion: "1" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + # Should NOT validate: it has a "FILE:" scheme + authorsFile: 'FILE:///no_such_authors_file.txt' + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v1/invalid/logo_data_scheme.yml b/testdata/v1/invalid/logo_data_scheme.yml new file mode 100644 index 0000000..854e4e5 --- /dev/null +++ b/testdata/v1/invalid/logo_data_scheme.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "1" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "data:" scheme. logo must be either an HTTP URL or a relative path +logo: "data:image/png;base64,iVBORw0KGgo=" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v1/invalid/logo_file_scheme_mixed_case.yml b/testdata/v1/invalid/logo_file_scheme_mixed_case.yml new file mode 100644 index 0000000..d6a1972 --- /dev/null +++ b/testdata/v1/invalid/logo_file_scheme_mixed_case.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "1" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "file:" scheme. logo must be either an HTTP URL or a relative path +logo: "File:///logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v1/invalid/logo_file_scheme_uppercase.yml b/testdata/v1/invalid/logo_file_scheme_uppercase.yml new file mode 100644 index 0000000..f314bc4 --- /dev/null +++ b/testdata/v1/invalid/logo_file_scheme_uppercase.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "1" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has a "file:" scheme. logo must be either an HTTP URL or a relative path +logo: "FILE:///logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/testdata/v1/invalid/logo_ftp_scheme.yml b/testdata/v1/invalid/logo_ftp_scheme.yml new file mode 100644 index 0000000..35203d4 --- /dev/null +++ b/testdata/v1/invalid/logo_ftp_scheme.yml @@ -0,0 +1,54 @@ +publiccodeYmlVersion: "1" + +name: Medusa +url: "https://github.com/italia/developers.italia.it.git" +releaseDate: "2017-04-15" + +platforms: + - web + +categories: + - cloud-management + +developmentStatus: development + +softwareType: "standalone/other" + +# Should NOT validate: it has an "ftp:" scheme. logo must be either an HTTP URL or a relative path +logo: "ftp://example.org/logo.png" + +description: + en: + localisedName: Medusa + shortDescription: > + A rather short description which + is probably useless + longDescription: > + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 158 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 316 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 474 characters. + Very long description of this software, also split + on multiple rows. You should note what the software + is and why one should need it. This is 632 characters. + features: + - Just one feature + +legal: + license: AGPL-3.0-or-later + +maintenance: + type: "community" + + contacts: + - name: Francesco Rossi + +localisation: + localisationReady: true + availableLanguages: + - en diff --git a/v0_test.go b/v0_test.go index d7e62b3..6de8e0f 100644 --- a/v0_test.go +++ b/v0_test.go @@ -216,6 +216,18 @@ func TestInvalidTestcasesV0(t *testing.T) { "logo_file_scheme3.yml": ValidationResults{ ValidationError{"", "logo", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed", 18, 1}, }, + "logo_file_scheme_uppercase.yml": ValidationResults{ + ValidationError{"", "logo", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed", 18, 1}, + }, + "logo_file_scheme_mixed_case.yml": ValidationResults{ + ValidationError{"", "logo", "is a file:// URL. Only relative paths or HTTP(s) URLs allowed", 18, 1}, + }, + "logo_ftp_scheme.yml": ValidationResults{ + ValidationError{"", "logo", "has an unsupported URL scheme 'ftp'. Only relative paths or HTTP(s) URLs allowed", 18, 1}, + }, + "logo_data_scheme.yml": ValidationResults{ + ValidationError{"", "logo", "has an unsupported URL scheme 'data'. Only relative paths or HTTP(s) URLs allowed", 18, 1}, + }, // Local publiccode.yml and URL in logo: should look for the logo remotely "logo_missing_url.yml": ValidationResults{ ValidationError{"", "logo", "HTTP GET failed for https://google.com/no_such_file.png: not found", 18, 1}, @@ -469,6 +481,22 @@ func TestInvalidTestcasesV0(t *testing.T) { 3, }, }, + "legal_authorsFile_file_scheme_uppercase.yml": ValidationResults{ + ValidationWarning{ + "", + "legal.authorsFile", + "This key is DEPRECATED and will be removed in the future. It's safe to drop it", + 42, + 3, + }, + ValidationError{ + "", + "legal.authorsFile", + "is a file:// URL. Only relative paths or HTTP(s) URLs allowed", + 42, + 3, + }, + }, // maintenance // maintenance.*