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

import (
Steven Allen's avatar
Steven Allen committed
4
	"encoding/base32"
5
	"fmt"
6
	"io/ioutil"
Steven Allen's avatar
Steven Allen committed
7
	"math/rand"
8
	"mime"
9
	"net/http"
10
	"strconv"
11 12
	"strings"

Hector Sanjuan's avatar
Hector Sanjuan committed
13
	cmds "github.com/ipfs/go-ipfs-cmds"
14

Hector Sanjuan's avatar
Hector Sanjuan committed
15
	files "github.com/ipfs/go-ipfs-files"
Steven Allen's avatar
Steven Allen committed
16
	logging "github.com/ipfs/go-log"
17 18
)

19
// parseRequest parses the data in a http.Request and returns a command Request object
Steven Allen's avatar
Steven Allen committed
20
func parseRequest(r *http.Request, root *cmds.Command) (*cmds.Request, error) {
21 22
	if r.URL.Path[0] == '/' {
		r.URL.Path = r.URL.Path[1:]
23 24
	}

25 26 27 28 29
	var (
		stringArgs []string
		pth        = strings.Split(r.URL.Path, "/")
		getPath    = pth[:len(pth)-1]
	)
30

31
	cmdPath, err := root.Resolve(getPath)
32
	if err != nil {
Matt Bell's avatar
Matt Bell committed
33
		// 404 if there is no command at that path
34
		return nil, ErrNotFound
rht's avatar
rht committed
35 36
	}

37 38 39 40 41 42 43
	for _, c := range cmdPath {
		if c.NoRemote {
			return nil, ErrNotFound
		}
	}

	cmd := cmdPath[len(cmdPath)-1]
keks's avatar
cleanup  
keks committed
44
	sub := cmd.Subcommands[pth[len(pth)-1]]
Jan Winkelmann's avatar
Jan Winkelmann committed
45 46

	if sub == nil {
47
		if cmd.Run == nil {
48 49 50
			return nil, ErrNotFound
		}

Matt Bell's avatar
Matt Bell committed
51 52
		// 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
53 54
		stringArgs = append(stringArgs, pth[len(pth)-1])
		pth = pth[:len(pth)-1]
55 56
	} else {
		cmd = sub
57 58
	}

59 60 61 62
	if cmd.NoRemote {
		return nil, ErrNotFound
	}

63
	opts, stringArgs2 := parseOptions(r)
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
	optDefs, err := root.GetOptions(pth)
	if err != nil {
		return nil, err
	}
	for k, v := range opts {
		if optDef, ok := optDefs[k]; ok {
			name := optDef.Names()[0]
			if k != name {
				opts[name] = v
				delete(opts, k)
			}
		}
	}
	// default to setting encoding to JSON
	if _, ok := opts[cmds.EncLong]; !ok {
		opts[cmds.EncLong] = cmds.JSON
	}

82 83
	stringArgs = append(stringArgs, stringArgs2...)

84
	// count required argument definitions
85
	numRequired := 0
86 87
	for _, argDef := range cmd.Arguments {
		if argDef.Required {
88
			numRequired++
89 90 91 92 93 94
		}
	}

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

95
	args := make([]string, valCount)
96 97

	valIndex := 0
98
	requiredFile := ""
99 100
	for _, argDef := range cmd.Arguments {
		// skip optional argument definitions if there aren't sufficient remaining values
101
		if valCount-valIndex <= numRequired && !argDef.Required {
102
			continue
103
		} else if argDef.Required {
104
			numRequired--
105 106
		}

Steven Allen's avatar
Steven Allen committed
107
		if argDef.Type == cmds.ArgString {
108
			if argDef.Variadic {
109
				for _, s := range stringArgs {
110 111
					args[valIndex] = s
					valIndex++
112
				}
Matt Bell's avatar
Matt Bell committed
113
				valCount -= len(stringArgs)
114 115

			} else if len(stringArgs) > 0 {
116
				args[valIndex] = stringArgs[0]
117
				stringArgs = stringArgs[1:]
118
				valIndex++
119 120 121

			} else {
				break
122
			}
Steven Allen's avatar
Steven Allen committed
123
		} else if argDef.Type == cmds.ArgFile && argDef.Required && len(requiredFile) == 0 {
124
			requiredFile = argDef.Name
125 126
		}
	}
127

128 129
	// create cmds.File from multipart/form-data contents
	contentType := r.Header.Get(contentTypeHeader)
130 131
	mediatype, _, _ := mime.ParseMediaType(contentType)

Łukasz Magiera's avatar
Łukasz Magiera committed
132
	var f files.Directory
133
	if mediatype == "multipart/form-data" {
134
		reader, err := r.MultipartReader()
135 136 137
		if err != nil {
			return nil, err
		}
138

Łukasz Magiera's avatar
Łukasz Magiera committed
139 140 141
		f, err = files.NewFileFromPartReader(reader, mediatype)
		if err != nil {
			return nil, err
142
		}
143 144
	}

145 146
	// if there is a required filearg, error if no files were provided
	if len(requiredFile) > 0 && f == nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
147
		return nil, fmt.Errorf("file argument '%s' is required", requiredFile)
148 149
	}

