Delete some now unused commands lib code
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Nov 21, 2017 at 20:40 UTC
68bb106980bd4a49f4c388add19be1d3d835d548
11 files changed
-2948
commands/cli/cmd_suggestion.go
deleted
-89
@@ -1,89 +0,0 @@
1
-package cli
2
-
3
-import (
4
- "fmt"
5
- "sort"
6
- "strings"
7
-
8
- levenshtein "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/texttheater/golang-levenshtein/levenshtein"
9
- cmds "github.com/ipfs/go-ipfs/commands"
10
-)
11
-
12
-// Make a custom slice that can be sorted by its levenshtein value
13
-type suggestionSlice []*suggestion
14
-
15
-type suggestion struct {
16
- cmd string
17
- levenshtein int
18
-}
19
-
20
-func (s suggestionSlice) Len() int {
21
- return len(s)
22
-}
23
-
24
-func (s suggestionSlice) Swap(i, j int) {
25
- s[i], s[j] = s[j], s[i]
26
-}
27
-
28
-func (s suggestionSlice) Less(i, j int) bool {
29
- return s[i].levenshtein < s[j].levenshtein
30
-}
31
-
32
-func suggestUnknownCmd(args []string, root *cmds.Command) []string {
33
- if root == nil {
34
- return nil
35
- }
36
-
37
- arg := args[0]
38
- var suggestions []string
39
- sortableSuggestions := make(suggestionSlice, 0)
40
- var sFinal []string
41
- const MIN_LEVENSHTEIN = 3
42
-
43
- var options = levenshtein.Options{
44
- InsCost: 1,
45
- DelCost: 3,
46
- SubCost: 2,
47
- Matches: func(sourceCharacter rune, targetCharacter rune) bool {
48
- return sourceCharacter == targetCharacter
49
- },
50
- }
51
-
52
- // Start with a simple strings.Contains check
53
- for name := range root.Subcommands {
54
- if strings.Contains(arg, name) {
55
- suggestions = append(suggestions, name)
56
- }
57
- }
58
-
59
- // If the string compare returns a match, return
60
- if len(suggestions) > 0 {
61
- return suggestions
62
- }
63
-
64
- for name := range root.Subcommands {
65
- lev := levenshtein.DistanceForStrings([]rune(arg), []rune(name), options)
66
- if lev <= MIN_LEVENSHTEIN {
67
- sortableSuggestions = append(sortableSuggestions, &suggestion{name, lev})
68
- }
69
- }
70
- sort.Sort(sortableSuggestions)
71
-
72
- for _, j := range sortableSuggestions {
73
- sFinal = append(sFinal, j.cmd)
74
- }
75
- return sFinal
76
-}
77
-
78
-func printSuggestions(inputs []string, root *cmds.Command) (err error) {
79
-
80
- suggestions := suggestUnknownCmd(inputs, root)
81
- if len(suggestions) > 1 {
82
- err = fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean any of these?\n\n\t%s", inputs[0], strings.Join(suggestions, "\n\t"))
83
- } else if len(suggestions) > 0 {
84
- err = fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean this?\n\n\t%s", inputs[0], suggestions[0])
85
- } else {
86
- err = fmt.Errorf("Unknown Command %q", inputs[0])
87
- }
88
- return
89
-}
commands/cli/helptext.go
deleted
-449
@@ -1,449 +0,0 @@
1
-package cli
2
-
3
-import (
4
- "fmt"
5
- "io"
6
- "sort"
7
- "strings"
8
- "text/template"
9
-
10
- cmds "github.com/ipfs/go-ipfs/commands"
11
- cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
12
-)
13
-
14
-const (
15
- requiredArg = "<%v>"
16
- optionalArg = "[<%v>]"
17
- variadicArg = "%v..."
18
- shortFlag = "-%v"
19
- longFlag = "--%v"
20
-
21
- indentStr = " "
22
-)
23
-
24
-type helpFields struct {
25
- Indent string
26
- Usage string
27
- Path string
28
- ArgUsage string
29
- Tagline string
30
- Arguments string
31
- Options string
32
- Synopsis string
33
- Subcommands string
34
- Description string
35
- MoreHelp bool
36
-}
37
-
38
-// TrimNewlines removes extra newlines from fields. This makes aligning
39
-// commands easier. Below, the leading + tralining newlines are removed:
40
-// Synopsis: `
41
-// ipfs config <key> - Get value of <key>
42
-// ipfs config <key> <value> - Set value of <key> to <value>
43
-// ipfs config --show - Show config file
44
-// ipfs config --edit - Edit config file in $EDITOR
45
-// `
46
-func (f *helpFields) TrimNewlines() {
47
- f.Path = strings.Trim(f.Path, "\n")
48
- f.ArgUsage = strings.Trim(f.ArgUsage, "\n")
49
- f.Tagline = strings.Trim(f.Tagline, "\n")
50
- f.Arguments = strings.Trim(f.Arguments, "\n")
51
- f.Options = strings.Trim(f.Options, "\n")
52
- f.Synopsis = strings.Trim(f.Synopsis, "\n")
53
- f.Subcommands = strings.Trim(f.Subcommands, "\n")
54
- f.Description = strings.Trim(f.Description, "\n")
55
-}
56
-
57
-// Indent adds whitespace the lines of fields.
58
-func (f *helpFields) IndentAll() {
59
- indent := func(s string) string {
60
- if s == "" {
61
- return s
62
- }
63
- return indentString(s, indentStr)
64
- }
65
-
66
- f.Arguments = indent(f.Arguments)
67
- f.Options = indent(f.Options)
68
- f.Synopsis = indent(f.Synopsis)
69
- f.Subcommands = indent(f.Subcommands)
70
- f.Description = indent(f.Description)
71
-}
72
-
73
-const usageFormat = "{{if .Usage}}{{.Usage}}{{else}}{{.Path}}{{if .ArgUsage}} {{.ArgUsage}}{{end}} - {{.Tagline}}{{end}}"
74
-
75
-const longHelpFormat = `USAGE
76
-{{.Indent}}{{template "usage" .}}
77
-
78
-{{if .Synopsis}}SYNOPSIS
79
-{{.Synopsis}}
80
-
81
-{{end}}{{if .Arguments}}ARGUMENTS
82
-
83
-{{.Arguments}}
84
-
85
-{{end}}{{if .Options}}OPTIONS
86
-
87
-{{.Options}}
88
-
89
-{{end}}{{if .Description}}DESCRIPTION
90
-
91
-{{.Description}}
92
-
93
-{{end}}{{if .Subcommands}}SUBCOMMANDS
94
-{{.Subcommands}}
95
-
96
-{{.Indent}}Use '{{.Path}} <subcmd> --help' for more information about each command.
97
-{{end}}
98
-`
99
-const shortHelpFormat = `USAGE
100
-{{.Indent}}{{template "usage" .}}
101
-{{if .Synopsis}}
102
-{{.Synopsis}}
103
-{{end}}{{if .Description}}
104
-{{.Description}}
105
-{{end}}{{if .Subcommands}}
106
-SUBCOMMANDS
107
-{{.Subcommands}}
108
-{{end}}{{if .MoreHelp}}
109
-Use '{{.Path}} --help' for more information about this command.
110
-{{end}}
111
-`
112
-
113
-var usageTemplate *template.Template
114
-var longHelpTemplate *template.Template
115
-var shortHelpTemplate *template.Template
116
-
117
-func init() {
118
- usageTemplate = template.Must(template.New("usage").Parse(usageFormat))
119
- longHelpTemplate = template.Must(usageTemplate.New("longHelp").Parse(longHelpFormat))
120
- shortHelpTemplate = template.Must(usageTemplate.New("shortHelp").Parse(shortHelpFormat))
121
-}
122
-
123
-// LongHelp writes a formatted CLI helptext string to a Writer for the given command
124
-func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer) error {
125
- cmd, err := root.Get(path)
126
- if err != nil {
127
- return err
128
- }
129
-
130
- pathStr := rootName
131
- if len(path) > 0 {
132
- pathStr += " " + strings.Join(path, " ")
133
- }
134
-
135
- fields := helpFields{
136
- Indent: indentStr,
137
- Path: pathStr,
138
- ArgUsage: usageText(cmd),
139
- Tagline: cmd.Helptext.Tagline,
140
- Arguments: cmd.Helptext.Arguments,
141
- Options: cmd.Helptext.Options,
142
- Synopsis: cmd.Helptext.Synopsis,
143
- Subcommands: cmd.Helptext.Subcommands,
144
- Description: cmd.Helptext.ShortDescription,
145
- Usage: cmd.Helptext.Usage,
146
- MoreHelp: (cmd != root),
147
- }
148
-
149
- if len(cmd.Helptext.LongDescription) > 0 {
150
- fields.Description = cmd.Helptext.LongDescription
151
- }
152
-
153
- // autogen fields that are empty
154
- if len(fields.Arguments) == 0 {
155
- fields.Arguments = strings.Join(argumentText(cmd), "\n")
156
- }
157
- if len(fields.Options) == 0 {
158
- fields.Options = strings.Join(optionText(cmd), "\n")
159
- }
160
- if len(fields.Subcommands) == 0 {
161
- fields.Subcommands = strings.Join(subcommandText(cmd, rootName, path), "\n")
162
- }
163
- if len(fields.Synopsis) == 0 {
164
- fields.Synopsis = generateSynopsis(cmd, pathStr)
165
- }
166
-
167
- // trim the extra newlines (see TrimNewlines doc)
168
- fields.TrimNewlines()
169
-
170
- // indent all fields that have been set
171
- fields.IndentAll()
172
-
173
- return longHelpTemplate.Execute(out, fields)
174
-}
175
-
176
-// ShortHelp writes a formatted CLI helptext string to a Writer for the given command
177
-func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer) error {
178
- cmd, err := root.Get(path)
179
- if err != nil {
180
- return err
181
- }
182
-
183
- // default cmd to root if there is no path
184
- if path == nil && cmd == nil {
185
- cmd = root
186
- }
187
-
188
- pathStr := rootName
189
- if len(path) > 0 {
190
- pathStr += " " + strings.Join(path, " ")
191
- }
192
-
193
- fields := helpFields{
194
- Indent: indentStr,
195
- Path: pathStr,
196
- ArgUsage: usageText(cmd),
197
- Tagline: cmd.Helptext.Tagline,
198
- Synopsis: cmd.Helptext.Synopsis,
199
- Description: cmd.Helptext.ShortDescription,
200
- Subcommands: cmd.Helptext.Subcommands,
201
- Usage: cmd.Helptext.Usage,
202
- MoreHelp: (cmd != root),
203
- }
204
-
205
- // autogen fields that are empty
206
- if len(fields.Subcommands) == 0 {
207
- fields.Subcommands = strings.Join(subcommandText(cmd, rootName, path), "\n")
208
- }
209
- if len(fields.Synopsis) == 0 {
210
- fields.Synopsis = generateSynopsis(cmd, pathStr)
211
- }
212
-
213
- // trim the extra newlines (see TrimNewlines doc)
214
- fields.TrimNewlines()
215
-
216
- // indent all fields that have been set
217
- fields.IndentAll()
218
-
219
- return shortHelpTemplate.Execute(out, fields)
220
-}
221
-
222
-func generateSynopsis(cmd *cmds.Command, path string) string {
223
- res := path
224
- for _, opt := range cmd.Options {
225
- valopt, ok := cmd.Helptext.SynopsisOptionsValues[opt.Names()[0]]
226
- if !ok {
227
- valopt = opt.Names()[0]
228
- }
229
- sopt := ""
230
- for i, n := range opt.Names() {
231
- pre := "-"
232
- if len(n) > 1 {
233
- pre = "--"
234
- }
235
- if opt.Type() == cmdkit.Bool && opt.Default() == true {
236
- pre = "--"
237
- sopt = fmt.Sprintf("%s%s=false", pre, n)
238
- break
239
- } else {
240
- if i == 0 {
241
- if opt.Type() == cmdkit.Bool {
242
- sopt = fmt.Sprintf("%s%s", pre, n)
243
- } else {
244
- sopt = fmt.Sprintf("%s%s=<%s>", pre, n, valopt)
245
- }
246
- } else {
247
- sopt = fmt.Sprintf("%s | %s%s", sopt, pre, n)
248
- }
249
- }
250
- }
251
- res = fmt.Sprintf("%s [%s]", res, sopt)
252
- }
253
- if len(cmd.Arguments) > 0 {
254
- res = fmt.Sprintf("%s [--]", res)
255
- }
256
- for _, arg := range cmd.Arguments {
257
- sarg := fmt.Sprintf("<%s>", arg.Name)
258
- if arg.Variadic {
259
- sarg = sarg + "..."
260
- }
261
-
262
- if !arg.Required {
263
- sarg = fmt.Sprintf("[%s]", sarg)
264
- }
265
- res = fmt.Sprintf("%s %s", res, sarg)
266
- }
267
- return strings.Trim(res, " ")
268
-}
269
-
270
-func argumentText(cmd *cmds.Command) []string {
271
- lines := make([]string, len(cmd.Arguments))
272
-
273
- for i, arg := range cmd.Arguments {
274
- lines[i] = argUsageText(arg)
275
- }
276
- lines = align(lines)
277
- for i, arg := range cmd.Arguments {
278
- lines[i] += " - " + arg.Description
279
- }
280
-
281
- return lines
282
-}
283
-
284
-func optionFlag(flag string) string {
285
- if len(flag) == 1 {
286
- return fmt.Sprintf(shortFlag, flag)
287
- }
288
-
289
- return fmt.Sprintf(longFlag, flag)
290
-}
291
-
292
-func optionText(cmd ...*cmds.Command) []string {
293
- // get a slice of the options we want to list out
294
- options := make([]cmdkit.Option, 0)
295
- for _, c := range cmd {
296
- options = append(options, c.Options...)
297
- }
298
-
299
- // add option names to output (with each name aligned)
300
- lines := make([]string, 0)
301
- j := 0
302
- for {
303
- done := true
304
- i := 0
305
- for _, opt := range options {
306
- if len(lines) < i+1 {
307
- lines = append(lines, "")
308
- }
309
-
310
- names := sortByLength(opt.Names())
311
- if len(names) >= j+1 {
312
- lines[i] += optionFlag(names[j])
313
- }
314
- if len(names) > j+1 {
315
- lines[i] += ", "
316
- done = false
317
- }
318
-
319
- i++
320
- }
321
-
322
- if done {
323
- break
324
- }
325
-
326
- lines = align(lines)
327
- j++
328
- }
329
- lines = align(lines)
330
-
331
- // add option types to output
332
- for i, opt := range options {
333
- lines[i] += " " + fmt.Sprintf("%v", opt.Type())
334
- }
335
- lines = align(lines)
336
-
337
- // add option descriptions to output
338
- for i, opt := range options {
339
- lines[i] += " - " + opt.Description()
340
- }
341
-
342
- return lines
343
-}
344
-
345
-func subcommandText(cmd *cmds.Command, rootName string, path []string) []string {
346
- prefix := fmt.Sprintf("%v %v", rootName, strings.Join(path, " "))
347
- if len(path) > 0 {
348
- prefix += " "
349
- }
350
-
351
- // Sorting fixes changing order bug #2981.
352
- sortedNames := make([]string, 0)
353
- for name := range cmd.Subcommands {
354
- sortedNames = append(sortedNames, name)
355
- }
356
- sort.Strings(sortedNames)
357
-
358
- subcmds := make([]*cmds.Command, len(cmd.Subcommands))
359
- lines := make([]string, len(cmd.Subcommands))
360
-
361
- for i, name := range sortedNames {
362
- sub := cmd.Subcommands[name]
363
- usage := usageText(sub)
364
- if len(usage) > 0 {
365
- usage = " " + usage
366
- }
367
- lines[i] = prefix + name + usage
368
- subcmds[i] = sub
369
- }
370
-
371
- lines = align(lines)
372
- for i, sub := range subcmds {
373
- lines[i] += " - " + sub.Helptext.Tagline
374
- }
375
-
376
- return lines
377
-}
378
-
379
-func usageText(cmd *cmds.Command) string {
380
- s := ""
381
- for i, arg := range cmd.Arguments {
382
- if i != 0 {
383
- s += " "
384
- }
385
- s += argUsageText(arg)
386
- }
387
-
388
- return s
389
-}
390
-
391
-func argUsageText(arg cmdkit.Argument) string {
392
- s := arg.Name
393
-
394
- if arg.Required {
395
- s = fmt.Sprintf(requiredArg, s)
396
- } else {
397
- s = fmt.Sprintf(optionalArg, s)
398
- }
399
-
400
- if arg.Variadic {
401
- s = fmt.Sprintf(variadicArg, s)
402
- }
403
-
404
- return s
405
-}
406
-
407
-func align(lines []string) []string {
408
- longest := 0
409
- for _, line := range lines {
410
- length := len(line)
411
- if length > longest {
412
- longest = length
413
- }
414
- }
415
-
416
- for i, line := range lines {
417
- length := len(line)
418
- if length > 0 {
419
- lines[i] += strings.Repeat(" ", longest-length)
420
- }
421
- }
422
-
423
- return lines
424
-}
425
-
426
-func indentString(line string, prefix string) string {
427
- return prefix + strings.Replace(line, "\n", "\n"+prefix, -1)
428
-}
429
-
430
-type lengthSlice []string
431
-
432
-func (ls lengthSlice) Len() int {
433
- return len(ls)
434
-}
435
-func (ls lengthSlice) Swap(a, b int) {
436
- ls[a], ls[b] = ls[b], ls[a]
437
-}
438
-func (ls lengthSlice) Less(a, b int) bool {
439
- return len(ls[a]) < len(ls[b])
440
-}
441
-
442
-func sortByLength(slice []string) []string {
443
- output := make(lengthSlice, len(slice))
444
- for i, val := range slice {
445
- output[i] = val
446
- }
447
- sort.Sort(output)
448
- return []string(output)
449
-}
commands/cli/helptext_test.go
deleted
-47
@@ -1,47 +0,0 @@
1
-package cli
2
-
3
-import (
4
- "strings"
5
- "testing"
6
-
7
- cmds "github.com/ipfs/go-ipfs/commands"
8
-
9
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
10
-)
11
-
12
-func TestSynopsisGenerator(t *testing.T) {
13
- command := &cmds.Command{
14
- Arguments: []cmdkit.Argument{
15
- cmdkit.StringArg("required", true, false, ""),
16
- cmdkit.StringArg("variadic", false, true, ""),
17
- },
18
- Options: []cmdkit.Option{
19
- cmdkit.StringOption("opt", "o", "Option"),
20
- },
21
- Helptext: cmdkit.HelpText{
22
- SynopsisOptionsValues: map[string]string{
23
- "opt": "OPTION",
24
- },
25
- },
26
- }
27
- syn := generateSynopsis(command, "cmd")
28
- t.Logf("Synopsis is: %s", syn)
29
- if !strings.HasPrefix(syn, "cmd ") {
30
- t.Fatal("Synopsis should start with command name")
31
- }
32
- if !strings.Contains(syn, "[--opt=<OPTION> | -o]") {
33
- t.Fatal("Synopsis should contain option descriptor")
34
- }
35
- if !strings.Contains(syn, "<required>") {
36
- t.Fatal("Synopsis should contain required argument")
37
- }
38
- if !strings.Contains(syn, "<variadic>...") {
39
- t.Fatal("Synopsis should contain variadic argument")
40
- }
41
- if !strings.Contains(syn, "[<variadic>...]") {
42
- t.Fatal("Synopsis should contain optional argument")
43
- }
44
- if !strings.Contains(syn, "[--]") {
45
- t.Fatal("Synopsis should contain options finalizer")
46
- }
47
-}
commands/cli/parse.go
deleted
-526
@@ -1,526 +0,0 @@
1
-package cli
2
-
3
-import (
4
- "fmt"
5
- "io"
6
- "os"
7
- "path"
8
- "path/filepath"
9
- "sort"
10
- "strings"
11
-
12
- cmds "github.com/ipfs/go-ipfs/commands"
13
- u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
14
- logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
15
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
16
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit/files"
17
- osh "gx/ipfs/QmXuBJ7DR6k3rmUEKtvVMhwjmXDuJgXXPUt4LQXKBMsU93/go-os-helper"
18
-)
19
-
20
-var log = logging.Logger("commands/cli")
21
-
22
-// Parse parses the input commandline string (cmd, flags, and args).
23
-// returns the corresponding command Request object.
24
-func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
25
- path, opts, stringVals, cmd, err := parseOpts(input, root)
26
- if err != nil {
27
- return nil, nil, path, err
28
- }
29
-
30
- optDefs, err := root.GetOptions(path)
31
- if err != nil {
32
- return nil, cmd, path, err
33
- }
34
-
35
- req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs)
36
- if err != nil {
37
- return nil, cmd, path, err
38
- }
39
-
40
- // This is an ugly hack to maintain our current CLI interface while fixing
41
- // other stdin usage bugs. Let this serve as a warning, be careful about the
42
- // choices you make, they will haunt you forever.
43
- if len(path) == 2 && path[0] == "bootstrap" {
44
- if (path[1] == "add" && opts["default"] == true) ||
45
- (path[1] == "rm" && opts["all"] == true) {
46
- stdin = nil
47
- }
48
- }
49
-
50
- stringArgs, fileArgs, err := ParseArgs(req, stringVals, stdin, cmd.Arguments, root)
51
- if err != nil {
52
- return req, cmd, path, err
53
- }
54
- req.SetArguments(stringArgs)
55
-
56
- if len(fileArgs) > 0 {
57
- file := files.NewSliceFile("", "", fileArgs)
58
- req.SetFiles(file)
59
- }
60
-
61
- err = cmd.CheckArguments(req)
62
-
63
- return req, cmd, path, err
64
-}
65
-
66
-func ParseArgs(req cmds.Request, inputs []string, stdin *os.File, argDefs []cmdkit.Argument, root *cmds.Command) ([]string, []files.File, error) {
67
- var err error
68
-
69
- // if -r is provided, and it is associated with the package builtin
70
- // recursive path option, allow recursive file paths
71
- recursiveOpt := req.Option(cmdkit.RecShort)
72
- recursive := false
73
- if recursiveOpt != nil && recursiveOpt.Definition() == cmdkit.OptionRecursivePath {
74
- recursive, _, err = recursiveOpt.Bool()
75
- if err != nil {
76
- return nil, nil, u.ErrCast()
77
- }
78
- }
79
-
80
- // if '--hidden' is provided, enumerate hidden paths
81
- hiddenOpt := req.Option("hidden")
82
- hidden := false
83
- if hiddenOpt != nil {
84
- hidden, _, err = hiddenOpt.Bool()
85
- if err != nil {
86
- return nil, nil, u.ErrCast()
87
- }
88
- }
89
- return parseArgs(inputs, stdin, argDefs, recursive, hidden, root)
90
-}
91
-
92
-// Parse a command line made up of sub-commands, short arguments, long arguments and positional arguments
93
-func parseOpts(args []string, root *cmds.Command) (
94
- path []string,
95
- opts map[string]interface{},
96
- stringVals []string,
97
- cmd *cmds.Command,
98
- err error,
99
-) {
100
- path = make([]string, 0, len(args))
101
- stringVals = make([]string, 0, len(args))
102
- optDefs := map[string]cmdkit.Option{}
103
- opts = map[string]interface{}{}
104
- cmd = root
105
-
106
- // parseFlag checks that a flag is valid and saves it into opts
107
- // Returns true if the optional second argument is used
108
- parseFlag := func(name string, arg *string, mustUse bool) (bool, error) {
109
- if _, ok := opts[name]; ok {
110
- return false, fmt.Errorf("Duplicate values for option '%s'", name)
111
- }
112
-
113
- optDef, found := optDefs[name]
114
- if !found {
115
- err = fmt.Errorf("Unrecognized option '%s'", name)
116
- return false, err
117
- }
118
- // mustUse implies that you must use the argument given after the '='
119
- // eg. -r=true means you must take true into consideration
120
- // mustUse == true in the above case
121
- // eg. ipfs -r <file> means disregard <file> since there is no '='
122
- // mustUse == false in the above situation
123
- //arg == nil implies the flag was specified without an argument
124
- if optDef.Type() == cmdkit.Bool {
125
- if arg == nil || !mustUse {
126
- opts[name] = true
127
- return false, nil
128
- }
129
- argVal := strings.ToLower(*arg)
130
- switch argVal {
131
- case "true":
132
- opts[name] = true
133
- return true, nil
134
- case "false":
135
- opts[name] = false
136
- return true, nil
137
- default:
138
- return true, fmt.Errorf("Option '%s' takes true/false arguments, but was passed '%s'", name, argVal)
139
- }
140
- } else {
141
- if arg == nil {
142
- return true, fmt.Errorf("Missing argument for option '%s'", name)
143
- }
144
- opts[name] = *arg
145
- return true, nil
146
- }
147
- }
148
-
149
- optDefs, err = root.GetOptions(path)
150
- if err != nil {
151
- return
152
- }
153
-
154
- consumed := false
155
- for i, arg := range args {
156
- switch {
157
- case consumed:
158
- // arg was already consumed by the preceding flag
159
- consumed = false
160
- continue
161
-
162
- case arg == "--":
163
- // treat all remaining arguments as positional arguments
164
- stringVals = append(stringVals, args[i+1:]...)
165
- return
166
-
167
- case strings.HasPrefix(arg, "--"):
168
- // arg is a long flag, with an optional argument specified
169
- // using `=' or in args[i+1]
170
- var slurped bool
171
- var next *string
172
- split := strings.SplitN(arg, "=", 2)
173
- if len(split) == 2 {
174
- slurped = false
175
- arg = split[0]
176
- next = &split[1]
177
- } else {
178
- slurped = true
179
- if i+1 < len(args) {
180
- next = &args[i+1]
181
- } else {
182
- next = nil
183
- }
184
- }
185
- consumed, err = parseFlag(arg[2:], next, len(split) == 2)
186
- if err != nil {
187
- return
188
- }
189
- if !slurped {
190
- consumed = false
191
- }
192
-
193
- case strings.HasPrefix(arg, "-") && arg != "-":
194
- // args is one or more flags in short form, followed by an optional argument
195
- // all flags except the last one have type bool
196
- for arg = arg[1:]; len(arg) != 0; arg = arg[1:] {
197
- var rest *string
198
- var slurped bool
199
- mustUse := false
200
- if len(arg) > 1 {
201
- slurped = false
202
- str := arg[1:]
203
- if len(str) > 0 && str[0] == '=' {
204
- str = str[1:]
205
- mustUse = true
206
- }
207
- rest = &str
208
- } else {
209
- slurped = true
210
- if i+1 < len(args) {
211
- rest = &args[i+1]
212
- } else {
213
- rest = nil
214
- }
215
- }
216
- var end bool
217
- end, err = parseFlag(arg[:1], rest, mustUse)
218
- if err != nil {
219
- return
220
- }
221
- if end {
222
- consumed = slurped
223
- break
224
- }
225
- }
226
-
227
- default:
228
- // arg is a sub-command or a positional argument
229
- sub := cmd.Subcommand(arg)
230
- if sub != nil {
231
- cmd = sub
232
- path = append(path, arg)
233
- optDefs, err = root.GetOptions(path)
234
- if err != nil {
235
- return
236
- }
237
-
238
- // If we've come across an external binary call, pass all the remaining
239
- // arguments on to it
240
- if cmd.External {
241
- stringVals = append(stringVals, args[i+1:]...)
242
- return
243
- }
244
- } else {
245
- stringVals = append(stringVals, arg)
246
- if len(path) == 0 {
247
- // found a typo or early argument
248
- err = printSuggestions(stringVals, root)
249
- return
250
- }
251
- }
252
- }
253
- }
254
- return
255
-}
256
-
257
-const msgStdinInfo = "ipfs: Reading from %s; send Ctrl-d to stop."
258
-
259
-func parseArgs(inputs []string, stdin *os.File, argDefs []cmdkit.Argument, recursive, hidden bool, root *cmds.Command) ([]string, []files.File, error) {
260
- // ignore stdin on Windows
261
- if osh.IsWindows() {
262
- stdin = nil
263
- }
264
-
265
- // count required argument definitions
266
- numRequired := 0
267
- for _, argDef := range argDefs {
268
- if argDef.Required {
269
- numRequired++
270
- }
271
- }
272
-
273
- // count number of values provided by user.
274
- // if there is at least one ArgDef, we can safely trigger the inputs loop
275
- // below to parse stdin.
276
- numInputs := len(inputs)
277
- if len(argDefs) > 0 && argDefs[len(argDefs)-1].SupportsStdin && stdin != nil {
278
- numInputs++
279
- }
280
-
281
- // if we have more arg values provided than argument definitions,
282
- // and the last arg definition is not variadic (or there are no definitions), return an error
283
- notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
284
- if notVariadic && len(inputs) > len(argDefs) {
285
- err := printSuggestions(inputs, root)
286
- return nil, nil, err
287
- }
288
-
289
- stringArgs := make([]string, 0, numInputs)
290
-
291
- fileArgs := make(map[string]files.File)
292
- argDefIndex := 0 // the index of the current argument definition
293
-
294
- for i := 0; i < numInputs; i++ {
295
- argDef := getArgDef(argDefIndex, argDefs)
296
-
297
- // skip optional argument definitions if there aren't sufficient remaining inputs
298
- for numInputs-i <= numRequired && !argDef.Required {
299
- argDefIndex++
300
- argDef = getArgDef(argDefIndex, argDefs)
301
- }
302
- if argDef.Required {
303
- numRequired--
304
- }
305
-
306
- fillingVariadic := argDefIndex+1 > len(argDefs)
307
- switch argDef.Type {
308
- case cmdkit.ArgString:
309
- if len(inputs) > 0 {
310
- stringArgs, inputs = append(stringArgs, inputs[0]), inputs[1:]
311
- } else if stdin != nil && argDef.SupportsStdin && !fillingVariadic {
312
- if r, err := maybeWrapStdin(stdin, msgStdinInfo); err == nil {
313
- fileArgs[stdin.Name()] = files.NewReaderFile("stdin", "", r, nil)
314
- stdin = nil
315
- }
316
- }
317
- case cmdkit.ArgFile:
318
- if len(inputs) > 0 {
319
- // treat stringArg values as file paths
320
- fpath := inputs[0]
321
- inputs = inputs[1:]
322
- var file files.File
323
- if fpath == "-" {
324
- r, err := maybeWrapStdin(stdin, msgStdinInfo)
325
- if err != nil {
326
- return nil, nil, err
327
- }
328
-
329
- fpath = stdin.Name()
330
- file = files.NewReaderFile("", fpath, r, nil)
331
- } else {
332
- nf, err := appendFile(fpath, argDef, recursive, hidden)
333
- if err != nil {
334
- return nil, nil, err
335
- }
336
-
337
- file = nf
338
- }
339
-
340
- fileArgs[fpath] = file
341
- } else if stdin != nil && argDef.SupportsStdin &&
342
- argDef.Required && !fillingVariadic {
343
- r, err := maybeWrapStdin(stdin, msgStdinInfo)
344
- if err != nil {
345
- return nil, nil, err
346
- }
347
-
348
- fpath := stdin.Name()
349
- fileArgs[fpath] = files.NewReaderFile("", fpath, r, nil)
350
- }
351
- }
352
-
353
- argDefIndex++
354
- }
355
-
356
- // check to make sure we didn't miss any required arguments
357
- if len(argDefs) > argDefIndex {
358
- for _, argDef := range argDefs[argDefIndex:] {
359
- if argDef.Required {
360
- return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name)
361
- }
362
- }
363
- }
364
-
365
- return stringArgs, filesMapToSortedArr(fileArgs), nil
366
-}
367
-
368
-func filesMapToSortedArr(fs map[string]files.File) []files.File {
369
- var names []string
370
- for name := range fs {
371
- names = append(names, name)
372
- }
373
-
374
- sort.Strings(names)
375
-
376
- var out []files.File
377
- for _, f := range names {
378
- out = append(out, fs[f])
379
- }
380
-
381
- return out
382
-}
383
-
384
-func getArgDef(i int, argDefs []cmdkit.Argument) *cmdkit.Argument {
385
- if i < len(argDefs) {
386
- // get the argument definition (usually just argDefs[i])
387
- return &argDefs[i]
388
-
389
- } else if len(argDefs) > 0 {
390
- // but if i > len(argDefs) we use the last argument definition)
391
- return &argDefs[len(argDefs)-1]
392
- }
393
-
394
- // only happens if there aren't any definitions
395
- return nil
396
-}
397
-
398
-const notRecursiveFmtStr = "'%s' is a directory, use the '-%s' flag to specify directories"
399
-const dirNotSupportedFmtStr = "Invalid path '%s', argument '%s' does not support directories"
400
-const winDriveLetterFmtStr = "%q is a drive letter, not a drive path"
401
-
402
-func appendFile(fpath string, argDef *cmdkit.Argument, recursive, hidden bool) (files.File, error) {
403
- // resolve Windows relative dot paths like `X:.\somepath`
404
- if osh.IsWindows() {
405
- if len(fpath) >= 3 && fpath[1:3] == ":." {
406
- var err error
407
- fpath, err = filepath.Abs(fpath)
408
- if err != nil {
409
- return nil, err
410
- }
411
- }
412
- }
413
-
414
- if fpath == "." {
415
- cwd, err := os.Getwd()
416
- if err != nil {
417
- return nil, err
418
- }
419
- cwd, err = filepath.EvalSymlinks(cwd)
420
- if err != nil {
421
- return nil, err
422
- }
423
- fpath = cwd
424
- }
425
-
426
- fpath = filepath.Clean(fpath)
427
-
428
- stat, err := os.Lstat(fpath)
429
- if err != nil {
430
- return nil, err
431
- }
432
-
433
- if stat.IsDir() {
434
- if !argDef.Recursive {
435
- return nil, fmt.Errorf(dirNotSupportedFmtStr, fpath, argDef.Name)
436
- }
437
- if !recursive {
438
- return nil, fmt.Errorf(notRecursiveFmtStr, fpath, cmdkit.RecShort)
439
- }
440
- }
441
-
442
- if osh.IsWindows() {
443
- return windowsParseFile(fpath, hidden, stat)
444
- }
445
-
446
- return files.NewSerialFile(path.Base(fpath), fpath, hidden, stat)
447
-}
448
-
449
-// Inform the user if a file is waiting on input
450
-func maybeWrapStdin(f *os.File, msg string) (io.ReadCloser, error) {
451
- isTty, err := isTty(f)
452
- if err != nil {
453
- return nil, err
454
- }
455
-
456
- if isTty {
457
- return newMessageReader(f, fmt.Sprintf(msg, f.Name())), nil
458
- }
459
-
460
- return f, nil
461
-}
462
-
463
-func isTty(f *os.File) (bool, error) {
464
- fInfo, err := f.Stat()
465
- if err != nil {
466
- log.Error(err)
467
- return false, err
468
- }
469
-
470
- return (fInfo.Mode() & os.ModeCharDevice) != 0, nil
471
-}
472
-
473
-type messageReader struct {
474
- r io.ReadCloser
475
- done bool
476
- message string
477
-}
478
-
479
-func newMessageReader(r io.ReadCloser, msg string) io.ReadCloser {
480
- return &messageReader{
481
- r: r,
482
- message: msg,
483
- }
484
-}
485
-
486
-func (r *messageReader) Read(b []byte) (int, error) {
487
- if !r.done {
488
- fmt.Fprintln(os.Stderr, r.message)
489
- r.done = true
490
- }
491
-
492
- return r.r.Read(b)
493
-}
494
-
495
-func (r *messageReader) Close() error {
496
- return r.r.Close()
497
-}
498
-
499
-func windowsParseFile(fpath string, hidden bool, stat os.FileInfo) (files.File, error) {
500
- // special cases for Windows drive roots i.e. `X:\` and their long form `\\?\X:\`
501
- // drive path must be preserved as `X:\` (or it's longform) and not converted to `X:`, `X:.`, `\`, or `/` here
502
- switch len(fpath) {
503
- case 3:
504
- // `X:` is cleaned to `X:.` which may not be the expected behaviour by the user, they'll need to provide more specific input
505
- if fpath[1:3] == ":." {
506
- return nil, fmt.Errorf(winDriveLetterFmtStr, fpath[:2])
507
- }
508
- // `X:\` needs to preserve the `\`, path.Base(filepath.ToSlash(fpath)) results in `X:` which is not valid
509
- if fpath[1:3] == ":\\" {
510
- return files.NewSerialFile(fpath, fpath, hidden, stat)
511
- }
512
- case 6:
513
- // `\\?\X:` long prefix form of `X:`, still ambiguous
514
- if fpath[:4] == "\\\\?\\" && fpath[5] == ':' {
515
- return nil, fmt.Errorf(winDriveLetterFmtStr, fpath)
516
- }
517
- case 7:
518
- // `\\?\X:\` long prefix form is translated into short form `X:\`
519
- if fpath[:4] == "\\\\?\\" && fpath[5] == ':' && fpath[6] == '\\' {
520
- fpath = string(fpath[4]) + ":\\"
521
- return files.NewSerialFile(fpath, fpath, hidden, stat)
522
- }
523
- }
524
-
525
- return files.NewSerialFile(path.Base(filepath.ToSlash(fpath)), fpath, hidden, stat)
526
-}
commands/cli/parse_test.go
deleted
-313
@@ -1,313 +0,0 @@
1
-package cli
2
-
3
-import (
4
- "io"
5
- "io/ioutil"
6
- "os"
7
- "runtime"
8
- "strings"
9
- "testing"
10
-
11
- "github.com/ipfs/go-ipfs/commands"
12
-
13
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
14
-)
15
-
16
-type kvs map[string]interface{}
17
-type words []string
18
-
19
-func sameWords(a words, b words) bool {
20
- if len(a) != len(b) {
21
- return false
22
- }
23
- for i, w := range a {
24
- if w != b[i] {
25
- return false
26
- }
27
- }
28
- return true
29
-}
30
-
31
-func sameKVs(a kvs, b kvs) bool {
32
- if len(a) != len(b) {
33
- return false
34
- }
35
- for k, v := range a {
36
- if v != b[k] {
37
- return false
38
- }
39
- }
40
- return true
41
-}
42
-
43
-func TestSameWords(t *testing.T) {
44
- a := []string{"v1", "v2"}
45
- b := []string{"v1", "v2", "v3"}
46
- c := []string{"v2", "v3"}
47
- d := []string{"v2"}
48
- e := []string{"v2", "v3"}
49
- f := []string{"v2", "v1"}
50
-
51
- test := func(a words, b words, v bool) {
52
- if sameWords(a, b) != v {
53
- t.Errorf("sameWords('%v', '%v') != %v", a, b, v)
54
- }
55
- }
56
-
57
- test(a, b, false)
58
- test(a, a, true)
59
- test(a, c, false)
60
- test(b, c, false)
61
- test(c, d, false)
62
- test(c, e, true)
63
- test(b, e, false)
64
- test(a, b, false)
65
- test(a, f, false)
66
- test(e, f, false)
67
- test(f, f, true)
68
-}
69
-
70
-func TestOptionParsing(t *testing.T) {
71
- subCmd := &commands.Command{}
72
- cmd := &commands.Command{
73
- Options: []cmdkit.Option{
74
- cmdkit.StringOption("string", "s", "a string"),
75
- cmdkit.BoolOption("bool", "b", "a bool"),
76
- },
77
- Subcommands: map[string]*commands.Command{
78
- "test": subCmd,
79
- },
80
- }
81
-
82
- testHelper := func(args string, expectedOpts kvs, expectedWords words, expectErr bool) {
83
- var opts map[string]interface{}
84
- var input []string
85
-
86
- _, opts, input, _, err := parseOpts(strings.Split(args, " "), cmd)
87
- if expectErr {
88
- if err == nil {
89
- t.Errorf("Command line '%v' parsing should have failed", args)
90
- }
91
- } else if err != nil {
92
- t.Errorf("Command line '%v' failed to parse: %v", args, err)
93
- } else if !sameWords(input, expectedWords) || !sameKVs(opts, expectedOpts) {
94
- t.Errorf("Command line '%v':\n parsed as %v %v\n instead of %v %v",
95
- args, opts, input, expectedOpts, expectedWords)
96
- }
97
- }
98
-
99
- testFail := func(args string) {
100
- testHelper(args, kvs{}, words{}, true)
101
- }
102
-
103
- test := func(args string, expectedOpts kvs, expectedWords words) {
104
- testHelper(args, expectedOpts, expectedWords, false)
105
- }
106
-
107
- test("test -", kvs{}, words{"-"})
108
- testFail("-b -b")
109
- test("test beep boop", kvs{}, words{"beep", "boop"})
110
- testFail("-s")
111
- test("-s foo", kvs{"s": "foo"}, words{})
112
- test("-sfoo", kvs{"s": "foo"}, words{})
113
- test("-s=foo", kvs{"s": "foo"}, words{})
114
- test("-b", kvs{"b": true}, words{})
115
- test("-bs foo", kvs{"b": true, "s": "foo"}, words{})
116
- test("-sb", kvs{"s": "b"}, words{})
117
- test("-b test foo", kvs{"b": true}, words{"foo"})
118
- test("--bool test foo", kvs{"bool": true}, words{"foo"})
119
- testFail("--bool=foo")
120
- testFail("--string")
121
- test("--string foo", kvs{"string": "foo"}, words{})
122
- test("--string=foo", kvs{"string": "foo"}, words{})
123
- test("-- -b", kvs{}, words{"-b"})
124
- test("test foo -b", kvs{"b": true}, words{"foo"})
125
- test("-b=false", kvs{"b": false}, words{})
126
- test("-b=true", kvs{"b": true}, words{})
127
- test("-b=false test foo", kvs{"b": false}, words{"foo"})
128
- test("-b=true test foo", kvs{"b": true}, words{"foo"})
129
- test("--bool=true test foo", kvs{"bool": true}, words{"foo"})
130
- test("--bool=false test foo", kvs{"bool": false}, words{"foo"})
131
- test("-b test true", kvs{"b": true}, words{"true"})
132
- test("-b test false", kvs{"b": true}, words{"false"})
133
- test("-b=FaLsE test foo", kvs{"b": false}, words{"foo"})
134
- test("-b=TrUe test foo", kvs{"b": true}, words{"foo"})
135
- test("-b test true", kvs{"b": true}, words{"true"})
136
- test("-b test false", kvs{"b": true}, words{"false"})
137
- test("-b --string foo test bar", kvs{"b": true, "string": "foo"}, words{"bar"})
138
- test("-b=false --string bar", kvs{"b": false, "string": "bar"}, words{})
139
- testFail("foo test")
140
-}
141
-
142
-func TestArgumentParsing(t *testing.T) {
143
- if runtime.GOOS == "windows" {
144
- t.Skip("stdin handling doesnt yet work on windows")
145
- }
146
- rootCmd := &commands.Command{
147
- Subcommands: map[string]*commands.Command{
148
- "noarg": {},
149
- "onearg": {
150
- Arguments: []cmdkit.Argument{
151
- cmdkit.StringArg("a", true, false, "some arg"),
152
- },
153
- },
154
- "twoargs": {
155
- Arguments: []cmdkit.Argument{
156
- cmdkit.StringArg("a", true, false, "some arg"),
157
- cmdkit.StringArg("b", true, false, "another arg"),
158
- },
159
- },
160
- "variadic": {
161
- Arguments: []cmdkit.Argument{
162
- cmdkit.StringArg("a", true, true, "some arg"),
163
- },
164
- },
165
- "optional": {
166
- Arguments: []cmdkit.Argument{
167
- cmdkit.StringArg("b", false, true, "another arg"),
168
- },
169
- },
170
- "optionalsecond": {
171
- Arguments: []cmdkit.Argument{
172
- cmdkit.StringArg("a", true, false, "some arg"),
173
- cmdkit.StringArg("b", false, false, "another arg"),
174
- },
175
- },
176
- "reversedoptional": {
177
- Arguments: []cmdkit.Argument{
178
- cmdkit.StringArg("a", false, false, "some arg"),
179
- cmdkit.StringArg("b", true, false, "another arg"),
180
- },
181
- },
182
- "stdinenabled": {
183
- Arguments: []cmdkit.Argument{
184
- cmdkit.StringArg("a", true, true, "some arg").EnableStdin(),
185
- },
186
- },
187
- "stdinenabled2args": &commands.Command{
188
- Arguments: []cmdkit.Argument{
189
- cmdkit.StringArg("a", true, false, "some arg"),
190
- cmdkit.StringArg("b", true, true, "another arg").EnableStdin(),
191
- },
192
- },
193
- "stdinenablednotvariadic": &commands.Command{
194
- Arguments: []cmdkit.Argument{
195
- cmdkit.StringArg("a", true, false, "some arg").EnableStdin(),
196
- },
197
- },
198
- "stdinenablednotvariadic2args": &commands.Command{
199
- Arguments: []cmdkit.Argument{
200
- cmdkit.StringArg("a", true, false, "some arg"),
201
- cmdkit.StringArg("b", true, false, "another arg").EnableStdin(),
202
- },
203
- },
204
- },
205
- }
206
-
207
- test := func(cmd words, f *os.File, res words) {
208
- if f != nil {
209
- if _, err := f.Seek(0, io.SeekStart); err != nil {
210
- t.Fatal(err)
211
- }
212
- }
213
- req, _, _, err := Parse(cmd, f, rootCmd)
214
- if err != nil {
215
- t.Errorf("Command '%v' should have passed parsing: %v", cmd, err)
216
- }
217
- if !sameWords(req.Arguments(), res) {
218
- t.Errorf("Arguments parsed from '%v' are '%v' instead of '%v'", cmd, req.Arguments(), res)
219
- }
220
- }
221
-
222
- testFail := func(cmd words, fi *os.File, msg string) {
223
- _, _, _, err := Parse(cmd, nil, rootCmd)
224
- if err == nil {
225
- t.Errorf("Should have failed: %v", msg)
226
- }
227
- }
228
-
229
- test([]string{"noarg"}, nil, []string{})
230
- testFail([]string{"noarg", "value!"}, nil, "provided an arg, but command didn't define any")
231
-
232
- test([]string{"onearg", "value!"}, nil, []string{"value!"})
233
- testFail([]string{"onearg"}, nil, "didn't provide any args, arg is required")
234
-
235
- test([]string{"twoargs", "value1", "value2"}, nil, []string{"value1", "value2"})
236
- testFail([]string{"twoargs", "value!"}, nil, "only provided 1 arg, needs 2")
237
- testFail([]string{"twoargs"}, nil, "didn't provide any args, 2 required")
238
-
239
- test([]string{"variadic", "value!"}, nil, []string{"value!"})
240
- test([]string{"variadic", "value1", "value2", "value3"}, nil, []string{"value1", "value2", "value3"})
241
- testFail([]string{"variadic"}, nil, "didn't provide any args, 1 required")
242
-
243
- test([]string{"optional", "value!"}, nil, []string{"value!"})
244
- test([]string{"optional"}, nil, []string{})
245
- test([]string{"optional", "value1", "value2"}, nil, []string{"value1", "value2"})
246
-
247
- test([]string{"optionalsecond", "value!"}, nil, []string{"value!"})
248
- test([]string{"optionalsecond", "value1", "value2"}, nil, []string{"value1", "value2"})
249
- testFail([]string{"optionalsecond"}, nil, "didn't provide any args, 1 required")
250
- testFail([]string{"optionalsecond", "value1", "value2", "value3"}, nil, "provided too many args, takes 2 maximum")
251
-
252
- test([]string{"reversedoptional", "value1", "value2"}, nil, []string{"value1", "value2"})
253
- test([]string{"reversedoptional", "value!"}, nil, []string{"value!"})
254
-
255
- testFail([]string{"reversedoptional"}, nil, "didn't provide any args, 1 required")
256
- testFail([]string{"reversedoptional", "value1", "value2", "value3"}, nil, "provided too many args, only takes 1")
257
-
258
- // Use a temp file to simulate stdin
259
- fileToSimulateStdin := func(t *testing.T, content string) *os.File {
260
- fstdin, err := ioutil.TempFile("", "")
261
- if err != nil {
262
- t.Fatal(err)
263
- }
264
- defer os.Remove(fstdin.Name())
265
-
266
- if _, err := io.WriteString(fstdin, content); err != nil {
267
- t.Fatal(err)
268
- }
269
- return fstdin
270
- }
271
-
272
- test([]string{"stdinenabled", "value1", "value2"}, nil, []string{"value1", "value2"})
273
-
274
- fstdin := fileToSimulateStdin(t, "stdin1")
275
- test([]string{"stdinenabled"}, fstdin, []string{"stdin1"})
276
- test([]string{"stdinenabled", "value1"}, fstdin, []string{"value1"})
277
- test([]string{"stdinenabled", "value1", "value2"}, fstdin, []string{"value1", "value2"})
278
-
279
- fstdin = fileToSimulateStdin(t, "stdin1\nstdin2")
280
- test([]string{"stdinenabled"}, fstdin, []string{"stdin1", "stdin2"})
281
-
282
- fstdin = fileToSimulateStdin(t, "stdin1\nstdin2\nstdin3")
283
- test([]string{"stdinenabled"}, fstdin, []string{"stdin1", "stdin2", "stdin3"})
284
-
285
- test([]string{"stdinenabled2args", "value1", "value2"}, nil, []string{"value1", "value2"})
286
-
287
- fstdin = fileToSimulateStdin(t, "stdin1")
288
- test([]string{"stdinenabled2args", "value1"}, fstdin, []string{"value1", "stdin1"})
289
- test([]string{"stdinenabled2args", "value1", "value2"}, fstdin, []string{"value1", "value2"})
290
- test([]string{"stdinenabled2args", "value1", "value2", "value3"}, fstdin, []string{"value1", "value2", "value3"})
291
-
292
- fstdin = fileToSimulateStdin(t, "stdin1\nstdin2")
293
- test([]string{"stdinenabled2args", "value1"}, fstdin, []string{"value1", "stdin1", "stdin2"})
294
-
295
- test([]string{"stdinenablednotvariadic", "value1"}, nil, []string{"value1"})
296
-
297
- fstdin = fileToSimulateStdin(t, "stdin1")
298
- test([]string{"stdinenablednotvariadic"}, fstdin, []string{"stdin1"})
299
- test([]string{"stdinenablednotvariadic", "value1"}, fstdin, []string{"value1"})
300
-
301
- test([]string{"stdinenablednotvariadic2args", "value1", "value2"}, nil, []string{"value1", "value2"})
302
-
303
- fstdin = fileToSimulateStdin(t, "stdin1")
304
- test([]string{"stdinenablednotvariadic2args", "value1"}, fstdin, []string{"value1", "stdin1"})
305
- test([]string{"stdinenablednotvariadic2args", "value1", "value2"}, fstdin, []string{"value1", "value2"})
306
- testFail([]string{"stdinenablednotvariadic2args"}, fstdin, "cant use stdin for non stdin arg")
307
-
308
- fstdin = fileToSimulateStdin(t, "stdin1")
309
- test([]string{"noarg"}, fstdin, []string{})
310
-
311
- fstdin = fileToSimulateStdin(t, "stdin1")
312
- test([]string{"optionalsecond", "value1", "value2"}, fstdin, []string{"value1", "value2"})
313
-}
commands/http/client.go
deleted
-310
@@ -1,310 +0,0 @@
1
-package http
2
-
3
-import (
4
- "context"
5
- "encoding/json"
6
- "errors"
7
- "fmt"
8
- "io"
9
- "io/ioutil"
10
- "net/http"
11
- "net/url"
12
- "reflect"
13
- "strconv"
14
- "strings"
15
-
16
- cmds "github.com/ipfs/go-ipfs/commands"
17
- config "github.com/ipfs/go-ipfs/repo/config"
18
-
19
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
20
-)
21
-
22
-const (
23
- ApiUrlFormat = "http://%s%s/%s?%s"
24
- ApiPath = "/api/v0" // TODO: make configurable
25
-)
26
-
27
-var OptionSkipMap = map[string]bool{
28
- "api": true,
29
-}
30
-
31
-// Client is the commands HTTP client interface.
32
-type Client interface {
33
- Send(req cmds.Request) (cmds.Response, error)
34
-}
35
-
36
-type client struct {
37
- serverAddress string
38
- httpClient *http.Client
39
-}
40
-
41
-func NewClient(address string) Client {
42
- return &client{
43
- serverAddress: address,
44
- httpClient: http.DefaultClient,
45
- }
46
-}
47
-
48
-func (c *client) Send(req cmds.Request) (cmds.Response, error) {
49
-
50
- if req.Context() == nil {
51
- log.Warningf("no context set in request")
52
- if err := req.SetRootContext(context.TODO()); err != nil {
53
- return nil, err
54
- }
55
- }
56
-
57
- // save user-provided encoding
58
- previousUserProvidedEncoding, found, err := req.Option(cmdkit.EncShort).String()
59
- if err != nil {
60
- return nil, err
61
- }
62
-
63
- // override with json to send to server
64
- req.SetOption(cmdkit.EncShort, cmds.JSON)
65
-
66
- // stream channel output
67
- req.SetOption(cmdkit.ChanOpt, "true")
68
-
69
- query, err := getQuery(req)
70
- if err != nil {
71
- return nil, err
72
- }
73
-
74
- var fileReader *MultiFileReader
75
- var reader io.Reader
76
-
77
- if req.Files() != nil {
78
- fileReader = NewMultiFileReader(req.Files(), true)
79
- reader = fileReader
80
- }
81
-
82
- path := strings.Join(req.Path(), "/")
83
- url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query)
84
-
85
- httpReq, err := http.NewRequest("POST", url, reader)
86
- if err != nil {
87
- return nil, err
88
- }
89
-
90
- // TODO extract string consts?
91
- if fileReader != nil {
92
- httpReq.Header.Set(contentTypeHeader, "multipart/form-data; boundary="+fileReader.Boundary())
93
- } else {
94
- httpReq.Header.Set(contentTypeHeader, applicationOctetStream)
95
- }
96
- httpReq.Header.Set(uaHeader, config.ApiVersion)
97
-
98
- httpReq.Cancel = req.Context().Done()
99
- httpReq.Close = true
100
-
101
- httpRes, err := c.httpClient.Do(httpReq)
102
- if err != nil {
103
- return nil, err
104
- }
105
-
106
- // using the overridden JSON encoding in request
107
- res, err := getResponse(httpRes, req)
108
- if err != nil {
109
- return nil, err
110
- }
111
-
112
- if found && len(previousUserProvidedEncoding) > 0 {
113
- // reset to user provided encoding after sending request
114
- // NB: if user has provided an encoding but it is the empty string,
115
- // still leave it as JSON.
116
- req.SetOption(cmdkit.EncShort, previousUserProvidedEncoding)
117
- }
118
-
119
- return res, nil
120
-}
121
-
122
-func getQuery(req cmds.Request) (string, error) {
123
- query := url.Values{}
124
- for k, v := range req.Options() {
125
- if OptionSkipMap[k] {
126
- continue
127
- }
128
- str := fmt.Sprintf("%v", v)
129
- query.Set(k, str)
130
- }
131
-
132
- args := req.StringArguments()
133
- argDefs := req.Command().Arguments
134
-
135
- argDefIndex := 0
136
-
137
- for _, arg := range args {
138
- argDef := argDefs[argDefIndex]
139
- // skip ArgFiles
140
- for argDef.Type == cmdkit.ArgFile {
141
- argDefIndex++
142
- argDef = argDefs[argDefIndex]
143
- }
144
-
145
- query.Add("arg", arg)
146
-
147
- if len(argDefs) > argDefIndex+1 {
148
- argDefIndex++
149
- }
150
- }
151
-
152
- return query.Encode(), nil
153
-}
154
-
155
-// getResponse decodes a http.Response to create a cmds.Response
156
-func getResponse(httpRes *http.Response, req cmds.Request) (cmds.Response, error) {
157
- var err error
158
- res := cmds.NewResponse(req)
159
-
160
- contentType := httpRes.Header.Get(contentTypeHeader)
161
- contentType = strings.Split(contentType, ";")[0]
162
-
163
- lengthHeader := httpRes.Header.Get(extraContentLengthHeader)
164
- if len(lengthHeader) > 0 {
165
- length, err := strconv.ParseUint(lengthHeader, 10, 64)
166
- if err != nil {
167
- return nil, err
168
- }
169
- res.SetLength(length)
170
- }
171
-
172
- rr := &httpResponseReader{httpRes}
173
- res.SetCloser(rr)
174
-
175
- if contentType != applicationJson {
176
- // for all non json output types, just stream back the output
177
- res.SetOutput(rr)
178
- return res, nil
179
-
180
- } else if len(httpRes.Header.Get(channelHeader)) > 0 {
181
- // if output is coming from a channel, decode each chunk
182
- outChan := make(chan interface{})
183
-
184
- go readStreamedJson(req, rr, outChan, res)
185
-
186
- res.SetOutput((<-chan interface{})(outChan))
187
- return res, nil
188
- }
189
-
190
- dec := json.NewDecoder(rr)
191
-
192
- // If we ran into an error
193
- if httpRes.StatusCode >= http.StatusBadRequest {
194
- var e *cmdkit.Error
195
-
196
- switch {
197
- case httpRes.StatusCode == http.StatusNotFound:
198
- // handle 404s
199
- e = &cmdkit.Error{Message: "Command not found.", Code: cmdkit.ErrClient}
200
-
201
- case contentType == plainText:
202
- // handle non-marshalled errors
203
- mes, err := ioutil.ReadAll(rr)
204
- if err != nil {
205
- return nil, err
206
- }
207
-
208
- e = &cmdkit.Error{Message: string(mes), Code: cmdkit.ErrNormal}
209
- default:
210
- // handle marshalled errors
211
- var rxErr cmdkit.Error
212
- err = dec.Decode(&rxErr)
213
- if err != nil {
214
- return nil, err
215
- }
216
- e = &rxErr
217
- }
218
-
219
- res.SetError(e, e.Code)
220
-
221
- return res, nil
222
- }
223
-
224
- outputType := reflect.TypeOf(req.Command().Type)
225
- v, err := decodeTypedVal(outputType, dec)
226
- if err != nil && err != io.EOF {
227
- return nil, err
228
- }
229
-
230
- res.SetOutput(v)
231
-
232
- return res, nil
233
-}
234
-
235
-// read json objects off of the given stream, and write the objects out to
236
-// the 'out' channel
237
-func readStreamedJson(req cmds.Request, rr io.Reader, out chan<- interface{}, resp cmds.Response) {
238
- defer close(out)
239
- dec := json.NewDecoder(rr)
240
- outputType := reflect.TypeOf(req.Command().Type)
241
-
242
- ctx := req.Context()
243
-
244
- for {
245
- v, err := decodeTypedVal(outputType, dec)
246
- if err != nil {
247
- if err != io.EOF {
248
- log.Error(err)
249
- resp.SetError(err, cmdkit.ErrNormal)
250
- }
251
- return
252
- }
253
-
254
- select {
255
- case <-ctx.Done():
256
- return
257
- case out <- v:
258
- }
259
- }
260
-}
261
-
262
-// decode a value of the given type, if the type is nil, attempt to decode into
263
-// an interface{} anyways
264
-func decodeTypedVal(t reflect.Type, dec *json.Decoder) (interface{}, error) {
265
- var v interface{}
266
- var err error
267
- if t != nil {
268
- v = reflect.New(t).Interface()
269
- err = dec.Decode(v)
270
- } else {
271
- err = dec.Decode(&v)
272
- }
273
-
274
- return v, err
275
-}
276
-
277
-// httpResponseReader reads from the response body, and checks for an error
278
-// in the http trailer upon EOF, this error if present is returned instead
279
-// of the EOF.
280
-type httpResponseReader struct {
281
- resp *http.Response
282
-}
283
-
284
-func (r *httpResponseReader) Read(b []byte) (int, error) {
285
- n, err := r.resp.Body.Read(b)
286
-
287
- // reading on a closed response body is as good as an io.EOF here
288
- if err != nil && strings.Contains(err.Error(), "read on closed response body") {
289
- err = io.EOF
290
- }
291
- if err == io.EOF {
292
- _ = r.resp.Body.Close()
293
- trailerErr := r.checkError()
294
- if trailerErr != nil {
295
- return n, trailerErr
296
- }
297
- }
298
- return n, err
299
-}
300
-
301
-func (r *httpResponseReader) checkError() error {
302
- if e := r.resp.Trailer.Get(StreamErrHeader); e != "" {
303
- return errors.New(e)
304
- }
305
- return nil
306
-}
307
-
308
-func (r *httpResponseReader) Close() error {
309
- return r.resp.Body.Close()
310
-}
commands/http/handler.go
deleted
-466
@@ -1,466 +0,0 @@
1
-package http
2
-
3
-import (
4
- "context"
5
- "errors"
6
- "fmt"
7
- "io"
8
- "net/http"
9
- "net/url"
10
- "runtime/debug"
11
- "strconv"
12
- "strings"
13
- "sync"
14
-
15
- cmds "github.com/ipfs/go-ipfs/commands"
16
- "github.com/ipfs/go-ipfs/repo/config"
17
-
18
- cors "gx/ipfs/QmPG2kW5t27LuHgHnvhUwbHCNHAt2eUcb4gPHqofrESUdB/cors"
19
- logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
20
- loggables "gx/ipfs/QmT4PgCNdv73hnFAqzHqwW44q7M9PWpykSswHDxndquZbc/go-libp2p-loggables"
21
- cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
22
-)
23
-
24
-var log = logging.Logger("commands/http")
25
-
26
-// the internal handler for the API
27
-type internalHandler struct {
28
- ctx cmds.Context
29
- root *cmds.Command
30
- cfg *ServerConfig
31
-}
32
-
33
-// The Handler struct is funny because we want to wrap our internal handler
34
-// with CORS while keeping our fields.
35
-type Handler struct {
36
- internalHandler
37
- corsHandler http.Handler
38
-}
39
-
40
-var (
41
- ErrNotFound = errors.New("404 page not found")
42
- errApiVersionMismatch = errors.New("api version mismatch")
43
-)
44
-
45
-const (
46
- StreamErrHeader = "X-Stream-Error"
47
- streamHeader = "X-Stream-Output"
48
- channelHeader = "X-Chunked-Output"
49
- extraContentLengthHeader = "X-Content-Length"
50
- uaHeader = "User-Agent"
51
- contentTypeHeader = "Content-Type"
52
- applicationJson = "application/json"
53
- applicationOctetStream = "application/octet-stream"
54
- plainText = "text/plain"
55
-)
56
-
57
-var AllowedExposedHeadersArr = []string{streamHeader, channelHeader, extraContentLengthHeader}
58
-var AllowedExposedHeaders = strings.Join(AllowedExposedHeadersArr, ", ")
59
-
60
-const (
61
- ACAOrigin = "Access-Control-Allow-Origin"
62
- ACAMethods = "Access-Control-Allow-Methods"
63
- ACACredentials = "Access-Control-Allow-Credentials"
64
-)
65
-
66
-var mimeTypes = map[string]string{
67
- cmds.Protobuf: "application/protobuf",
68
- cmds.JSON: "application/json",
69
- cmds.XML: "application/xml",
70
- cmds.Text: "text/plain",
71
-}
72
-
73
-type ServerConfig struct {
74
- // Headers is an optional map of headers that is written out.
75
- Headers map[string][]string
76
-
77
- // cORSOpts is a set of options for CORS headers.
78
- cORSOpts *cors.Options
79
-
80
- // cORSOptsRWMutex is a RWMutex for read/write CORSOpts
81
- cORSOptsRWMutex sync.RWMutex
82
-}
83
-
84
-func skipAPIHeader(h string) bool {
85
- switch h {
86
- case "Access-Control-Allow-Origin":
87
- return true
88
- case "Access-Control-Allow-Methods":
89
- return true
90
- case "Access-Control-Allow-Credentials":
91
- return true
92
- default:
93
- return false
94
- }
95
-}
96
-
97
-func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) http.Handler {
98
- if cfg == nil {
99
- panic("must provide a valid ServerConfig")
100
- }
101
-
102
- // setup request logger
103
- ctx.ReqLog = new(cmds.ReqLog)
104
-
105
- // Wrap the internal handler with CORS handling-middleware.
106
- // Create a handler for the API.
107
- internal := internalHandler{
108
- ctx: ctx,
109
- root: root,
110
- cfg: cfg,
111
- }
112
- c := cors.New(*cfg.cORSOpts)
113
- return &Handler{internal, c.Handler(internal)}
114
-}
115
-
116
-func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
117
- // Call the CORS handler which wraps the internal handler.
118
- i.corsHandler.ServeHTTP(w, r)
119
-}
120
-
121
-func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
122
- log.Debug("incoming API request: ", r.URL)
123
-
124
- defer func() {
125
- if r := recover(); r != nil {
126
- log.Error("a panic has occurred in the commands handler!")
127
- log.Error(r)
128
-
129
- debug.PrintStack()
130
- }
131
- }()
132
-
133
- // get the node's context to pass into the commands.
134
- node, err := i.ctx.GetNode()
135
- if err != nil {
136
- s := fmt.Sprintf("cmds/http: couldn't GetNode(): %s", err)
137
- http.Error(w, s, http.StatusInternalServerError)
138
- return
139
- }
140
-
141
- ctx, cancel := context.WithCancel(node.Context())
142
- defer cancel()
143
- ctx = logging.ContextWithLoggable(ctx, loggables.Uuid("requestId"))
144
- if cn, ok := w.(http.CloseNotifier); ok {
145
- clientGone := cn.CloseNotify()
146
- go func() {
147
- select {
148
- case <-clientGone:
149
- case <-ctx.Done():
150
- }
151
- cancel()
152
- }()
153
- }
154
-
155
- if !allowOrigin(r, i.cfg) || !allowReferer(r, i.cfg) {
156
- w.WriteHeader(http.StatusForbidden)
157
- w.Write([]byte("403 - Forbidden"))
158
- log.Warningf("API blocked request to %s. (possible CSRF)", r.URL)
159
- return
160
- }
161
-
162
- req, err := Parse(r, i.root)
163
- if err != nil {
164
- if err == ErrNotFound {
165
- w.WriteHeader(http.StatusNotFound)
166
- } else {
167
- w.WriteHeader(http.StatusBadRequest)
168
- }
169
- w.Write([]byte(err.Error()))
170
- return
171
- }
172
-
173
- reqLogEnt := i.ctx.ReqLog.Add(req)
174
- defer i.ctx.ReqLog.Finish(reqLogEnt)
175
-
176
- //ps: take note of the name clash - commands.Context != context.Context
177
- req.SetInvocContext(i.ctx)
178
-
179
- err = req.SetRootContext(ctx)
180
- if err != nil {
181
- http.Error(w, err.Error(), http.StatusInternalServerError)
182
- return
183
- }
184
-
185
- // call the command
186
- res := i.root.Call(req)
187
-
188
- // set user's headers first.
189
- for k, v := range i.cfg.Headers {
190
- if !skipAPIHeader(k) {
191
- w.Header()[k] = v
192
- }
193
- }
194
-
195
- // now handle responding to the client properly
196
- sendResponse(w, r, res, req)
197
-}
198
-
199
-func guessMimeType(res cmds.Response) (string, error) {
200
- // Try to guess mimeType from the encoding option
201
- enc, found, err := res.Request().Option(cmdkit.EncShort).String()
202
- if err != nil {
203
- return "", err
204
- }
205
- if !found {
206
- return "", errors.New("no encoding option set")
207
- }
208
-
209
- if m, ok := mimeTypes[enc]; ok {
210
- return m, nil
211
- }
212
-
213
- return mimeTypes[cmds.JSON], nil
214
-}
215
-
216
-func sendResponse(w http.ResponseWriter, r *http.Request, res cmds.Response, req cmds.Request) {
217
- h := w.Header()
218
- // Expose our agent to allow identification
219
- h.Set("Server", "go-ipfs/"+config.CurrentVersionNumber)
220
-
221
- mime, err := guessMimeType(res)
222
- if err != nil {
223
- http.Error(w, err.Error(), http.StatusInternalServerError)
224
- return
225
- }
226
-
227
- status := http.StatusOK
228
- // if response contains an error, write an HTTP error status code
229
- if e := res.Error(); e != nil {
230
- if e.Code == cmdkit.ErrClient {
231
- status = http.StatusBadRequest
232
- } else {
233
- status = http.StatusInternalServerError
234
- }
235
- // NOTE: The error will actually be written out by the reader below
236
- }
237
-
238
- out, err := res.Reader()
239
- if err != nil {
240
- http.Error(w, err.Error(), http.StatusInternalServerError)
241
- return
242
- }
243
-
244
- // Set up our potential trailer
245
- h.Set("Trailer", StreamErrHeader)
246
-
247
- if res.Length() > 0 {
248
- h.Set("X-Content-Length", strconv.FormatUint(res.Length(), 10))
249
- }
250
-
251
- if _, ok := res.Output().(io.Reader); ok {
252
- // set streams output type to text to avoid issues with browsers rendering
253
- // html pages on priveleged api ports
254
- mime = "text/plain"
255
- h.Set(streamHeader, "1")
256
- }
257
-
258
- // if output is a channel and user requested streaming channels,
259
- // use chunk copier for the output
260
- _, isChan := res.Output().(chan interface{})
261
- if !isChan {
262
- _, isChan = res.Output().(<-chan interface{})
263
- }
264
-
265
- if isChan {
266
- h.Set(channelHeader, "1")
267
- }
268
-
269
- // catch-all, set to text as default
270
- if mime == "" {
271
- mime = "text/plain"
272
- }
273
-
274
- h.Set(contentTypeHeader, mime)
275
-
276
- // set 'allowed' headers
277
- h.Set("Access-Control-Allow-Headers", AllowedExposedHeaders)
278
- // expose those headers
279
- h.Set("Access-Control-Expose-Headers", AllowedExposedHeaders)
280
-
281
- if r.Method == "HEAD" { // after all the headers.
282
- return
283
- }
284
-
285
- w.WriteHeader(status)
286
- err = flushCopy(w, out)
287
- if err != nil {
288
- log.Error("err: ", err)
289
- w.Header().Set(StreamErrHeader, sanitizedErrStr(err))
290
- }
291
-}
292
-
293
-func flushCopy(w io.Writer, r io.Reader) error {
294
- buf := make([]byte, 4096)
295
- f, ok := w.(http.Flusher)
296
- if !ok {
297
- _, err := io.Copy(w, r)
298
- return err
299
- }
300
- for {
301
- n, err := r.Read(buf)
302
- switch err {
303
- case io.EOF:
304
- if n <= 0 {
305
- return nil
306
- }
307
- // if data was returned alongside the EOF, pretend we didnt
308
- // get an EOF. The next read call should also EOF.
309
- case nil:
310
- // continue
311
- default:
312
- return err
313
- }
314
-
315
- nw, err := w.Write(buf[:n])
316
- if err != nil {
317
- return err
318
- }
319
-
320
- if nw != n {
321
- return fmt.Errorf("http write failed to write full amount: %d != %d", nw, n)
322
- }
323
-
324
- f.Flush()
325
- }
326
-}
327
-
328
-func sanitizedErrStr(err error) string {
329
- s := err.Error()
330
- s = strings.Split(s, "\n")[0]
331
- s = strings.Split(s, "\r")[0]
332
- return s
333
-}
334
-
335
-func NewServerConfig() *ServerConfig {
336
- cfg := new(ServerConfig)
337
- cfg.cORSOpts = new(cors.Options)
338
- return cfg
339
-}
340
-
341
-func (cfg ServerConfig) AllowedOrigins() []string {
342
- cfg.cORSOptsRWMutex.RLock()
343
- defer cfg.cORSOptsRWMutex.RUnlock()
344
- return cfg.cORSOpts.AllowedOrigins
345
-}
346
-
347
-func (cfg *ServerConfig) SetAllowedOrigins(origins ...string) {
348
- cfg.cORSOptsRWMutex.Lock()
349
- defer cfg.cORSOptsRWMutex.Unlock()
350
- o := make([]string, len(origins))
351
- copy(o, origins)
352
- cfg.cORSOpts.AllowedOrigins = o
353
-}
354
-
355
-func (cfg *ServerConfig) AppendAllowedOrigins(origins ...string) {
356
- cfg.cORSOptsRWMutex.Lock()
357
- defer cfg.cORSOptsRWMutex.Unlock()
358
- cfg.cORSOpts.AllowedOrigins = append(cfg.cORSOpts.AllowedOrigins, origins...)
359
-}
360
-
361
-func (cfg ServerConfig) AllowedMethods() []string {
362
- cfg.cORSOptsRWMutex.RLock()
363
- defer cfg.cORSOptsRWMutex.RUnlock()
364
- return []string(cfg.cORSOpts.AllowedMethods)
365
-}
366
-
367
-func (cfg *ServerConfig) SetAllowedMethods(methods ...string) {
368
- cfg.cORSOptsRWMutex.Lock()
369
- defer cfg.cORSOptsRWMutex.Unlock()
370
- if cfg.cORSOpts == nil {
371
- cfg.cORSOpts = new(cors.Options)
372
- }
373
- cfg.cORSOpts.AllowedMethods = methods
374
-}
375
-
376
-func (cfg *ServerConfig) SetAllowCredentials(flag bool) {
377
- cfg.cORSOptsRWMutex.Lock()
378
- defer cfg.cORSOptsRWMutex.Unlock()
379
- cfg.cORSOpts.AllowCredentials = flag
380
-}
381
-
382
-// allowOrigin just stops the request if the origin is not allowed.
383
-// the CORS middleware apparently does not do this for us...
384
-func allowOrigin(r *http.Request, cfg *ServerConfig) bool {
385
- origin := r.Header.Get("Origin")
386
-
387
- // curl, or ipfs shell, typing it in manually, or clicking link
388
- // NOT in a browser. this opens up a hole. we should close it,
389
- // but right now it would break things. TODO
390
- if origin == "" {
391
- return true
392
- }
393
- origins := cfg.AllowedOrigins()
394
- for _, o := range origins {
395
- if o == "*" { // ok! you asked for it!
396
- return true
397
- }
398
-
399
- if o == origin { // allowed explicitly
400
- return true
401
- }
402
- }
403
-
404
- return false
405
-}
406
-
407
-// allowReferer this is here to prevent some CSRF attacks that
408
-// the API would be vulnerable to. We check that the Referer
409
-// is allowed by CORS Origin (origins and referrers here will
410
-// work similarly in the normla uses of the API).
411
-// See discussion at https://github.com/ipfs/go-ipfs/issues/1532
412
-func allowReferer(r *http.Request, cfg *ServerConfig) bool {
413
- referer := r.Referer()
414
-
415
- // curl, or ipfs shell, typing it in manually, or clicking link
416
- // NOT in a browser. this opens up a hole. we should close it,
417
- // but right now it would break things. TODO
418
- if referer == "" {
419
- return true
420
- }
421
-
422
- u, err := url.Parse(referer)
423
- if err != nil {
424
- // bad referer. but there _is_ something, so bail.
425
- log.Debug("failed to parse referer: ", referer)
426
- // debug because referer comes straight from the client. dont want to
427
- // let people DOS by putting a huge referer that gets stored in log files.
428
- return false
429
- }
430
- origin := u.Scheme + "://" + u.Host
431
-
432
- // check CORS ACAOs and pretend Referer works like an origin.
433
- // this is valid for many (most?) sane uses of the API in
434
- // other applications, and will have the desired effect.
435
- origins := cfg.AllowedOrigins()
436
- for _, o := range origins {
437
- if o == "*" { // ok! you asked for it!
438
- return true
439
- }
440
-
441
- // referer is allowed explicitly
442
- if o == origin {
443
- return true
444
- }
445
- }
446
-
447
- return false
448
-}
449
-
450
-// apiVersionMatches checks whether the api client is running the
451
-// same version of go-ipfs. for now, only the exact same version of
452
-// client + server work. In the future, we should use semver for
453
-// proper API versioning! \o/
454
-func apiVersionMatches(r *http.Request) error {
455
- clientVersion := r.UserAgent()
456
- // skips check if client is not go-ipfs
457
- if clientVersion == "" || !strings.Contains(clientVersion, "/go-ipfs/") {
458
- return nil
459
- }
460
-
461
- daemonVersion := config.ApiVersion
462
- if daemonVersion != clientVersion {
463
- return fmt.Errorf("%s (%s != %s)", errApiVersionMismatch, daemonVersion, clientVersion)
464
- }
465
- return nil
466
-}
commands/http/handler_test.go
deleted
-348
@@ -1,348 +0,0 @@
1
-package http
2
-
3
-import (
4
- "net/http"
5
- "net/http/httptest"
6
- "net/url"
7
- "testing"
8
-
9
- cmds "github.com/ipfs/go-ipfs/commands"
10
- ipfscmd "github.com/ipfs/go-ipfs/core/commands"
11
- coremock "github.com/ipfs/go-ipfs/core/mock"
12
-)
13
-
14
-func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
15
- for name, value := range reqHeaders {
16
- if resHeaders.Get(name) != value {
17
- t.Errorf("Invalid header '%s', wanted '%s', got '%s'", name, value, resHeaders.Get(name))
18
- }
19
- }
20
-}
21
-
22
-func assertStatus(t *testing.T, actual, expected int) {
23
- if actual != expected {
24
- t.Errorf("Expected status: %d got: %d", expected, actual)
25
- }
26
-}
27
-
28
-func originCfg(origins []string) *ServerConfig {
29
- cfg := NewServerConfig()
30
- cfg.SetAllowedOrigins(origins...)
31
- cfg.SetAllowedMethods("GET", "PUT", "POST")
32
- return cfg
33
-}
34
-
35
-type testCase struct {
36
- Method string
37
- Path string
38
- Code int
39
- Origin string
40
- Referer string
41
- AllowOrigins []string
42
- ReqHeaders map[string]string
43
- ResHeaders map[string]string
44
-}
45
-
46
-var defaultOrigins = []string{
47
- "http://localhost",
48
- "http://127.0.0.1",
49
- "https://localhost",
50
- "https://127.0.0.1",
51
-}
52
-
53
-func getTestServer(t *testing.T, origins []string) *httptest.Server {
54
- cmdsCtx, err := coremock.MockCmdsCtx()
55
- if err != nil {
56
- t.Error("failure to initialize mock cmds ctx", err)
57
- return nil
58
- }
59
-
60
- cmdRoot := &cmds.Command{
61
- Subcommands: map[string]*cmds.Command{
62
- "version": ipfscmd.VersionCmd,
63
- },
64
- }
65
-
66
- if len(origins) == 0 {
67
- origins = defaultOrigins
68
- }
69
-
70
- handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins))
71
- return httptest.NewServer(handler)
72
-}
73
-
74
-func (tc *testCase) test(t *testing.T) {
75
- // defaults
76
- method := tc.Method
77
- if method == "" {
78
- method = "GET"
79
- }
80
-
81
- path := tc.Path
82
- if path == "" {
83
- path = "/api/v0/version"
84
- }
85
-
86
- expectCode := tc.Code
87
- if expectCode == 0 {
88
- expectCode = 200
89
- }
90
-
91
- // request
92
- req, err := http.NewRequest(method, path, nil)
93
- if err != nil {
94
- t.Error(err)
95
- return
96
- }
97
-
98
- for k, v := range tc.ReqHeaders {
99
- req.Header.Add(k, v)
100
- }
101
- if tc.Origin != "" {
102
- req.Header.Add("Origin", tc.Origin)
103
- }
104
- if tc.Referer != "" {
105
- req.Header.Add("Referer", tc.Referer)
106
- }
107
-
108
- // server
109
- server := getTestServer(t, tc.AllowOrigins)
110
- if server == nil {
111
- return
112
- }
113
- defer server.Close()
114
-
115
- req.URL, err = url.Parse(server.URL + path)
116
- if err != nil {
117
- t.Error(err)
118
- return
119
- }
120
-
121
- res, err := http.DefaultClient.Do(req)
122
- if err != nil {
123
- t.Error(err)
124
- return
125
- }
126
-
127
- // checks
128
- t.Log("GET", server.URL+path, req.Header, res.Header)
129
- assertHeaders(t, res.Header, tc.ResHeaders)
130
- assertStatus(t, res.StatusCode, expectCode)
131
-}
132
-
133
-func TestDisallowedOrigins(t *testing.T) {
134
- gtc := func(origin string, allowedOrigins []string) testCase {
135
- return testCase{
136
- Origin: origin,
137
- AllowOrigins: allowedOrigins,
138
- ResHeaders: map[string]string{
139
- ACAOrigin: "",
140
- ACAMethods: "",
141
- ACACredentials: "",
142
- "Access-Control-Max-Age": "",
143
- "Access-Control-Expose-Headers": "",
144
- },
145
- Code: http.StatusForbidden,
146
- }
147
- }
148
-
149
- tcs := []testCase{
150
- gtc("http://barbaz.com", nil),
151
- gtc("http://barbaz.com", []string{"http://localhost"}),
152
- gtc("http://127.0.0.1", []string{"http://localhost"}),
153
- gtc("http://localhost", []string{"http://127.0.0.1"}),
154
- gtc("http://127.0.0.1:1234", nil),
155
- gtc("http://localhost:1234", nil),
156
- }
157
-
158
- for _, tc := range tcs {
159
- tc.test(t)
160
- }
161
-}
162
-
163
-func TestAllowedOrigins(t *testing.T) {
164
- gtc := func(origin string, allowedOrigins []string) testCase {
165
- return testCase{
166
- Origin: origin,
167
- AllowOrigins: allowedOrigins,
168
- ResHeaders: map[string]string{
169
- ACAOrigin: origin,
170
- ACAMethods: "",
171
- ACACredentials: "",
172
- "Access-Control-Max-Age": "",
173
- "Access-Control-Expose-Headers": AllowedExposedHeaders,
174
- },
175
- Code: http.StatusOK,
176
- }
177
- }
178
-
179
- tcs := []testCase{
180
- gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
181
- gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
182
- gtc("http://localhost", nil),
183
- gtc("http://127.0.0.1", nil),
184
- }
185
-
186
- for _, tc := range tcs {
187
- tc.test(t)
188
- }
189
-}
190
-
191
-func TestWildcardOrigin(t *testing.T) {
192
- gtc := func(origin string, allowedOrigins []string) testCase {
193
- return testCase{
194
- Origin: origin,
195
- AllowOrigins: allowedOrigins,
196
- ResHeaders: map[string]string{
197
- ACAOrigin: origin,
198
- ACAMethods: "",
199
- ACACredentials: "",
200
- "Access-Control-Max-Age": "",
201
- "Access-Control-Expose-Headers": AllowedExposedHeaders,
202
- },
203
- Code: http.StatusOK,
204
- }
205
- }
206
-
207
- tcs := []testCase{
208
- gtc("http://barbaz.com", []string{"*"}),
209
- gtc("http://barbaz.com", []string{"http://localhost", "*"}),
210
- gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
211
- gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
212
- gtc("http://127.0.0.1", []string{"*"}),
213
- gtc("http://localhost", []string{"*"}),
214
- gtc("http://127.0.0.1:1234", []string{"*"}),
215
- gtc("http://localhost:1234", []string{"*"}),
216
- }
217
-
218
- for _, tc := range tcs {
219
- tc.test(t)
220
- }
221
-}
222
-
223
-func TestDisallowedReferer(t *testing.T) {
224
- gtc := func(referer string, allowedOrigins []string) testCase {
225
- return testCase{
226
- Origin: "http://localhost",
227
- Referer: referer,
228
- AllowOrigins: allowedOrigins,
229
- ResHeaders: map[string]string{
230
- ACAOrigin: "http://localhost",
231
- ACAMethods: "",
232
- ACACredentials: "",
233
- "Access-Control-Max-Age": "",
234
- "Access-Control-Expose-Headers": "",
235
- },
236
- Code: http.StatusForbidden,
237
- }
238
- }
239
-
240
- tcs := []testCase{
241
- gtc("http://foobar.com", nil),
242
- gtc("http://localhost:1234", nil),
243
- gtc("http://127.0.0.1:1234", nil),
244
- }
245
-
246
- for _, tc := range tcs {
247
- tc.test(t)
248
- }
249
-}
250
-
251
-func TestAllowedReferer(t *testing.T) {
252
- gtc := func(referer string, allowedOrigins []string) testCase {
253
- return testCase{
254
- Origin: "http://localhost",
255
- AllowOrigins: allowedOrigins,
256
- ResHeaders: map[string]string{
257
- ACAOrigin: "http://localhost",
258
- ACAMethods: "",
259
- ACACredentials: "",
260
- "Access-Control-Max-Age": "",
261
- "Access-Control-Expose-Headers": AllowedExposedHeaders,
262
- },
263
- Code: http.StatusOK,
264
- }
265
- }
266
-
267
- tcs := []testCase{
268
- gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
269
- gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
270
- gtc("http://localhost", nil),
271
- gtc("http://127.0.0.1", nil),
272
- }
273
-
274
- for _, tc := range tcs {
275
- tc.test(t)
276
- }
277
-}
278
-
279
-func TestWildcardReferer(t *testing.T) {
280
- gtc := func(origin string, allowedOrigins []string) testCase {
281
- return testCase{
282
- Origin: origin,
283
- AllowOrigins: allowedOrigins,
284
- ResHeaders: map[string]string{
285
- ACAOrigin: origin,
286
- ACAMethods: "",
287
- ACACredentials: "",
288
- "Access-Control-Max-Age": "",
289
- "Access-Control-Expose-Headers": AllowedExposedHeaders,
290
- },
291
- Code: http.StatusOK,
292
- }
293
- }
294
-
295
- tcs := []testCase{
296
- gtc("http://barbaz.com", []string{"*"}),
297
- gtc("http://barbaz.com", []string{"http://localhost", "*"}),
298
- gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
299
- gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
300
- gtc("http://127.0.0.1", []string{"*"}),
301
- gtc("http://localhost", []string{"*"}),
302
- gtc("http://127.0.0.1:1234", []string{"*"}),
303
- gtc("http://localhost:1234", []string{"*"}),
304
- }
305
-
306
- for _, tc := range tcs {
307
- tc.test(t)
308
- }
309
-}
310
-
311
-func TestAllowedMethod(t *testing.T) {
312
- gtc := func(method string, ok bool) testCase {
313
- code := http.StatusOK
314
- hdrs := map[string]string{
315
- ACAOrigin: "http://localhost",
316
- ACAMethods: method,
317
- ACACredentials: "",
318
- "Access-Control-Max-Age": "",
319
- "Access-Control-Expose-Headers": "",
320
- }
321
-
322
- if !ok {
323
- hdrs[ACAOrigin] = ""
324
- hdrs[ACAMethods] = ""
325
- }
326
-
327
- return testCase{
328
- Method: "OPTIONS",
329
- Origin: "http://localhost",
330
- AllowOrigins: []string{"*"},
331
- ReqHeaders: map[string]string{
332
- "Access-Control-Request-Method": method,
333
- },
334
- ResHeaders: hdrs,
335
- Code: code,
336
- }
337
- }
338
-
339
- tcs := []testCase{
340
- gtc("PUT", true),
341
- gtc("GET", true),
342
- gtc("FOOBAR", false),
343
- }
344
-
345
- for _, tc := range tcs {
346
- tc.test(t)
347
- }
348
-}
commands/http/multifilereader.go
deleted
-126
@@ -1,126 +0,0 @@
1
-package http
2
-
3
-import (
4
- "bytes"
5
- "fmt"
6
- "io"
7
- "mime/multipart"
8
- "net/textproto"
9
- "net/url"
10
- "sync"
11
-
12
- files "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit/files"
13
-)
14
-
15
-// MultiFileReader reads from a `commands.File` (which can be a directory of files
16
-// or a regular file) as HTTP multipart encoded data.
17
-type MultiFileReader struct {
18
- io.Reader
19
-
20
- files []files.File
21
- currentFile io.Reader
22
- buf bytes.Buffer
23
- mpWriter *multipart.Writer
24
- closed bool
25
- mutex *sync.Mutex
26
-
27
- // if true, the data will be type 'multipart/form-data'
28
- // if false, the data will be type 'multipart/mixed'
29
- form bool
30
-}
31
-
32
-// NewMultiFileReader constructs a MultiFileReader. `file` can be any `commands.File`.
33
-// If `form` is set to true, the multipart data will have a Content-Type of 'multipart/form-data',
34
-// if `form` is false, the Content-Type will be 'multipart/mixed'.
35
-func NewMultiFileReader(file files.File, form bool) *MultiFileReader {
36
- mfr := &MultiFileReader{
37
- files: []files.File{file},
38
- form: form,
39
- mutex: &sync.Mutex{},
40
- }
41
- mfr.mpWriter = multipart.NewWriter(&mfr.buf)
42
-
43
- return mfr
44
-}
45
-
46
-func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
47
- mfr.mutex.Lock()
48
- defer mfr.mutex.Unlock()
49
-
50
- // if we are closed and the buffer is flushed, end reading
51
- if mfr.closed && mfr.buf.Len() == 0 {
52
- return 0, io.EOF
53
- }
54
-
55
- // if the current file isn't set, advance to the next file
56
- if mfr.currentFile == nil {
57
- var file files.File
58
- for file == nil {
59
- if len(mfr.files) == 0 {
60
- mfr.mpWriter.Close()
61
- mfr.closed = true
62
- return mfr.buf.Read(buf)
63
- }
64
-
65
- nextfile, err := mfr.files[len(mfr.files)-1].NextFile()
66
- if err == io.EOF {
67
- mfr.files = mfr.files[:len(mfr.files)-1]
68
- continue
69
- } else if err != nil {
70
- return 0, err
71
- }
72
-
73
- file = nextfile
74
- }
75
-
76
- // handle starting a new file part
77
- if !mfr.closed {
78
-
79
- var contentType string
80
- if _, ok := file.(*files.Symlink); ok {
81
- contentType = "application/symlink"
82
- } else if file.IsDirectory() {
83
- mfr.files = append(mfr.files, file)
84
- contentType = "application/x-directory"
85
- } else {
86
- // otherwise, use the file as a reader to read its contents
87
- contentType = "application/octet-stream"
88
- }
89
-
90
- mfr.currentFile = file
91
-
92
- // write the boundary and headers
93
- header := make(textproto.MIMEHeader)
94
- filename := url.QueryEscape(file.FileName())
95
- header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", filename))
96
-
97
- header.Set("Content-Type", contentType)
98
- if rf, ok := file.(*files.ReaderFile); ok {
99
- header.Set("abspath", rf.AbsPath())
100
- }
101
-
102
- _, err := mfr.mpWriter.CreatePart(header)
103
- if err != nil {
104
- return 0, err
105
- }
106
- }
107
- }
108
-
109
- // if the buffer has something in it, read from it
110
- if mfr.buf.Len() > 0 {
111
- return mfr.buf.Read(buf)
112
- }
113
-
114
- // otherwise, read from file data
115
- written, err = mfr.currentFile.Read(buf)
116
- if err == io.EOF {
117
- mfr.currentFile = nil
118
- return written, nil
119
- }
120
- return written, err
121
-}
122
-
123
-// Boundary returns the boundary string to be used to separate files in the multipart data
124
-func (mfr *MultiFileReader) Boundary() string {
125
- return mfr.mpWriter.Boundary()
126
-}
commands/http/multifilereader_test.go
deleted
-114
@@ -1,114 +0,0 @@
1
-package http
2
-
3
-import (
4
- "io"
5
- "io/ioutil"
6
- "mime/multipart"
7
- "strings"
8
- "testing"
9
-
10
- "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit/files"
11
-)
12
-
13
-func TestOutput(t *testing.T) {
14
- text := "Some text! :)"
15
- fileset := []files.File{
16
- files.NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader(text)), nil),
17
- files.NewSliceFile("boop", "boop", []files.File{
18
- files.NewReaderFile("boop/a.txt", "boop/a.txt", ioutil.NopCloser(strings.NewReader("bleep")), nil),
19
- files.NewReaderFile("boop/b.txt", "boop/b.txt", ioutil.NopCloser(strings.NewReader("bloop")), nil),
20
- }),
21
- files.NewReaderFile("beep.txt", "beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil),
22
- }
23
- sf := files.NewSliceFile("", "", fileset)
24
- buf := make([]byte, 20)
25
-
26
- // testing output by reading it with the go stdlib "mime/multipart" Reader
27
- mfr := NewMultiFileReader(sf, true)
28
- mpReader := multipart.NewReader(mfr, mfr.Boundary())
29
-
30
- part, err := mpReader.NextPart()
31
- if part == nil || err != nil {
32
- t.Fatal("Expected non-nil part, nil error")
33
- }
34
- mpf, err := files.NewFileFromPart(part)
35
- if mpf == nil || err != nil {
36
- t.Fatal("Expected non-nil MultipartFile, nil error")
37
- }
38
- if mpf.IsDirectory() {
39
- t.Fatal("Expected file to not be a directory")
40
- }
41
- if mpf.FileName() != "file.txt" {
42
- t.Fatal("Expected filename to be \"file.txt\"")
43
- }
44
- if n, err := mpf.Read(buf); n != len(text) || err != nil {
45
- t.Fatal("Expected to read from file", n, err)
46
- }
47
- if string(buf[:len(text)]) != text {
48
- t.Fatal("Data read was different than expected")
49
- }
50
-
51
- part, err = mpReader.NextPart()
52
- if part == nil || err != nil {
53
- t.Fatal("Expected non-nil part, nil error")
54
- }
55
- mpf, err = files.NewFileFromPart(part)
56
- if mpf == nil || err != nil {
57
- t.Fatal("Expected non-nil MultipartFile, nil error")
58
- }
59
- if !mpf.IsDirectory() {
60
- t.Fatal("Expected file to be a directory")
61
- }
62
- if mpf.FileName() != "boop" {
63
- t.Fatal("Expected filename to be \"boop\"")
64
- }
65
-
66
- part, err = mpReader.NextPart()
67
- if part == nil || err != nil {
68
- t.Fatal("Expected non-nil part, nil error")
69
- }
70
- child, err := files.NewFileFromPart(part)
71
- if child == nil || err != nil {
72
- t.Fatal("Expected to be able to read a child file")
73
- }
74
- if child.IsDirectory() {
75
- t.Fatal("Expected file to not be a directory")
76
- }
77
- if child.FileName() != "boop/a.txt" {
78
- t.Fatal("Expected filename to be \"some/file/path\"")
79
- }
80
-
81
- part, err = mpReader.NextPart()
82
- if part == nil || err != nil {
83
- t.Fatal("Expected non-nil part, nil error")
84
- }
85
- child, err = files.NewFileFromPart(part)
86
- if child == nil || err != nil {
87
- t.Fatal("Expected to be able to read a child file")
88
- }
89
- if child.IsDirectory() {
90
- t.Fatal("Expected file to not be a directory")
91
- }
92
- if child.FileName() != "boop/b.txt" {
93
- t.Fatal("Expected filename to be \"some/file/path\"")
94
- }
95
-
96
- child, err = mpf.NextFile()
97
- if child != nil || err != io.EOF {
98
- t.Fatal("Expected to get (nil, io.EOF)")
99
- }
100
-
101
- part, err = mpReader.NextPart()
102
- if part == nil || err != nil {
103
- t.Fatal("Expected non-nil part, nil error")
104
- }
105
- mpf, err = files.NewFileFromPart(part)
106
- if mpf == nil || err != nil {
107
- t.Fatal("Expected non-nil MultipartFile, nil error")
108
- }
109
-
110
- part, err = mpReader.NextPart()
111
- if part != nil || err != io.EOF {
112
- t.Fatal("Expected to get (nil, io.EOF)")
113
- }
114
-}
commands/http/parse.go
deleted
-160
@@ -1,160 +0,0 @@
1
-package http
2
-
3
-import (
4
- "errors"
5
- "fmt"
6
- "mime"
7
- "net/http"
8
- "strings"
9
-
10
- cmds "github.com/ipfs/go-ipfs/commands"
11
- path "github.com/ipfs/go-ipfs/path"
12
-
13
- cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
14
- files "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit/files"
15
-)
16
-
17
-// Parse parses the data in a http.Request and returns a command Request object
18
-func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) {
19
- if !strings.HasPrefix(r.URL.Path, ApiPath) {
20
- return nil, errors.New("Unexpected path prefix")
21
- }
22
- pth := path.SplitList(strings.TrimPrefix(r.URL.Path, ApiPath+"/"))
23
-
24
- stringArgs := make([]string, 0)
25
-
26
- if err := apiVersionMatches(r); err != nil {
27
- if pth[0] != "version" { // compatibility with previous version check
28
- return nil, err
29
- }
30
- }
31
-
32
- cmd, err := root.Get(pth[:len(pth)-1])
33
- if err != nil {
34
- // 404 if there is no command at that path
35
- return nil, ErrNotFound
36
- }
37
-
38
- if sub := cmd.Subcommand(pth[len(pth)-1]); sub == nil {
39
- if len(pth) <= 1 {
40
- return nil, ErrNotFound
41
- }
42
-
43
- // if the last string in the path isn't a subcommand, use it as an argument
44
- // e.g. /objects/Qabc12345 (we are passing "Qabc12345" to the "objects" command)
45
- stringArgs = append(stringArgs, pth[len(pth)-1])
46
- pth = pth[:len(pth)-1]
47
-
48
- } else {
49
- cmd = sub
50
- }
51
-
52
- opts, stringArgs2 := parseOptions(r)
53
- stringArgs = append(stringArgs, stringArgs2...)
54
-
55
- // count required argument definitions
56
- numRequired := 0
57
- for _, argDef := range cmd.Arguments {
58
- if argDef.Required {
59
- numRequired++
60
- }
61
- }
62
-
63
- // count the number of provided argument values
64
- valCount := len(stringArgs)
65
-
66
- args := make([]string, valCount)
67
-
68
- valIndex := 0
69
- requiredFile := ""
70
- for _, argDef := range cmd.Arguments {
71
- // skip optional argument definitions if there aren't sufficient remaining values
72
- if valCount-valIndex <= numRequired && !argDef.Required {
73
- continue
74
- } else if argDef.Required {
75
- numRequired--
76
- }
77
-
78
- if argDef.Type == cmdkit.ArgString {
79
- if argDef.Variadic {
80
- for _, s := range stringArgs {
81
- args[valIndex] = s
82
- valIndex++
83
- }
84
- valCount -= len(stringArgs)
85
-
86
- } else if len(stringArgs) > 0 {
87
- args[valIndex] = stringArgs[0]
88
- stringArgs = stringArgs[1:]
89
- valIndex++
90
-
91
- } else {
92
- break
93
- }
94
- } else if argDef.Type == cmdkit.ArgFile && argDef.Required && len(requiredFile) == 0 {
95
- requiredFile = argDef.Name
96
- }
97
- }
98
-
99
- optDefs, err := root.GetOptions(pth)
100
- if err != nil {
101
- return nil, err
102
- }
103
-
104
- // create cmds.File from multipart/form-data contents
105
- contentType := r.Header.Get(contentTypeHeader)
106
- mediatype, _, _ := mime.ParseMediaType(contentType)
107
-
108
- var f files.File
109
- if mediatype == "multipart/form-data" {
110
- reader, err := r.MultipartReader()
111
- if err != nil {
112
- return nil, err
113
- }
114
-
115
- f = &files.MultipartFile{
116
- Mediatype: mediatype,
117
- Reader: reader,
118
- }
119
- }
120
-
121
- // if there is a required filearg, error if no files were provided
122
- if len(requiredFile) > 0 && f == nil {
123
- return nil, fmt.Errorf("File argument '%s' is required", requiredFile)
124
- }
125
-
126
- req, err := cmds.NewRequest(pth, opts, args, f, cmd, optDefs)
127
- if err != nil {
128
- return nil, err
129
- }
130
-
131
- err = cmd.CheckArguments(req)
132
- if err != nil {
133
- return nil, err
134
- }
135
-
136
- return req, nil
137
-}
138
-
139
-func parseOptions(r *http.Request) (map[string]interface{}, []string) {
140
- opts := make(map[string]interface{})
141
- var args []string
142
-
143
- query := r.URL.Query()
144
- for k, v := range query {
145
- if k == "arg" {
146
- args = v
147
- } else {
148
- opts[k] = v[0]
149
- }
150
- }
151
-
152
- // default to setting encoding to JSON
153
- _, short := opts[cmdkit.EncShort]
154
- _, long := opts[cmdkit.EncLong]
155
- if !short && !long {
156
- opts[cmdkit.EncShort] = cmds.JSON
157
- }
158
-
159
- return opts, args
160
-}