github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/cpe/cpe_test.go (about)

     1  package cpe
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func Test_New(t *testing.T) {
    15  	tests := []struct {
    16  		name     string
    17  		input    string
    18  		expected CPE
    19  	}{
    20  		{
    21  			name:     "gocase",
    22  			input:    `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
    23  			expected: Must(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
    24  		},
    25  		{
    26  			name:     "dashes",
    27  			input:    `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
    28  			expected: Must(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
    29  		},
    30  		{
    31  			name:     "URL escape characters",
    32  			input:    `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
    33  			expected: Must(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
    34  		},
    35  	}
    36  
    37  	for _, test := range tests {
    38  		t.Run(test.name, func(t *testing.T) {
    39  			actual, err := New(test.input)
    40  			if err != nil {
    41  				t.Fatalf("got an error while creating CPE: %+v", err)
    42  			}
    43  
    44  			if String(actual) != String(test.expected) {
    45  				t.Errorf("mismatched entries:\n\texpected:%+v\n\t  actual:%+v\n", String(test.expected), String(actual))
    46  			}
    47  
    48  		})
    49  	}
    50  }
    51  
    52  func Test_normalizeCpeField(t *testing.T) {
    53  
    54  	tests := []struct {
    55  		field    string
    56  		expected string
    57  	}{
    58  		{
    59  			field:    "something",
    60  			expected: "something",
    61  		},
    62  		{
    63  			field:    "some\\thing",
    64  			expected: `some\thing`,
    65  		},
    66  		{
    67  			field:    "*",
    68  			expected: "",
    69  		},
    70  		{
    71  			field:    "",
    72  			expected: "",
    73  		},
    74  	}
    75  	for _, test := range tests {
    76  		t.Run(test.field, func(t *testing.T) {
    77  			assert.Equal(t, test.expected, normalizeField(test.field))
    78  		})
    79  	}
    80  }
    81  
    82  func Test_CPEParser(t *testing.T) {
    83  	var testCases []struct {
    84  		CPEString string `json:"cpe-string"`
    85  		CPEUrl    string `json:"cpe-url"`
    86  		WFN       CPE    `json:"wfn"`
    87  	}
    88  	out, err := os.ReadFile("test-fixtures/cpe-data.json")
    89  	require.NoError(t, err)
    90  	require.NoError(t, json.Unmarshal(out, &testCases))
    91  
    92  	for _, test := range testCases {
    93  		t.Run(test.CPEString, func(t *testing.T) {
    94  			c1, err := New(test.CPEString)
    95  			assert.NoError(t, err)
    96  			c2, err := New(test.CPEUrl)
    97  			assert.NoError(t, err)
    98  			assert.Equal(t, c1, c2)
    99  			assert.Equal(t, c1, test.WFN)
   100  			assert.Equal(t, c2, test.WFN)
   101  			assert.Equal(t, String(test.WFN), test.CPEString)
   102  		})
   103  	}
   104  }
   105  
   106  func Test_InvalidCPE(t *testing.T) {
   107  	type testcase struct {
   108  		name        string
   109  		in          string
   110  		expected    string
   111  		expectedErr bool
   112  	}
   113  
   114  	tests := []testcase{
   115  		{
   116  			// 5.3.2: The underscore (x5f) MAY be used, and it SHOULD be used in place of whitespace characters (which SHALL NOT be used)
   117  			name:     "translates spaces",
   118  			in:       "cpe:2.3:a:some-vendor:name:1 2:*:*:*:*:*:*:*",
   119  			expected: "cpe:2.3:a:some-vendor:name:1_2:*:*:*:*:*:*:*",
   120  		},
   121  		{
   122  			// it isn't easily possible in the string formatted string to detect improper escaping of : (it will fail parsing)
   123  			name:        "unescaped ':' cannot be helped -- too many fields",
   124  			in:          "cpe:2.3:a:some-vendor:name:::*:*:*:*:*:*:*",
   125  			expectedErr: true,
   126  		},
   127  		{
   128  			name:     "too few fields",
   129  			in:       "cpe:2.3:a:some-vendor:name:*:*:*:*:*:*:*",
   130  			expected: "cpe:2.3:a:some-vendor:name:*:*:*:*:*:*:*:*",
   131  		},
   132  		// Note: though the CPE spec does not allow for ? and * as escaped character input, these seem to be allowed in
   133  		// the NVD CPE validator for this reason these edge cases were removed
   134  	}
   135  
   136  	// the wfn library does not account for escapes of . and -
   137  	exceptions := ".-"
   138  	// it isn't easily possible in the string formatted string to detect improper escaping of : (it will fail parsing)
   139  	skip := ":"
   140  
   141  	// make escape exceptions for section 5.3.2 of the CPE spec (2.3)
   142  	for _, char := range allowedCPEPunctuation {
   143  		if strings.Contains(skip, string(char)) {
   144  			continue
   145  		}
   146  
   147  		in := fmt.Sprintf("cpe:2.3:a:some-vendor:name:*:%s:*:*:*:*:*:*", string(char))
   148  		exp := fmt.Sprintf(`cpe:2.3:a:some-vendor:name:*:\%s:*:*:*:*:*:*`, string(char))
   149  		if strings.Contains(exceptions, string(char)) {
   150  			exp = in
   151  		}
   152  
   153  		tests = append(tests, testcase{
   154  			name:        fmt.Sprintf("allowes future escape of character (%s)", string(char)),
   155  			in:          in,
   156  			expected:    exp,
   157  			expectedErr: false,
   158  		})
   159  	}
   160  
   161  	for _, test := range tests {
   162  		t.Run(test.name, func(t *testing.T) {
   163  			c, err := New(test.in)
   164  			if test.expectedErr {
   165  				assert.Error(t, err)
   166  				if t.Failed() {
   167  					t.Logf("got CPE: %q details: %+v", String(c), c)
   168  				}
   169  				return
   170  			}
   171  			require.NoError(t, err)
   172  			assert.Equal(t, test.expected, String(c))
   173  		})
   174  	}
   175  }
   176  
   177  func Test_RoundTrip(t *testing.T) {
   178  	tests := []struct {
   179  		name      string
   180  		cpe       string
   181  		parsedCPE CPE
   182  	}{
   183  		{
   184  			name: "normal",
   185  			cpe:  "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
   186  			parsedCPE: CPE{
   187  				Part:    "a",
   188  				Vendor:  "some-vendor",
   189  				Product: "name",
   190  				Version: "3.2",
   191  			},
   192  		},
   193  		{
   194  			name: "escaped colon",
   195  			cpe:  "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   196  			parsedCPE: CPE{
   197  				Part:    "a",
   198  				Vendor:  "some-vendor",
   199  				Product: "name",
   200  				Version: "1:3.2",
   201  			},
   202  		},
   203  		{
   204  			name: "escaped forward slash",
   205  			cpe:  "cpe:2.3:a:test\\/some-vendor:name:3.2:*:*:*:*:*:*:*",
   206  			parsedCPE: CPE{
   207  				Part:    "a",
   208  				Vendor:  "test/some-vendor",
   209  				Product: "name",
   210  				Version: "3.2",
   211  			},
   212  		},
   213  	}
   214  
   215  	for _, test := range tests {
   216  		t.Run(test.name, func(t *testing.T) {
   217  			// CPE string must be preserved through a round trip
   218  			assert.Equal(t, test.cpe, String(Must(test.cpe)))
   219  			// The parsed CPE must be the same after a round trip
   220  			assert.Equal(t, Must(test.cpe), Must(String(Must(test.cpe))))
   221  			// The test case parsed CPE must be the same after parsing the input string
   222  			assert.Equal(t, test.parsedCPE, Must(test.cpe))
   223  			// The test case parsed CPE must produce the same string as the input cpe
   224  			assert.Equal(t, String(test.parsedCPE), test.cpe)
   225  		})
   226  	}
   227  }