-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathjerry-curl.go
473 lines (413 loc) · 13.4 KB
/
jerry-curl.go
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
/*
Program written by Matt Tesauro <[email protected]>
as part of the OWASP WTE and OWASP OpenStack Security projects
This file, jerry-curl, is a wrapper for the curl command allowing
default arguements to be easly added when invoking curl. This is
partularly useful when headers need to be added to every request
such as HTTP basic auth or talking to REST APIs.
jerry-curl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jerry-curl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with jerry-curl. If not, see <http://www.gnu.org/licenses/>.
Sat, 03 Nov 2012 17:25:25 -0600
*/
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
)
// Global declarations
var configDir string = ".jerry-curl"
var configFile string = "jerry-curl.config"
var home string
var version string = "1.1"
func main() {
// Check if curl is installed
curl := curlCheck()
// Check if the configuration file exits and create if necessary
createConfig()
// Report argument collisions with curl
argClash(os.Args)
// Parse the command line arguments
jerryArgs, curlArgs := parseArgs(os.Args)
// Iterate through jerryArgs to see if alternate config was set
newConfig := ""
for _, value := range jerryArgs {
if (strings.Contains(value, "--config")) || (strings.Contains(value, "-c")) {
sp := strings.Index(value, " ")
newConfig = value[sp+1:]
}
}
// Read config file
base, extraArgs := readConfig(newConfig)
// Interate through the maps and finish generating the final command
curlCmd, showOnly := genCurlCmd(jerryArgs, curlArgs, base)
// Generate curl command with arguments
finalCmd := append(extraArgs, curlCmd...)
// Show command based on command line arguments and exit
if showOnly {
fmt.Println("Here is the curl command which would run:")
fmt.Printf("%s %s\n", curl, strings.Join(finalCmd, " "))
os.Exit(0)
}
// Build the resulting curl command to run
cmd := exec.Command("curl", finalCmd...)
// Catch stdout
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error:\n\t%v", err)
os.Exit(0)
}
// Catch stderr
errorReader, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error:\n\t%v", err)
os.Exit(0)
}
// Start executing the curl command
err = cmd.Start()
if err != nil {
fmt.Printf("Error occurred: %v\n", err)
os.Exit(0)
}
// Use IO copy to print curl's stdout
io.Copy(os.Stdout, stdout)
// Buffer stderr for possible future display
buf := new(bytes.Buffer)
buf.ReadFrom(errorReader)
// Wait for curl to complete
cmd.Wait()
// Check for a curl error
if cmd.ProcessState.String() != "exit status 0" {
// ToDo: Filter out the tranfer progress bar stuff
// annoying but not critical to filter out
fmt.Println("A curl error occured:\n")
fmt.Printf("\t%s\n", buf.String())
}
}
func curlCheck() string {
path, err := exec.LookPath("curl")
if err != nil {
fmt.Println("The curl command must be installed and in your path.")
fmt.Println("Please install curl to enjoy all of jerry-curl")
os.Exit(0)
}
return path
}
func createConfig() {
// Setup a few things
home = os.Getenv("HOME")
if home == "" {
fmt.Println("The environmental varaible HOME needs to be set to your home directory")
os.Exit(0)
}
// ToDo - simplify this to a single path.join with all three strings as args
config := path.Join(home, configDir)
configFile := path.Join(config, configFile)
// Check if the config file exists and return early if its present
file, _ := os.Stat(configFile)
if file != nil {
return
}
// Create the config directory if it doesn't exist
_, err := os.Stat(config)
if err != nil {
// Need to create config directory
err = os.Mkdir(config, 0700)
if err != nil {
fmt.Printf("Unable to create config directory at %s\n", config)
}
}
// Create a default config file
defaultConfig := `# Some examples of items to put in this config file
#
# For repeated requests to the same URL:
# BASE=http://www.example.com
# NOTE: BASE is the only config option done as a key=value pair.
# All others are simply one command line option per line.
# Make sure the line starts with "BASE="
#
# NOTE: DO NOT QUOTE ARGUMENTS TO COMMAND LINE OPTIONS
# jerry-curl will automagically quote them for you
#
# Proxy curl commands:
# --proxy 127.0.0.1:8080
#
# Allow insecure SSL:
# --insecure
#
# Include headers in the output
# -include
#
# Set an Auth header
# -H X-Auth-Token: 55555555-5555-5555-5555-555555555555
#
# Set accepts header
# -H Accept: application/json
#
# Set content-type header
# -H Content-Type: application/json
# `
// Write a default config
fileBytes := []byte(defaultConfig)
err = ioutil.WriteFile(configFile, fileBytes, 0600)
if err != nil {
fmt.Println("Unable to write config file")
fmt.Printf("Please check permissions of %s\n", config)
os.Exit(0)
}
}
func argClash(args []string) {
// Run through the map, counting matches on command line arguements
// used by both jerry-curl and curl or two of the same jerry-curl arguments
countConfig := 0
countC := 0
countShow := 0
countS := 0
countUrl := 0
countU := 0
message := "Error(s):"
for _, arg := range args {
switch arg {
case "--config":
countConfig++
case "-c":
countC++
case "--show":
countShow++
case "-s":
countS++
case "--url-path":
countUrl++
case "-u":
countU++
}
}
// Add any necessary error messages for argument clashes between jerry-curl and curl
if countConfig >= 2 {
message += "\n\t* Option --config used twice and is ambiguous."
message += "\n\t If you want the curl version, use -K instead"
}
if countC >= 2 {
message += "\n\t* Option -c used twice and is ambiguous."
message += "\n\t If you want the curl version, use --cookie-jar <file name> instead"
}
if countS >= 2 {
message += "\n\t* Option -s used twice and is ambiguous."
message += "\n\t If you want the curl version, use --silent instead"
}
if countU >= 2 {
message += "\n\t* Option -u used twice and is ambiguous."
message += "\n\t If you want the curl version, use --user <user:password> instead"
}
// Check for using two forms of the same argument
if (countConfig >= 1) && (countC >= 1) {
message += "\n\t* Both option --config and -c used and is ambiguous."
message += "\n\t jerry-curl accepts either --config OR -c but not both"
}
if (countShow >= 1) && (countS >= 1) {
message += "\n\t* Both option --show and -s used and is ambiguous."
message += "\n\t jerry-curl accepts either --show OR -s but not both"
}
if (countUrl >= 1) && (countU >= 1) {
message += "\n\t* Both option --url-path and -u used and is ambiguous."
message += "\n\t jerry-curl accepts either --url-path OR -u but not both"
}
// Print appropriate message(s) and exit
// if ((countConfig + countC + countS + countU) > 0)
if len(message) > 9 {
fmt.Printf("%s\n", message)
os.Exit(0)
}
}
func parseArgs(args []string) (map[int]string, []string) {
// Setup maps to hold the arguments for both jerry-curl and curl
jerryArgs := make(map[int]string)
var curlArgs []string
// skip set to true to skip adding the os.Args[0] from being processed
skip := true
var next string
// If jerry-curl is called witn no arguments then print the help menu and exit
if len(args) == 1 {
printHelp()
}
// iterate through args sent and place jerry-curl args into one map
// all other args are passed through to curl via curlArgs
for n, arg := range args {
missing := false
switch arg {
case "-c", "--config":
// Check for an option without an arguement at the end of the command line
// which would make args[n+1:n+2] out of bounds
if len(args) < n+2 {
missing = true
} else {
next = strings.Join(args[n+1:n+2], "")
}
if missing || next[0] == 45 {
fmt.Println("Error:")
fmt.Printf(" jerry-curl's %s option requires an agrument\n", arg)
fmt.Printf(" such as %s ./path/to/config\n\n", arg)
os.Exit(0)
}
jerryArgs[n] = arg + " " + next
// skip adding the next arg to curlArgs
skip = true
case "-s", "--show":
jerryArgs[n] = arg
case "-u", "--url-path":
// As above, check args length before slicing
if len(args) < n+2 {
missing = true
} else {
next = strings.Join(args[n+1:n+2], "")
}
if missing || next[0] == 45 {
fmt.Println("Error:")
fmt.Printf(" jerry-curl's %s option requires an agrument\n", arg)
fmt.Printf(" such as %s /path/to/add/to/url\n\n", arg)
os.Exit(0)
}
jerryArgs[n] = arg + " " + next
// skip adding the next arg to curlArgs
skip = true
case "-h", "--help":
//If we're here, print the help and exit
printHelp()
default:
if skip == false {
curlArgs = append(curlArgs, arg)
} else {
skip = false
}
}
}
return jerryArgs, curlArgs
}
func printHelp() {
// Print jerry-curls help message and exit
fmt.Println("Usage: jerry-curl [-h|--help]")
fmt.Println(" or: jerry-curl [jerry-curl options] [optional arguments for curl]")
fmt.Println("")
fmt.Printf(" jerry-curl version %s\n", version)
fmt.Println("")
fmt.Println(" jerry-curl is a wrapper for the curl command which adds ")
fmt.Println(" options from a configuration file and the command line")
fmt.Println(" allowing for short repeated curl calls.")
fmt.Println("")
fmt.Println(" jerry-curl works by calling curl like the below:")
fmt.Println(" curl [config options] [BASE][URLPATH] [command-line arguments]")
fmt.Println("")
fmt.Println(" jerry-curl commandline options:")
fmt.Println(" -c, --config FILE Select a different config file from the default")
fmt.Println(" which is $HOME/.jerry-curl/jerry-curl.config")
fmt.Println(" Example: jerry-curl --config=./my-custom-config")
fmt.Println(" -s, --show Show the curl command - DO NOT EXECUTE IT")
fmt.Println(" -u, --url-path URLPATH Set a path to append to the base URL")
fmt.Println(" Example: jerry-curl --url-path=/app/path/here")
fmt.Println(" -h, --help help, aka this message")
fmt.Println("")
fmt.Println(" Note: options --config, -c, -s -u are used by both jerry-curl and curl. If")
fmt.Println(" You want those sent to curl, please use their alternate forms. Using --show")
fmt.Println(" can help diagose if jerry-curl or curl is recieving a command-line option")
fmt.Println("")
fmt.Println(" If no config file exists, one will be created with commented examples in directory named ")
fmt.Println(" .jerry-curl in your home directory when jerry-curl is run for the first time.")
fmt.Println(" For user ltorvalds, it would be /home/ltorvalds/.jerry-curl/jerry-curl.config")
fmt.Println("")
os.Exit(0)
}
func readConfig(newConfig string) (string, []string) {
var extras []string // extra args from config file
var base string // base URL from config file
var config string = ""
// Check to see if the config option was used
if len(newConfig) > 0 {
config = newConfig
} else {
// Setup a few things to use the default config location
home = os.Getenv("HOME")
if home == "" {
fmt.Println("The environmental varaible HOME needs to be set to your home directory")
os.Exit(0)
}
config = path.Join(home, configDir, configFile)
}
// Read configuration file to pull out any configured items
file, err := os.Open(config)
if err != nil {
fmt.Printf("Error reading configuration file at %s\n", config)
fmt.Printf("\t OS error: %s\n", err.Error())
os.Exit(0)
}
defer file.Close()
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n')
for err == nil {
// Handle lines that are not comments
if strings.Index(line, "#") != 0 {
line = strings.Trim(line, " ")
n := strings.Index(line, "BASE=")
// Set base or else its an extra arg for curl
if n == 0 {
base = strings.TrimRight(line[n+5:], "\n")
} else {
p := strings.Index(line, " ")
if p == -1 {
extras = append(extras, strings.TrimRight(line, "\n"))
} else {
quoted := strings.SplitAfterN(strings.TrimRight(line, "\n"), " ", 2)
extras = append(extras, strings.Trim(quoted[0], " "))
extras = append(extras, strings.Trim(quoted[1], " "))
}
}
}
line, err = reader.ReadString('\n')
}
if err != io.EOF {
fmt.Println(err)
os.Exit(0)
}
return base, extras
}
func genCurlCmd(jerry map[int]string, curl []string, base string) ([]string, bool) {
// Some setup
show := false
path := ""
var sCurl []string
// Iterate through jerry pulling out any necessary bits
for _, arg := range jerry {
if (strings.Index(arg, "--show") == 0) || (strings.Index(arg, "-s") == 0) {
show = true
}
if (strings.Index(arg, "--url-path") == 0) || (strings.Index(arg, "-u")) == 0 {
path = strings.Replace(arg, "--url-path ", "", 1)
path = strings.TrimLeft(path, "-u ")
}
}
// Iterate through curl arguements and sanity check them
// for quoted arguments
for _, item := range curl[:] {
if strings.Index(item, "\"") >= 0 {
newItem := "'" + item + "'"
sCurl = append(sCurl, newItem)
} else {
sCurl = append(sCurl, item)
}
}
url := strings.Trim(base, " ") + strings.Trim(path, " ")
return append(sCurl, url), show
}