Steven Allen's avatar
Steven Allen committed
150
	ctx := logging.ContextWithLoggable(r.Context(), uuidLoggable())
151
	req, err := cmds.NewRequest(ctx, pth, opts, args, f, root)
152 153 154
	if err != nil {
		return nil, err
	}
155 156 157 158 159 160

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

161 162
	err = req.FillDefaults()
	return req, err
163 164
}

165
func parseOptions(r *http.Request) (map[string]interface{}, []string) {
166
	opts := make(map[string]interface{})
167
	var args []string
168 169 170

	query := r.URL.Query()
	for k, v := range query {
Matt Bell's avatar
Matt Bell committed
171
		if k == "arg" {
172
			args = v
Matt Bell's avatar
Matt Bell committed
173
		} else {
174

Matt Bell's avatar
Matt Bell committed
175 176
			opts[k] = v[0]
		}
177 178
	}

179 180 181 182 183 184 185 186 187
	return opts, args
}

// parseResponse decodes a http.Response to create a cmds.Response
func parseResponse(httpRes *http.Response, req *cmds.Request) (cmds.Response, error) {
	res := &Response{
		res: httpRes,
		req: req,
		rr:  &responseReader{httpRes},
188 189
	}

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	lengthHeader := httpRes.Header.Get(extraContentLengthHeader)
	if len(lengthHeader) > 0 {
		length, err := strconv.ParseUint(lengthHeader, 10, 64)
		if err != nil {
			return nil, err
		}
		res.length = length
	}

	contentType := httpRes.Header.Get(contentTypeHeader)
	contentType = strings.Split(contentType, ";")[0]

	encType, found := MIMEEncodings[contentType]
	if found {
		makeDec, ok := cmds.Decoders[encType]
		if ok {
			res.dec = makeDec(res.rr)
207
		} else if encType != "text" {
208
			log.Errorf("could not find decoder for encoding %q", encType)
209
		} // else we have an io.Reader, which is okay
210 211
	} else {
		log.Errorf("could not guess encoding from content type %q", contentType)
212 213 214 215
	}

	// If we ran into an error
	if httpRes.StatusCode >= http.StatusBadRequest {
Steven Allen's avatar
Steven Allen committed
216
		e := &cmds.Error{}
217 218 219 220 221

		switch {
		case httpRes.StatusCode == http.StatusNotFound:
			// handle 404s
			e.Message = "Command not found."
Steven Allen's avatar
Steven Allen committed
222
			e.Code = cmds.ErrClient
223 224 225 226 227 228 229
		case contentType == plainText:
			// handle non-marshalled errors
			mes, err := ioutil.ReadAll(res.rr)
			if err != nil {
				return nil, err
			}
			e.Message = string(mes)
Steven Allen's avatar
Steven Allen committed
230 231 232 233 234 235 236 237 238 239
			switch httpRes.StatusCode {
			case http.StatusNotFound, http.StatusBadRequest:
				e.Code = cmds.ErrClient
			case http.StatusTooManyRequests:
				e.Code = cmds.ErrRateLimited
			case http.StatusForbidden:
				e.Code = cmds.ErrForbidden
			default:
				e.Code = cmds.ErrNormal
			}
240 241
		case res.dec == nil:
			return nil, fmt.Errorf("unknown error content type: %s", contentType)
242
		default:
243
			// handle errors from value
244 245
			err := res.dec.Decode(e)
			if err != nil {
keks's avatar
vet  
keks committed
246
				log.Errorf("error parsing error %q", err.Error())
247
			}
248 249
		}

250
		return nil, e
251 252 253
	}

	return res, nil
254
}
Steven Allen's avatar
Steven Allen committed
255 256 257 258 259 260 261 262 263

func uuidLoggable() logging.Loggable {
	ids := make([]byte, 16)
	rand.Read(ids)

	return logging.Metadata{
		"requestId": base32.HexEncoding.EncodeToString(ids),
	}
}