• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 6442892572

07 Oct 2023 06:45PM UTC coverage: 67.119% (-23.3%) from 90.418%
6442892572

push

github

thschmitt
Upgrade to golang 1.21, update dependencies to latest versions

- Upgrade to golang 1.21
- Update all dependencies to latest versions

3068 of 4571 relevant lines covered (67.12%)

362.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.44
/executor/http_executor.go
1
package executor
2

3
import (
4
        "bytes"
5
        "crypto/rand"
6
        "crypto/tls"
7
        "encoding/json"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "mime/multipart"
12
        "net/http"
13
        "net/url"
14
        "strings"
15
        "time"
16

17
        "github.com/UiPath/uipathcli/auth"
18
        "github.com/UiPath/uipathcli/config"
19
        "github.com/UiPath/uipathcli/log"
20
        "github.com/UiPath/uipathcli/output"
21
        "github.com/UiPath/uipathcli/utils"
22
)
23

24
const NotConfiguredErrorTemplate = `Run config command to set organization and tenant:
25

26
    uipath config
27

28
For more information you can view the help:
29

30
    uipath config --help
31
`
32

33
// The HttpExecutor implements the Executor interface and constructs HTTP request
34
// from the given command line parameters and configurations.
35
type HttpExecutor struct {
36
        authenticators []auth.Authenticator
37
}
38

39
func (e HttpExecutor) Call(context ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
40
        return utils.Retry(func() error {
2✔
41
                return e.call(context, writer, logger)
1✔
42
        })
1✔
43
}
44

45
func (e HttpExecutor) requestId() string {
1✔
46
        bytes := make([]byte, 16)
1✔
47
        _, _ = rand.Read(bytes)
1✔
48
        return fmt.Sprintf("%x%x%x%x%x", bytes[0:4], bytes[4:6], bytes[6:8], bytes[8:10], bytes[10:])
1✔
49
}
1✔
50

51
func (e HttpExecutor) addHeaders(request *http.Request, headerParameters []ExecutionParameter) {
1✔
52
        formatter := newParameterFormatter()
1✔
53
        request.Header.Add("x-request-id", e.requestId())
1✔
54
        for _, parameter := range headerParameters {
1✔
55
                headerValue := formatter.Format(parameter)
×
56
                request.Header.Add(parameter.Name, headerValue)
×
57
        }
×
58
}
59

60
func (e HttpExecutor) calculateMultipartSize(parameters []ExecutionParameter) int64 {
×
61
        result := int64(0)
×
62
        for _, parameter := range parameters {
×
63
                switch v := parameter.Value.(type) {
×
64
                case string:
×
65
                        result = result + int64(len(v))
×
66
                case utils.Stream:
×
67
                        size, err := v.Size()
×
68
                        if err == nil {
×
69
                                result = result + size
×
70
                        }
×
71
                }
72
        }
73
        return result
×
74
}
75

76
func (e HttpExecutor) writeMultipartForm(writer *multipart.Writer, parameters []ExecutionParameter) error {
×
77
        for _, parameter := range parameters {
×
78
                switch v := parameter.Value.(type) {
×
79
                case string:
×
80
                        w, err := writer.CreateFormField(parameter.Name)
×
81
                        if err != nil {
×
82
                                return fmt.Errorf("Error creating form field '%s': %w", parameter.Name, err)
×
83
                        }
×
84
                        _, err = w.Write([]byte(v))
×
85
                        if err != nil {
×
86
                                return fmt.Errorf("Error writing form field '%s': %w", parameter.Name, err)
×
87
                        }
×
88
                case utils.Stream:
×
89
                        w, err := writer.CreateFormFile(parameter.Name, v.Name())
×
90
                        if err != nil {
×
91
                                return fmt.Errorf("Error writing form file '%s': %w", parameter.Name, err)
×
92
                        }
×
93
                        data, err := v.Data()
×
94
                        if err != nil {
×
95
                                return err
×
96
                        }
×
97
                        defer data.Close()
×
98
                        _, err = io.Copy(w, data)
×
99
                        if err != nil {
×
100
                                return fmt.Errorf("Error writing form file '%s': %w", parameter.Name, err)
×
101
                        }
×
102
                }
103
        }
104
        return nil
×
105
}
106

