github.com/projectdiscovery/nuclei/v2@v2.9.15/internal/runner/nucleicloud/cloud.go (about) 1 package nucleicloud 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "mime/multipart" 10 "net/http" 11 "net/url" 12 "os" 13 "path/filepath" 14 "strconv" 15 "strings" 16 "time" 17 18 jsoniter "github.com/json-iterator/go" 19 "github.com/pkg/errors" 20 "github.com/projectdiscovery/nuclei/v2/pkg/output" 21 "github.com/projectdiscovery/retryablehttp-go" 22 ) 23 24 // Client is a client for result retrieval from nuclei-cloud API 25 type Client struct { 26 baseURL string 27 apiKey string 28 httpclient *retryablehttp.Client 29 } 30 31 const ( 32 pollInterval = 3 * time.Second 33 resultSize = 100 34 defaultBaseURL = "https://cloud-dev.nuclei.sh" 35 ) 36 37 // HTTPErrorRetryPolicy is to retry for HTTPCodes >= 500. 38 func HTTPErrorRetryPolicy() func(ctx context.Context, resp *http.Response, err error) (bool, error) { 39 return func(ctx context.Context, resp *http.Response, err error) (bool, error) { 40 if resp != nil && resp.StatusCode >= http.StatusInternalServerError { 41 return true, errors.New(resp.Status) 42 } 43 return retryablehttp.CheckRecoverableErrors(ctx, resp, err) 44 } 45 } 46 47 // New returns a nuclei-cloud API client 48 func New(baseURL, apiKey string) *Client { 49 options := retryablehttp.DefaultOptionsSingle 50 options.NoAdjustTimeout = true 51 options.Timeout = 60 * time.Second 52 options.CheckRetry = HTTPErrorRetryPolicy() 53 client := retryablehttp.NewClient(options) 54 55 baseAppURL := baseURL 56 if baseAppURL == "" { 57 baseAppURL = defaultBaseURL 58 } 59 return &Client{httpclient: client, baseURL: baseAppURL, apiKey: apiKey} 60 } 61 62 // AddScan adds a scan for templates and target to nuclei server 63 func (c *Client) AddScan(req *AddScanRequest) (int64, error) { 64 var buf bytes.Buffer 65 if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil { 66 return 0, errors.Wrap(err, "could not encode request") 67 } 68 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/scan", c.baseURL), bytes.NewReader(buf.Bytes())) 69 if err != nil { 70 return 0, errors.Wrap(err, "could not make request") 71 } 72 73 resp, err := c.sendRequest(httpReq) 74 if err != nil { 75 return 0, errors.Wrap(err, "could not do request") 76 } 77 defer resp.Body.Close() 78 79 var data map[string]int64 80 if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil { 81 return 0, errors.Wrap(err, "could not decode resp") 82 } 83 id := data["id"] 84 return id, nil 85 } 86 87 // GetResults gets results from nuclei server for an ID 88 // until there are no more results left to retrieve. 89 func (c *Client) GetResults(ID int64, checkProgress bool, limit int, callback func(*output.ResultEvent)) error { 90 lastID := int64(0) 91 92 for { 93 uri := fmt.Sprintf("%s/results?id=%d&from=%d&size=%d", c.baseURL, ID, lastID, limit) 94 httpReq, err := retryablehttp.NewRequest(http.MethodGet, uri, nil) 95 if err != nil { 96 return errors.Wrap(err, "could not make request") 97 } 98 99 resp, err := c.sendRequest(httpReq) 100 if err != nil { 101 return errors.Wrap(err, "could not do request") 102 } 103 104 var items GetResultsResponse 105 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 106 resp.Body.Close() 107 return errors.Wrap(err, "could not decode results") 108 } 109 resp.Body.Close() 110 111 for _, item := range items.Items { 112 lastID = item.ID 113 114 var result output.ResultEvent 115 if err := jsoniter.NewDecoder(strings.NewReader(item.Raw)).Decode(&result); err != nil { 116 return errors.Wrap(err, "could not decode result item") 117 } 118 callback(&result) 119 } 120 121 // This is checked during scan is added else if no item found break out of loop. 122 if checkProgress { 123 if items.Finished && len(items.Items) == 0 { 124 break 125 } 126 } else if len(items.Items) == 0 { 127 break 128 } 129 130 time.Sleep(pollInterval) 131 } 132 return nil 133 } 134 135 func (c *Client) GetScans(limit int, from string) ([]GetScanRequest, error) { 136 var items []GetScanRequest 137 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/scan?from=%s&size=%d", c.baseURL, url.QueryEscape(from), limit), nil) 138 if err != nil { 139 return items, errors.Wrap(err, "could not make request") 140 } 141 142 resp, err := c.sendRequest(httpReq) 143 if err != nil { 144 return nil, errors.Wrap(err, "could not do request") 145 } 146 defer resp.Body.Close() 147 148 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 149 return items, errors.Wrap(err, "could not decode results") 150 } 151 return items, nil 152 } 153 154 func (c *Client) GetScan(id int64) (GetScanRequest, error) { 155 var items GetScanRequest 156 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/scan/%d", c.baseURL, id), nil) 157 if err != nil { 158 return items, errors.Wrap(err, "could not make request") 159 } 160 161 resp, err := c.sendRequest(httpReq) 162 if err != nil { 163 return items, errors.Wrap(err, "could not do request") 164 } 165 defer resp.Body.Close() 166 167 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 168 return items, errors.Wrap(err, "could not decode results") 169 } 170 return items, nil 171 } 172 173 // Delete a scan and it's issues by the scan id. 174 func (c *Client) DeleteScan(id int64) (DeleteScanResults, error) { 175 deletescan := DeleteScanResults{} 176 httpReq, err := retryablehttp.NewRequest(http.MethodDelete, fmt.Sprintf("%s/scan?id=%d", c.baseURL, id), nil) 177 if err != nil { 178 return deletescan, errors.Wrap(err, "could not make request") 179 } 180 181 resp, err := c.sendRequest(httpReq) 182 if err != nil { 183 return deletescan, errors.Wrap(err, "could not do request") 184 } 185 defer resp.Body.Close() 186 187 if err := jsoniter.NewDecoder(resp.Body).Decode(&deletescan); err != nil { 188 return deletescan, errors.Wrap(err, "could not delete scan") 189 } 190 return deletescan, nil 191 } 192 193 // StatusDataSource returns the status for a data source 194 func (c *Client) StatusDataSource(statusRequest StatusDataSourceRequest) (int64, error) { 195 var buf bytes.Buffer 196 if err := jsoniter.NewEncoder(&buf).Encode(statusRequest); err != nil { 197 return 0, errors.Wrap(err, "could not encode request") 198 } 199 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources/status", c.baseURL), bytes.NewReader(buf.Bytes())) 200 if err != nil { 201 return 0, errors.Wrap(err, "could not make request") 202 } 203 204 resp, err := c.sendRequest(httpReq) 205 if err != nil { 206 return 0, errors.Wrap(err, "could not do request") 207 } 208 defer resp.Body.Close() 209 210 var data StatusDataSourceResponse 211 if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil { 212 return 0, errors.Wrap(err, "could not decode resp") 213 } 214 return data.ID, nil 215 } 216 217 // AddDataSource adds a new data source 218 func (c *Client) AddDataSource(req AddDataSourceRequest) (*AddDataSourceResponse, error) { 219 var buf bytes.Buffer 220 if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil { 221 return nil, errors.Wrap(err, "could not encode request") 222 } 223 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources", c.baseURL), bytes.NewReader(buf.Bytes())) 224 if err != nil { 225 return nil, errors.Wrap(err, "could not make request") 226 } 227 resp, err := c.sendRequest(httpReq) 228 if err != nil { 229 return nil, errors.Wrap(err, "could not do request") 230 } 231 defer resp.Body.Close() 232 233 var data AddDataSourceResponse 234 if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil { 235 return nil, errors.Wrap(err, "could not decode resp") 236 } 237 return &data, nil 238 } 239 240 // SyncDataSource syncs contents for a data source. The call blocks until 241 // update is completed. 242 func (c *Client) SyncDataSource(ID int64) error { 243 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/datasources/%d/sync", c.baseURL, ID), nil) 244 if err != nil { 245 return errors.Wrap(err, "could not make request") 246 } 247 248 resp, err := c.sendRequest(httpReq) 249 if err != nil { 250 return errors.Wrap(err, "could not do request") 251 } 252 defer resp.Body.Close() 253 _, _ = io.Copy(io.Discard, resp.Body) 254 return nil 255 } 256 257 // ExistsDataSourceItem identifies whether data source item exist 258 func (c *Client) ExistsDataSourceItem(req ExistsDataSourceItemRequest) error { 259 var buf bytes.Buffer 260 if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil { 261 return errors.Wrap(err, "could not encode request") 262 } 263 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources/exists", c.baseURL), bytes.NewReader(buf.Bytes())) 264 if err != nil { 265 return errors.Wrap(err, "could not make request") 266 } 267 resp, err := c.sendRequest(httpReq) 268 if err != nil { 269 return errors.Wrap(err, "could not do request") 270 } 271 defer resp.Body.Close() 272 _, _ = io.Copy(io.Discard, resp.Body) 273 return nil 274 } 275 276 func (c *Client) ListDatasources() ([]GetDataSourceResponse, error) { 277 var items []GetDataSourceResponse 278 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/datasources", c.baseURL), nil) 279 if err != nil { 280 return items, errors.Wrap(err, "could not make request") 281 } 282 283 resp, err := c.sendRequest(httpReq) 284 if err != nil { 285 return nil, errors.Wrap(err, "could not do request") 286 } 287 defer resp.Body.Close() 288 289 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 290 return items, errors.Wrap(err, "could not decode results") 291 } 292 return items, nil 293 } 294 295 func (c *Client) ListReportingSources() ([]GetReportingSourceResponse, error) { 296 var items []GetReportingSourceResponse 297 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/reporting", c.baseURL), nil) 298 if err != nil { 299 return items, errors.Wrap(err, "could not make request") 300 } 301 302 resp, err := c.sendRequest(httpReq) 303 if err != nil { 304 return nil, errors.Wrap(err, "could not do request") 305 } 306 defer resp.Body.Close() 307 308 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 309 return items, errors.Wrap(err, "could not decode results") 310 } 311 return items, nil 312 } 313 314 func (c *Client) ToggleReportingSource(ID int64, status bool) error { 315 r := ReportingSourceStatus{Enabled: status} 316 317 var buf bytes.Buffer 318 if err := jsoniter.NewEncoder(&buf).Encode(r); err != nil { 319 return errors.Wrap(err, "could not encode request") 320 } 321 httpReq, err := retryablehttp.NewRequest(http.MethodPut, fmt.Sprintf("%s/reporting/%d", c.baseURL, ID), bytes.NewReader(buf.Bytes())) 322 if err != nil { 323 return errors.Wrap(err, "could not make request") 324 } 325 326 resp, err := c.sendRequest(httpReq) 327 if err != nil { 328 return errors.Wrap(err, "could not do request") 329 } 330 defer resp.Body.Close() 331 _, _ = io.Copy(io.Discard, resp.Body) 332 return nil 333 } 334 335 func (c *Client) ListTargets(query string) ([]GetTargetResponse, error) { 336 var builder strings.Builder 337 _, _ = builder.WriteString(c.baseURL) 338 _, _ = builder.WriteString("/targets") 339 if query != "" { 340 _, _ = builder.WriteString("?query=") 341 _, _ = builder.WriteString(url.QueryEscape(query)) 342 } 343 344 var items []GetTargetResponse 345 httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil) 346 if err != nil { 347 return items, errors.Wrap(err, "could not make request") 348 } 349 350 resp, err := c.sendRequest(httpReq) 351 if err != nil { 352 return nil, errors.Wrap(err, "could not do request") 353 } 354 defer resp.Body.Close() 355 356 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 357 return items, errors.Wrap(err, "could not decode results") 358 } 359 return items, nil 360 } 361 362 func (c *Client) ListTemplates(query string) ([]GetTemplatesResponse, error) { 363 var builder strings.Builder 364 _, _ = builder.WriteString(c.baseURL) 365 _, _ = builder.WriteString("/templates") 366 if query != "" { 367 _, _ = builder.WriteString("?query=") 368 _, _ = builder.WriteString(url.QueryEscape(query)) 369 } 370 371 var items []GetTemplatesResponse 372 httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil) 373 if err != nil { 374 return items, errors.Wrap(err, "could not make request") 375 } 376 377 resp, err := c.sendRequest(httpReq) 378 if err != nil { 379 return nil, errors.Wrap(err, "could not do request") 380 } 381 defer resp.Body.Close() 382 383 if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil { 384 return items, errors.Wrap(err, "could not decode results") 385 } 386 return items, nil 387 } 388 389 func (c *Client) RemoveDatasource(datasource int64, name string) error { 390 var builder strings.Builder 391 _, _ = builder.WriteString(c.baseURL) 392 _, _ = builder.WriteString("/datasources") 393 394 if name != "" { 395 _, _ = builder.WriteString("?name=") 396 _, _ = builder.WriteString(name) 397 } else if datasource != 0 { 398 _, _ = builder.WriteString("?id=") 399 _, _ = builder.WriteString(strconv.FormatInt(datasource, 10)) 400 } 401 402 httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil) 403 if err != nil { 404 return errors.Wrap(err, "could not make request") 405 } 406 407 resp, err := c.sendRequest(httpReq) 408 if err != nil { 409 return errors.Wrap(err, "could not do request") 410 } 411 defer resp.Body.Close() 412 _, _ = io.Copy(io.Discard, resp.Body) 413 return nil 414 } 415 416 func (c *Client) AddTemplate(name, contents string) (string, error) { 417 file, err := os.Open(contents) 418 if err != nil { 419 return "", errors.Wrap(err, "could not open contents") 420 } 421 defer file.Close() 422 423 var buf bytes.Buffer 424 writer := multipart.NewWriter(&buf) 425 _ = writer.WriteField("name", name) 426 fileWriter, _ := writer.CreateFormFile("file", filepath.Base(contents)) 427 _, _ = io.Copy(fileWriter, file) 428 _ = writer.Close() 429 430 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/templates", c.baseURL), &buf) 431 if err != nil { 432 return "", errors.Wrap(err, "could not make request") 433 } 434 httpReq.Header.Set("Content-Type", writer.FormDataContentType()) 435 436 resp, err := c.sendRequest(httpReq) 437 if err != nil { 438 return "", errors.Wrap(err, "could not do request") 439 } 440 defer resp.Body.Close() 441 442 var item AddItemResponse 443 if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil { 444 return "", errors.Wrap(err, "could not decode results") 445 } 446 return item.Ok, nil 447 } 448 449 func (c *Client) AddTarget(name, contents string) (string, error) { 450 file, err := os.Open(contents) 451 if err != nil { 452 return "", errors.Wrap(err, "could not open contents") 453 } 454 defer file.Close() 455 456 var buf bytes.Buffer 457 writer := multipart.NewWriter(&buf) 458 _ = writer.WriteField("name", name) 459 fileWriter, _ := writer.CreateFormFile("file", filepath.Base(contents)) 460 _, _ = io.Copy(fileWriter, file) 461 _ = writer.Close() 462 463 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/targets", c.baseURL), &buf) 464 if err != nil { 465 return "", errors.Wrap(err, "could not make request") 466 } 467 httpReq.Header.Set("Content-Type", writer.FormDataContentType()) 468 469 resp, err := c.sendRequest(httpReq) 470 if err != nil { 471 return "", errors.Wrap(err, "could not do request") 472 } 473 defer resp.Body.Close() 474 475 var item AddItemResponse 476 if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil { 477 return "", errors.Wrap(err, "could not decode results") 478 } 479 return item.Ok, nil 480 } 481 482 func (c *Client) RemoveTemplate(ID int64, name string) error { 483 var builder strings.Builder 484 _, _ = builder.WriteString(c.baseURL) 485 _, _ = builder.WriteString("/templates") 486 487 if name != "" { 488 _, _ = builder.WriteString("?name=") 489 _, _ = builder.WriteString(name) 490 } else if ID != 0 { 491 _, _ = builder.WriteString("?id=") 492 _, _ = builder.WriteString(strconv.FormatInt(ID, 10)) 493 } 494 httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil) 495 if err != nil { 496 return errors.Wrap(err, "could not make request") 497 } 498 499 resp, err := c.sendRequest(httpReq) 500 if err != nil { 501 return errors.Wrap(err, "could not do request") 502 } 503 defer resp.Body.Close() 504 _, _ = io.Copy(io.Discard, resp.Body) 505 return nil 506 } 507 508 func (c *Client) RemoveTarget(ID int64, name string) error { 509 var builder strings.Builder 510 _, _ = builder.WriteString(c.baseURL) 511 _, _ = builder.WriteString("/targets") 512 513 if name != "" { 514 _, _ = builder.WriteString("?name=") 515 _, _ = builder.WriteString(name) 516 } else if ID != 0 { 517 _, _ = builder.WriteString("?id=") 518 _, _ = builder.WriteString(strconv.FormatInt(ID, 10)) 519 } 520 httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil) 521 if err != nil { 522 return errors.Wrap(err, "could not make request") 523 } 524 525 resp, err := c.sendRequest(httpReq) 526 if err != nil { 527 return errors.Wrap(err, "could not do request") 528 } 529 defer resp.Body.Close() 530 _, _ = io.Copy(io.Discard, resp.Body) 531 return nil 532 } 533 534 func (c *Client) GetTarget(ID int64, name string) (io.ReadCloser, error) { 535 var builder strings.Builder 536 _, _ = builder.WriteString(c.baseURL) 537 _, _ = builder.WriteString("/targets/get") 538 539 if name != "" { 540 _, _ = builder.WriteString("?name=") 541 _, _ = builder.WriteString(name) 542 } else if ID != 0 { 543 _, _ = builder.WriteString("?id=") 544 _, _ = builder.WriteString(strconv.FormatInt(ID, 10)) 545 } 546 httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil) 547 if err != nil { 548 return nil, errors.Wrap(err, "could not make request") 549 } 550 551 resp, err := c.sendRequest(httpReq) 552 if err != nil { 553 return nil, errors.Wrap(err, "could not do request") 554 } 555 return resp.Body, nil 556 } 557 558 func (c *Client) GetTemplate(ID int64, name string) (io.ReadCloser, error) { 559 var builder strings.Builder 560 _, _ = builder.WriteString(c.baseURL) 561 _, _ = builder.WriteString("/templates/get") 562 563 if name != "" { 564 _, _ = builder.WriteString("?name=") 565 _, _ = builder.WriteString(name) 566 } else if ID != 0 { 567 _, _ = builder.WriteString("?id=") 568 _, _ = builder.WriteString(strconv.FormatInt(ID, 10)) 569 } 570 httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil) 571 if err != nil { 572 return nil, errors.Wrap(err, "could not make request") 573 } 574 575 resp, err := c.sendRequest(httpReq) 576 if err != nil { 577 return nil, errors.Wrap(err, "could not do request") 578 } 579 return resp.Body, nil 580 } 581 582 func (c *Client) ExistsTarget(id int64) (ExistsInputResponse, error) { 583 var item ExistsInputResponse 584 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/targets/%d/exists", c.baseURL, id), nil) 585 if err != nil { 586 return item, errors.Wrap(err, "could not make request") 587 } 588 589 resp, err := c.sendRequest(httpReq) 590 if err != nil { 591 return item, errors.Wrap(err, "could not do request") 592 } 593 defer resp.Body.Close() 594 595 if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil { 596 return item, errors.Wrap(err, "could not decode results") 597 } 598 return item, nil 599 } 600 601 func (c *Client) ExistsTemplate(id int64) (ExistsInputResponse, error) { 602 var item ExistsInputResponse 603 httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/templates/%d/exists", c.baseURL, id), nil) 604 if err != nil { 605 return item, errors.Wrap(err, "could not make request") 606 } 607 608 resp, err := c.sendRequest(httpReq) 609 if err != nil { 610 return item, errors.Wrap(err, "could not do request") 611 } 612 defer resp.Body.Close() 613 614 if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil { 615 return item, errors.Wrap(err, "could not decode results") 616 } 617 return item, nil 618 } 619 620 const apiKeyParameter = "X-API-Key" 621 622 type errorResponse struct { 623 Message string `json:"message"` 624 } 625 626 func (c *Client) sendRequest(req *retryablehttp.Request) (*http.Response, error) { 627 req.Header.Set(apiKeyParameter, c.apiKey) 628 629 resp, err := c.httpclient.Do(req) 630 if err != nil { 631 return nil, errors.Wrap(err, "could not do request") 632 } 633 if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { 634 data, _ := io.ReadAll(resp.Body) 635 resp.Body.Close() 636 var errRes errorResponse 637 if err = json.NewDecoder(bytes.NewReader(data)).Decode(&errRes); err == nil { 638 return nil, errors.New(errRes.Message) 639 } 640 return nil, fmt.Errorf("unknown error, status code: %d=%s", resp.StatusCode, string(data)) 641 } 642 return resp, nil 643 } 644 645 // AddReportingSource adds a new data source 646 func (c *Client) AddReportingSource(req AddReportingSourceRequest) (*AddReportingSourceResponse, error) { 647 var buf bytes.Buffer 648 if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil { 649 return nil, errors.Wrap(err, "could not encode request") 650 } 651 httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/reporting/add-source", c.baseURL), bytes.NewReader(buf.Bytes())) 652 if err != nil { 653 return nil, errors.Wrap(err, "could not make request") 654 } 655 resp, err := c.sendRequest(httpReq) 656 if err != nil { 657 return nil, errors.Wrap(err, "could not do request") 658 } 659 defer resp.Body.Close() 660 661 var data AddReportingSourceResponse 662 if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil { 663 return nil, errors.Wrap(err, "could not decode resp") 664 } 665 return &data, nil 666 }