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

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

10 11
	cmds "github.com/ipfs/go-ipfs/commands"
	files "github.com/ipfs/go-ipfs/commands/files"
rht's avatar
rht committed
12
	path "github.com/ipfs/go-ipfs/path"
13 14 15
)

// Parse parses the data in a http.Request and returns a command Request object
16
func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) {
17 18 19
	if !strings.HasPrefix(r.URL.Path, ApiPath) {
		return nil, errors.New("Unexpected path prefix")
	}
rht's avatar
rht committed
20
	pth := path.SplitList(strings.TrimPrefix(r.URL.Path, ApiPath+"/"))
21

22
	stringArgs := make([]string, 0)
23

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

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

rht's avatar
rht committed
35 36
	}

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

Matt Bell's avatar
Matt Bell committed
42 43
		// 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
44 45 46
		stringArgs = append(stringArgs, pth[len(pth)-1])
		pth = pth[:len(pth)-1]

47 48
	} else {
		cmd = sub
49 50
	}

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

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

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

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

	valIndex := 0
68
	requiredFile := ""
69 70
	for _, argDef := range cmd.Arguments {
		// skip optional argument definitions if there aren't sufficient remaining values
71
		if valCount-valIndex <= numRequired && !argDef.Required {
72
			continue
73
		} else if argDef.Required {
74
			numRequired--
75 76 77 78
		}

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

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

			} else {
				break
92
			}
93 94
		} else if argDef.Type == cmds.ArgFile && argDef.Required && len(requiredFile) == 0 {
			requiredFile = argDef.Name
95 96
		}
	}
97

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

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

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

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

120 121 122 123 124
	// 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
125
	req, err := cmds.NewRequest(pth, opts, args, f, cmd, optDefs)
126 127 128
	if err != nil {
		return nil, err
	}
129 130 131 132 133 134 135

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

	return req, nil
136 137
}

138
func parseOptions(r *http.Request) (map[string]interface{}, []string) {
139
	opts := make(map[string]interface{})
140
	var args []string
141 142 143

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

	// default to setting encoding to JSON
	_, short := opts[cmds.EncShort]
	_, long := opts[cmds.EncLong]
	if !short && !long {
		opts[cmds.EncShort] = cmds.JSON
	}

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