107
func (e HttpExecutor) serializeJson(body io.Writer, parameters []ExecutionParameter) error {
×
108
        data := map[string]interface{}{}
×
109
        for _, parameter := range parameters {
×
110
                data[parameter.Name] = parameter.Value
×
111
        }
×
112
        result, err := json.Marshal(data)
×
113
        if err != nil {
×
114
                return fmt.Errorf("Error creating body: %w", err)
×
115
        }
×
116
        _, err = body.Write(result)
×
117
        if err != nil {
×
118
                return fmt.Errorf("Error writing body: %w", err)
×
119
        }
×
120
        return nil
×
121
}
122

123
func (e HttpExecutor) validateUri(uri string) (*url.URL, error) {
1✔
124
        if strings.Contains(uri, "{organization}") {
1✔
125
                return nil, fmt.Errorf("Missing organization parameter!\n\n%s", NotConfiguredErrorTemplate)
×
126
        }
×
127
        if strings.Contains(uri, "{tenant}") {
1✔
128
                return nil, fmt.Errorf("Missing tenant parameter!\n\n%s", NotConfiguredErrorTemplate)
×
129
        }
×
130

131
        result, err := url.Parse(uri)
1✔
132
        if err != nil {
1✔
133
                return nil, fmt.Errorf("Invalid URI '%s': %w", uri, err)
×
134
        }
×
135
        return result, nil
1✔
136
}
137

138
func (e HttpExecutor) formatUri(baseUri url.URL, route string, pathParameters []ExecutionParameter, queryParameters []ExecutionParameter) (*url.URL, error) {
1✔
139
        formatter := newUriFormatter(baseUri, route)
1✔
140
        for _, parameter := range pathParameters {
3✔
141
                formatter.FormatPath(parameter)
2✔
142
        }
2✔
143
        formatter.AddQueryString(queryParameters)
1✔
144
        return e.validateUri(formatter.Uri())
1✔
145
}
146

147
func (e HttpExecutor) executeAuthenticators(authConfig config.AuthConfig, debug bool, insecure bool, request *http.Request) (*auth.AuthenticatorResult, error) {
1✔
148
        authRequest := *auth.NewAuthenticatorRequest(request.URL.String(), map[string]string{})
1✔
149
        ctx := *auth.NewAuthenticatorContext(authConfig.Type, authConfig.Config, debug, insecure, authRequest)
1✔
150
        for _, authProvider := range e.authenticators {
4✔
151
                result := authProvider.Auth(ctx)
3✔
152
                if result.Error != "" {
3✔
153
                        return nil, errors.New(result.Error)
×
154
                }
×
155
                ctx.Config = result.Config
3✔
156
                for k, v := range result.RequestHeader {
4✔
157
                        ctx.Request.Header[k] = v
1✔
158
                }
1✔
159
        }
160
        return auth.AuthenticatorSuccess(ctx.Request.Header, ctx.Config), nil
1✔
161
}
162

163
func (e HttpExecutor) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader {
2✔
164
        if length < 10*1024*1024 {
4✔
165
                return reader
2✔
166
        }
2✔
167
        progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) {
×
168
                displayText := text
×
169
                if progress.Completed {
×
170
                        displayText = completedText
×
171
                }
×
172
                progressBar.Update(displayText, progress.BytesRead, length, progress.BytesPerSecond)
×
173
        })
174
        return progressReader
×
175
}
176

177
func (e HttpExecutor) writeMultipartBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, errorChan chan error) (string, int64) {
×
178
        contentLength := e.calculateMultipartSize(parameters)
×
179
        formWriter := multipart.NewWriter(bodyWriter)
×
180
        go func() {
×
181
                defer bodyWriter.Close()
×
182
                defer formWriter.Close()
×
183
                err := e.writeMultipartForm(formWriter, parameters)
×
184
                if err != nil {
×
185
                        errorChan <- err
×
186
                        return
×
187
                }
×
188
        }()
189
        return formWriter.FormDataContentType(), contentLength
×
190
}
191

