@@ -3,7 +3,7 @@ import { execSync } from "node:child_process"
3
3
import fs from 'node:fs'
4
4
import os from "node:os" ;
5
5
import { EOL } from "os" ;
6
- import { getCustomPath } from "../tools.js" ;
6
+ import { getCustom , getCustomPath } from "../tools.js" ;
7
7
import path from 'node:path'
8
8
import Sbom from '../sbom.js'
9
9
import { PackageURL } from 'packageurl-js'
@@ -174,6 +174,93 @@ function enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, s
174
174
} )
175
175
}
176
176
177
+ /**
178
+ *
179
+ * @param {[string] } lines - array of lines of go.mod manifest
180
+ * @param {string } goMod - content of go.mod manifest
181
+ * @return {[string] } all dependencies from go.mod file as array
182
+ */
183
+ function collectAllDepsFromManifest ( lines , goMod ) {
184
+ let result
185
+ // collect all deps that starts with require keyword
186
+
187
+ result = lines . filter ( ( line ) => line . trim ( ) . startsWith ( "require" ) && ! line . includes ( "(" ) ) . map ( ( dep ) => dep . substring ( "require" . length ) . trim ( ) )
188
+
189
+
190
+
191
+ // collect all deps that are inside `require` blocks
192
+ let currentSegmentOfGoMod = goMod
193
+ let requirePositionObject = decideRequireBlockIndex ( currentSegmentOfGoMod )
194
+ while ( requirePositionObject . index > - 1 ) {
195
+ let depsInsideRequirementsBlock = currentSegmentOfGoMod . substring ( requirePositionObject . index + requirePositionObject . startingOffeset ) . trim ( ) ;
196
+ let endOfBlockIndex = depsInsideRequirementsBlock . indexOf ( ")" )
197
+ let currentIndex = 0
198
+ while ( currentIndex < endOfBlockIndex )
199
+ {
200
+ let endOfLinePosition = depsInsideRequirementsBlock . indexOf ( EOL , currentIndex ) ;
201
+ let dependency = depsInsideRequirementsBlock . substring ( currentIndex , endOfLinePosition )
202
+ result . push ( dependency . trim ( ) )
203
+ currentIndex = endOfLinePosition + 1
204
+ }
205
+ currentSegmentOfGoMod = currentSegmentOfGoMod . substring ( endOfBlockIndex + 1 ) . trim ( )
206
+ requirePositionObject = decideRequireBlockIndex ( currentSegmentOfGoMod )
207
+ }
208
+
209
+ function decideRequireBlockIndex ( goMod ) {
210
+ let object = { }
211
+ let index = goMod . indexOf ( "require(" )
212
+ object . startingOffeset = "require(" . length
213
+ if ( index === - 1 )
214
+ {
215
+ index = goMod . indexOf ( "require (" )
216
+ object . startingOffeset = "require (" . length
217
+ if ( index === - 1 )
218
+ {
219
+ index = goMod . indexOf ( "require (" )
220
+ object . startingOffeset = "require (" . length
221
+ }
222
+ }
223
+ object . index = index
224
+ return object
225
+ }
226
+ return result
227
+ }
228
+
229
+ /**
230
+ *
231
+ * @param {string } rootElementName the rootElementName element of go mod graph, to compare only direct deps from go mod graph against go.mod manifest
232
+ * @param {[string] } goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
233
+ * @param {string }manifest path to go.mod manifest on file system
234
+ * @private
235
+ */
236
+ function performManifestVersionsCheck ( rootElementName , goModGraphOutputRows , manifest ) {
237
+ let goMod = fs . readFileSync ( manifest ) . toString ( ) . trim ( )
238
+ let lines = goMod . split ( EOL ) ;
239
+ let comparisonLines = goModGraphOutputRows . filter ( ( line ) => line . startsWith ( rootElementName ) ) . map ( ( line ) => getChildVertexFromEdge ( line ) )
240
+ let manifestDeps = collectAllDepsFromManifest ( lines , goMod )
241
+ try {
242
+ comparisonLines . forEach ( ( dependency ) => {
243
+ let parts = dependency . split ( "@" )
244
+ let version = parts [ 1 ]
245
+ let depName = parts [ 0 ]
246
+ manifestDeps . forEach ( dep => {
247
+ let components = dep . trim ( ) . split ( " " ) ;
248
+ let currentDepName = components [ 0 ]
249
+ let currentVersion = components [ 1 ]
250
+ if ( currentDepName === depName ) {
251
+ if ( currentVersion !== version ) {
252
+ throw new Error ( `versions mismatch for dependency name ${ depName } , manifest version=${ currentVersion } , installed Version=${ version } , if you want to allow version mismatch for analysis between installed and requested packages, set environment variable/setting - MATCH_MANIFEST_VERSIONS=false` )
253
+ }
254
+ }
255
+ } )
256
+ } )
257
+ }
258
+ catch ( error ) {
259
+ console . error ( "Can't continue with analysis" )
260
+ throw error
261
+ }
262
+ }
263
+
177
264
/**
178
265
* Create SBOM json string for go Module.
179
266
* @param {string } manifest - path for go.mod
@@ -201,6 +288,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
201
288
let sbom = new Sbom ( ) ;
202
289
let rows = goGraphOutput . split ( EOL ) ;
203
290
let root = getParentVertexFromEdge ( rows [ 0 ] )
291
+ let matchManifestVersions = getCustom ( "MATCH_MANIFEST_VERSIONS" , "false" ) ;
292
+ if ( matchManifestVersions === "true" ) {
293
+ {
294
+ performManifestVersionsCheck ( root , rows , manifest )
295
+ }
296
+ }
204
297
let mainModule = toPurl ( root , "@" , undefined )
205
298
sbom . addRoot ( mainModule )
206
299
0 commit comments