-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathmain.go
More file actions
258 lines (212 loc) · 8.32 KB
/
main.go
File metadata and controls
258 lines (212 loc) · 8.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package main
import (
"archive/zip"
"flag"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/fatih/color"
)
var doesSCAFileExist bool = false
var doesMapFileExist bool = false
func main() {
// parse all the command line flags
sourcePtr := flag.String("source", "", "The path of the JavaScript app you want to package (required)")
targetPtr := flag.String("target", ".", "The path where you want the vc-output.zip to be stored to")
testsPtr := flag.String("tests", "", "The path that contains your test files (relative to the source). Uses a heuristic to identifiy tests automatically in case no path is provided")
// overwrite `flag.Usage` to print a usage example and a program description when `--help` is called
flag.Usage = func() {
// the binary name of this tool
binaryName := filepath.Base(os.Args[0])
// may be `os.Stderr` but not necessarily
w := flag.CommandLine.Output()
fmt.Fprintf(w, "Usage of %s:\n", binaryName)
flag.PrintDefaults()
fmt.Fprintf(w, "\nExample: \n\t%s -source ./sample-projects/sample-node-project -target .\n", binaryName)
}
flag.Parse()
color.Green("#################################################")
color.Green("# #")
color.Green("# Veracode JavaScript Packager (Unofficial) #")
color.Green("# #")
color.Green("#################################################" + "\n\n")
// check if the AppVersion was already set during compilation - otherwise manually get it from `./current_version`
CheckAppVersion()
color.Yellow("Current version: %s\n\n", AppVersion)
// check if a later version of this tool exists
NotifyOfUpdates()
// fail if `--source` was not provided
if *sourcePtr == "" {
color.Red("No `-source` was provided. Run `--help` for the built-in help.")
return
}
// add the current date to the output zip name, like e.g. "2023-Jan-04"
currentTime := time.Now()
outputZipPath := filepath.Join(*targetPtr, "vc-output_"+currentTime.Format("2006-Jan-02")+".zip")
// echo the provided flags
var testsPath string
log.Info("Provided Flags:")
log.Info("\t`-source` directory to zip up: ", *sourcePtr)
log.Info("\t`-target` directory for the output: ", *targetPtr)
if *testsPtr == "" {
log.Info("\tNo `-test` directory was provided... Heuristics will be used to identify (and omit) common test directory names" + "\n\n")
testsPath = ""
} else {
// combine that last segment of the `sourcePtr` with the value provided via `-test`.
// Example: If `-test mytests` and `-source /some/node-project`, then `testsPathToLog` will be: "node-project/mytests"
var testsPathToLog string = filepath.Join(path.Base(*sourcePtr), *testsPtr)
log.Info("\tProvided `-test` directory (its content will be omitted): ", testsPathToLog, "\n\n")
// we want the test path to start with a `/`, e.g. `test` would become `/test`. This makes string matching easier
// as we can e.g. check if a path has the suffix `/test` instead of checking if it has the suffix `test` (the latter may be
// ambigious; e.g. "attest" has the suffix "test" but may contain actual source code and not tests)
testsPath = string(os.PathSeparator) + *testsPtr
}
// check for some "smells" (e.g. the `package-lock.json` file is missing), and print corresponding warnings/errors
log.Info("Checking for 'smells' that indicate packaging issues - Started...")
checkForPotentialSmells(*sourcePtr)
log.Info("'Smells' Check - Done\n\n")
log.Info("Creating a Zip while omitting non-required files - Started...")
// generate the zip file, and omit all non-required files
if err := zipSource(*sourcePtr, outputZipPath, testsPath); err != nil {
log.Error(err)
}
log.Info("Zip Process - Done")
log.Info("Wrote archive to: ", outputZipPath)
log.Info("Please upload this archive to the Veracode Platform")
}
func checkForPotentialSmells(source string) {
err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// only do checks for first party code
if !strings.Contains(path, "node_modules") {
// check if one of the files required for SCA exists... Note that `bower.json` may be part of `bower_components`. Thus,
// the `if` above does not account for `bower_components` even though it has 3rd party code.
if !doesSCAFileExist {
doesSCAFileExist = CheckIfSCAFileExists(path)
}
// for the remaining checks, we don't want to look into `bower_components` or any other sort of build folder
if !strings.Contains(path, "bower_components") && !strings.Contains(path, "build") &&
!strings.Contains(path, "dist") && !strings.Contains(path, "public") {
// check for `.map` files (only in non-3rd party and "non-build" code)
if strings.HasSuffix(path, ".map") {
doesMapFileExist = true
}
}
}
return nil
})
if err != nil {
log.Error(err)
}
if !doesSCAFileExist {
log.Warn("\tNo `package-lock.json` or `yarn.lock` or `bower.json` file found.. (This file is required for Veracode SCA)..." +
" You may not receive Veracode SCA results!")
}
if doesMapFileExist {
log.Warn("\tThe 1st party code contains `.map` files outside of `/build`, `/dist` or `/public` (which indicates minified JavaScript)...")
log.Warn("\tPlease pass a directory to this tool that contains the unminified/unbundled/unconcatenated JavaScript (or TypeScript)")
}
}
func zipSource(source string, target string, testsPath string) error {
// 1. Create a ZIP file and zip.Writer
f, err := os.Create(target)
if err != nil {
return err
}
defer f.Close()
writer := zip.NewWriter(f)
defer writer.Close()
// 2. Go through all the files of the source
return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// avoids processing the created zip...
// - Say the tool is finished and an `/vc-output_2023-Jan-05.zip` is created...
// - In this case, the analysis may restart with this zip as `path`
// - This edge case was observed when running the tool within a sample JS app..
// - ... i.e., `veracode-js-packager -source . -target .`
if strings.HasSuffix(path, ".zip") {
return nil
}
// avoid processing the Veracode JavaScript Packager binary itself - in case it is copied into the
// directory where the JS app resides
if strings.Contains(path, "veracode-js-packager") || strings.Contains(path, "vc-js-packager") {
return nil
}
// 3. Create a local file header
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// set compression
header.Method = zip.Deflate
// 4. Set relative path of a file as the header name
// -> We want the following:
// - Say `-source some/path/my-js-project` is provided...
// - Now, say we have a path `some/path/my-js-project/build/some.js`....
// - In this scenario, we want `header.Name` to be `build/some.js`
header.Name, err = filepath.Rel(source, path)
if err != nil {
return err
}
// avoids the `./` folder in the root of the output zip
if header.Name == "." {
return nil
}
// prepends the `/` we want before e.g. `build/some.js`
headerNameWithSlash := string(os.PathSeparator) + header.Name
// check if the path is required for the upload (otherwise, it will be omitted)
if !isRequired(headerNameWithSlash, testsPath) {
return nil
}
if info.IsDir() {
// add e.g. a `/` if the current path is a directory
header.Name += string(os.PathSeparator)
}
// 5. Create writer for the file header and save content of the file
headerWriter, err := writer.CreateHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(headerWriter, f)
return err
})
}
func isRequired(path string, testsPath string) bool {
return !IsNodeModules(path) &&
!IsAngularCacheFolder(path) &&
!IsBowerComponents(path) &&
!IsGitFolder(path) &&
!IsInTestFolder(path, testsPath) &&
!IsTestFile(path) &&
!IsStyleSheet(path) &&
!IsImage(path) &&
!IsVideo(path) &&
!IsDocument(path) &&
!IsFont(path) &&
!IsDb(path) &&
!IsBuildFolder(path) &&
!IsDistFolder(path) &&
!IsPublicFolder(path) &&
!IsIdeFolder(path) &&
!IsMinified(path) &&
!IsArchive(path) &&
!IsMiscNotRequiredFile(path)
// the default is to not omit the file
}