192
func (e HttpExecutor) writeInputBody(bodyWriter *io.PipeWriter, input utils.Stream, errorChan chan error) {
×
193
        go func() {
×
194
                defer bodyWriter.Close()
×
195
                data, err := input.Data()
×
196
                if err != nil {
×
197
                        errorChan <- err
×
198
                        return
×
199
                }
×
200
                defer data.Close()
×
201
                _, err = io.Copy(bodyWriter, data)
×
202
                if err != nil {
×
203
                        errorChan <- err
×
204
                        return
×
205
                }
×
206
        }()
207
}
208

209
func (e HttpExecutor) writeUrlEncodedBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, errorChan chan error) {
×
210
        go func() {
×
211
                defer bodyWriter.Close()
×
212
                formatter := newQueryStringFormatter()
×
213
                queryString := formatter.Format(parameters)
×
214
                _, err := bodyWriter.Write([]byte(queryString))
×
215
                if err != nil {
×
216
                        errorChan <- err
×
217
                        return
×
218
                }
×
219
        }()
220
}
221

222
func (e HttpExecutor) writeJsonBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, errorChan chan error) {
×
223
        go func() {
×
224
                defer bodyWriter.Close()
×
225
                err := e.serializeJson(bodyWriter, parameters)
×
226
                if err != nil {
×
227
                        errorChan <- err
×
228
                        return
×
229
                }
×
230
        }()
231
}
232

233
func (e HttpExecutor) writeBody(context ExecutionContext, errorChan chan error) (io.Reader, string, int64) {
1✔
234
        if context.Input != nil {
1✔
235
                reader, writer := io.Pipe()
×
236
                e.writeInputBody(writer, context.Input, errorChan)
×
237
                return reader, context.ContentType, -1
×
238
        }
×
239
        formParameters := context.Parameters.Form()
1✔
240
        if len(formParameters) > 0 {
1✔
241
                reader, writer := io.Pipe()
×
242
                contentType, contentLength := e.writeMultipartBody(writer, formParameters, errorChan)
×
243
                return reader, contentType, contentLength
×
244
        }
×
245
        bodyParameters := context.Parameters.Body()
1✔
246
        if len(bodyParameters) > 0 && context.ContentType == "application/x-www-form-urlencoded" {
1✔
247
                reader, writer := io.Pipe()
×
248
                e.writeUrlEncodedBody(writer, bodyParameters, errorChan)
×
249
                return reader, context.ContentType, -1
×
250
        }
×
251
        if len(bodyParameters) > 0 {
1✔
252
                reader, writer := io.Pipe()
×
253
                e.writeJsonBody(writer, bodyParameters, errorChan)
×
254
                return reader, context.ContentType, -1
×
255
        }
×
256
        return bytes.NewReader([]byte{}), context.ContentType, -1
1✔
257
}
258

259
func (e HttpExecutor) send(client *http.Client, request *http.Request, errorChan chan error) (*http.Response, error) {
1✔
260
        responseChan := make(chan *http.Response)
1✔
261
        go func(client *http.Client, request *http.Request) {
2✔
262
                response, err := client.Do(request)
1✔
263
                if err != nil {
1✔
264
                        errorChan <- err
×
265
                        return
×
266
                }
×
267
                responseChan <- response
1✔
268
        }(client, request)
269

270
        select {
1✔
271
        case err := <-errorChan:
×
272
                return nil, err
×
273
        case response := <-responseChan:
1✔
274
                return response, nil
1✔
275
        }
276
}
277

278
func (e HttpExecutor) logRequest(logger log.Logger, request *http.Request) {
×
279
        buffer := &bytes.Buffer{}
×
280
        _, _ = buffer.ReadFrom(request.Body)
×
281
        body := buffer.Bytes()
×
282
        request.Body = io.NopCloser(bytes.NewReader(body))
×
283
        requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, bytes.NewReader(body))
