@cryptotaxi247 / kubo / commits / 68bb10698

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 -}