parse.go 3.59 KB
Newer Older
1 2 3
package http

import (
4
	"errors"
5
	"fmt"
6
	"mime"
7 8 9
	"net/http"
	"strings"

10
	cmds "github.com/ipfs/go-ipfs/commands"
rht's avatar
rht committed
11
	path "github.com/ipfs/go-ipfs/path"
Jan Winkelmann's avatar
Jan Winkelmann committed
12

Steven Allen's avatar
Steven Allen committed
13 14
	cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
	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 20 21
	if !strings.HasPrefix(r.URL.Path, ApiPath) {
		return nil, errors.New("Unexpected path prefix")
	}
rht's avatar
rht committed
22
	pth := path.SplitList(strings.TrimPrefix(r.URL.Path, ApiPath+"/"))
23

24
	stringArgs := make([]string, 0)
25

rht's avatar
rht committed
26
	if err := apiVersionMatches(r); err != nil {
rht's avatar
rht committed
27
		if pth[0] != "version" { // compatibility with previous version check
rht's avatar
rht committed
28 29 30 31
			return nil, err
		}
	}

rht's avatar
rht committed
32
	cmd, err := root.Get(pth[:len(pth)-1])
33
	if err != nil {
Matt Bell's avatar
Matt Bell committed
34
		// 404 if there is no command at that path
35
		return nil, ErrNotFound
rht's avatar
rht committed
36 37
	}

rht's avatar
rht committed
38 39
	if sub := cmd.Subcommand(pth[len(pth)-1]); sub == nil {
		if len(pth) <= 1 {
40 41 42
			return nil, ErrNotFound
		}

Matt Bell's avatar
Matt Bell committed
43 44
		// if the last string in the path isn't a subcommand, use it as an argument
		// e.g. /objects/Qabc12345 (we are passing "Qabc12345" to the "objects" command)
rht's avatar
rht committed
45 46 47
		stringArgs = append(stringArgs, pth[len(pth)-1])
		pth = pth[:len(pth)-1]

48 49
	} else {
		cmd = sub
50 51
	}

52 53 54
	opts, stringArgs2 := parseOptions(r)
	stringArgs = append(stringArgs, stringArgs2...)

55
	// count required argument definitions
56
	numRequired := 0
57 58
	for _, argDef := range cmd.Arguments {
		if argDef.Required {
59
			numRequired++
60 61 62 63 64 65
		}
	}

	// count the number of provided argument values
	valCount := len(stringArgs)

66
	args := make([]string, valCount)
67 68

	valIndex := 0
69
	requiredFile := ""
70 71
	for _, argDef := range cmd.Arguments {
		// 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
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
78
		if argDef.Type == cmdkit.ArgString {
79
			if argDef.Variadic {
80
				for _, s := range stringArgs {
81 82
					args[valIndex] = s
					valIndex++
83
				}
Matt Bell's avatar
Matt Bell committed
84
				valCount -= len(stringArgs)
85 86

			} else if len(stringArgs) > 0 {
87
				args[valIndex] = stringArgs[0]
88
				stringArgs = stringArgs[1:]
89
				valIndex++
90 91 92

			} else {
				break
93
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
94
		} else if argDef.Type == cmdkit.ArgFile && argDef.Required && len(requiredFile) == 0 {
95
			requiredFile = argDef.Name
96 97
		}
	}
98

rht's avatar
rht committed
99
	optDefs, err := root.GetOptions(pth)
100 101 102 103
	if err != nil {
		return nil, err
	}

104 105
	// create cmds.File from multipart/form-data contents
	contentType := r.Header.Get(contentTypeHeader)
106 107
	mediatype, _, _ := mime.ParseMediaType(contentType)

108
	var f files.File
109
	if mediatype == "multipart/form-data" {
110
		reader, err := r.MultipartReader()
111 112 113
		if err != nil {
			return nil, err
		}
114 115 116 117 118

		f = &files.MultipartFile{
			Mediatype: mediatype,
			Reader:    reader,
		}
119 120
	}

121 122 123 124 125
	// if there is a required filearg, error if no files were provided
	if len(requiredFile) > 0 && f == nil {
		return nil, fmt.Errorf("File argument '%s' is required", requiredFile)
	}

rht's avatar
rht committed
126
	req, err := cmds.NewRequest(pth, opts, args, f, cmd, optDefs)
127 128 129
	if err != nil {
		return nil, err
	}
130 131 132 133 134 135 136

	err = cmd.CheckArguments(req)
	if err != nil {
		return nil, err
	}

	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 144

	query := r.URL.Query()
	for k, v := range query {
Matt Bell's avatar
Matt Bell committed
145
		if k == "arg" {
146
			args = v
Matt Bell's avatar
Matt Bell committed
147 148 149
		} else {
			opts[k] = v[0]
		}
150 151 152
	}

	// default to setting encoding to JSON
Jan Winkelmann's avatar
Jan Winkelmann committed
153 154
	_, short := opts[cmdkit.EncShort]
	_, long := opts[cmdkit.EncLong]
155
	if !short && !long {
Jan Winkelmann's avatar
Jan Winkelmann committed
156
		opts[cmdkit.EncShort] = cmds.JSON
157 158
	}

Matt Bell's avatar
Matt Bell committed
159
	return opts, args
160
}