×
284
        logger.LogRequest(*requestInfo)
×
285
}
×
286

287
func (e HttpExecutor) logResponse(logger log.Logger, response *http.Response, body []byte) {
1✔
288
        responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
1✔
289
        logger.LogResponse(*responseInfo)
1✔
290
}
1✔
291

292
func (e HttpExecutor) pathParameters(context ExecutionContext) []ExecutionParameter {
1✔
293
        pathParameters := context.Parameters.Path()
1✔
294
        if context.Organization != "" {
2✔
295
                pathParameters = append(pathParameters, *NewExecutionParameter("organization", context.Organization, "path"))
1✔
296
        }
1✔
297
        if context.Tenant != "" {
2✔
298
                pathParameters = append(pathParameters, *NewExecutionParameter("tenant", context.Tenant, "path"))
1✔
299
        }
1✔
300
        return pathParameters
1✔
301
}
302

303
func (e HttpExecutor) call(context ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
304
        uri, err := e.formatUri(context.BaseUri, context.Route, e.pathParameters(context), context.Parameters.Query())
1✔
305
        if err != nil {
1✔
306
                return err
×
307
        }
×
308
        requestError := make(chan error)
1✔
309
        bodyReader, contentType, contentLength := e.writeBody(context, requestError)
1✔
310
        uploadBar := utils.NewProgressBar(logger)
1✔
311
        uploadReader := e.progressReader("uploading...", "completing  ", bodyReader, contentLength, uploadBar)
1✔
312
        defer uploadBar.Remove()
1✔
313
        request, err := http.NewRequest(context.Method, uri.String(), uploadReader)
1✔
314
        if err != nil {
1✔
315
                return fmt.Errorf("Error preparing request: %w", err)
×
316
        }
×
317
        if contentType != "" {
1✔
318
                request.Header.Add("Content-Type", contentType)
×
319
        }
×
320
        e.addHeaders(request, context.Parameters.Header())
1✔
321
        auth, err := e.executeAuthenticators(context.AuthConfig, context.Debug, context.Insecure, request)
1✔
322
        if err != nil {
1✔
323
                return err
×
324
        }
×
325
        for k, v := range auth.RequestHeader {
2✔
326
                request.Header.Add(k, v)
1✔
327
        }
1✔
328

329
        transport := &http.Transport{
1✔
330
                TLSClientConfig:       &tls.Config{InsecureSkipVerify: context.Insecure}, //nolint // This is user configurable and disabled by default
1✔
331
                ResponseHeaderTimeout: 60 * time.Second,
1✔
332
        }
1✔
333
        client := &http.Client{Transport: transport}
1✔
334
        if context.Debug {
1✔
335
                e.logRequest(logger, request)
×
336
        }
×
337
        response, err := e.send(client, request, requestError)
1✔
338
        if err != nil {
1✔
339
                return utils.Retryable(fmt.Errorf("Error sending request: %w", err))
×
340
        }
×
341
        defer response.Body.Close()
1✔
342
        downloadBar := utils.NewProgressBar(logger)
1✔
343
        downloadReader := e.progressReader("downloading...", "completing    ", response.Body, response.ContentLength, downloadBar)
1✔
344
        defer downloadBar.Remove()
1✔
345
        body, err := io.ReadAll(downloadReader)
1✔
346
        if err != nil {
1✔
347
                return utils.Retryable(fmt.Errorf("Error reading response body: %w", err))
×
348
        }
×
349
        e.logResponse(logger, response, body)
1✔
350
        if response.StatusCode >= 500 {
1✔
351
                return utils.Retryable(fmt.Errorf("Service returned status code '%v' and body '%v'", response.StatusCode, string(body)))
×
352
        }
×
353
        err = writer.WriteResponse(*output.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body)))
1✔
354
        if err != nil {
1✔
355
                return err
×
356
        }
×
357
        return nil
1✔
358
}
359

360
func NewHttpExecutor(authenticators []auth.Authenticator) *HttpExecutor {
39✔
361
        return &HttpExecutor{authenticators}
39✔
362
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc