github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/malwarescan/malwarescan.go (about)

     1  package malwarescan
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  
     9  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // ScanResult : Returned by the scan endpoint of the malwarescan api of SAP CP
    14  type ScanResult struct {
    15  	MalwareDetected          bool   `json:"malwareDetected"`
    16  	EncryptedContentDetected bool   `json:"encryptedContentDetected"`
    17  	ScanSize                 int    `json:"scanSize"`
    18  	Finding                  string `json:"finding,omitempty"`
    19  	MimeType                 string `json:"mimeType"`
    20  	SHA256                   string `json:"SHA256"`
    21  }
    22  
    23  // Info : Returned by the info endpoint of the malwarescan api of SAP CP
    24  type Info struct {
    25  	MaxScanSize        int
    26  	SignatureTimestamp string
    27  	EngineVersion      string
    28  }
    29  
    30  // ScanError : Returned by the malwarescan api of SAP CP in case of an error
    31  type ScanError struct {
    32  	Message string
    33  }
    34  
    35  // Client : Interface for the malwarescan api provided by SAP CP (see https://api.sap.com/api/MalwareScanAPI/overview)
    36  type Client interface {
    37  	Scan(candidate io.Reader) (*ScanResult, error)
    38  	Info() (*Info, error)
    39  }
    40  
    41  // ClientImpl : Client implementation of the malwarescan api provided by SAP CP (see https://api.sap.com/api/MalwareScanAPI/overview)
    42  type ClientImpl struct {
    43  	HTTPClient piperhttp.Sender
    44  	Host       string
    45  }
    46  
    47  // Scan : Triggers a malwarescan in SAP CP for the given content.
    48  func (c *ClientImpl) Scan(candidate io.Reader) (*ScanResult, error) {
    49  	var scanResult ScanResult
    50  
    51  	headers := http.Header{}
    52  	headers.Add("Content-Type", "application/octet-stream")
    53  
    54  	err := c.sendAPIRequest("POST", "/scan", candidate, headers, &scanResult)
    55  
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &scanResult, nil
    61  
    62  }
    63  
    64  // Info : Returns some information about the scanengine used by the malwarescan service.
    65  func (c *ClientImpl) Info() (*Info, error) {
    66  	var info Info
    67  
    68  	err := c.sendAPIRequest("GET", "/info", nil, nil, &info)
    69  
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return &info, nil
    75  }
    76  
    77  func (c *ClientImpl) sendAPIRequest(method, endpoint string, body io.Reader, header http.Header, obj interface{}) error {
    78  	// piper http utils mashall some http response codes into errors. We wan't to check the status code
    79  	// ourselves hence we wait with returning that error (maybe also related to errors others than http status codes)
    80  
    81  	// sendRequest results in any combination of nil and non-nil response and error.
    82  	// a response body could even be already closed.
    83  	response, err := c.HTTPClient.SendRequest(method, c.Host+endpoint, body, header, nil)
    84  	if err != nil {
    85  		return errors.Wrap(err, fmt.Sprintf("Failed to send request to MalwareService."))
    86  	}
    87  
    88  	if response.StatusCode != 200 {
    89  		var scanError ScanError
    90  
    91  		err = c.unmarshalResponse(response, &scanError)
    92  
    93  		if err != nil {
    94  			return fmt.Errorf("MalwareService returned with status code %d, no further information available", response.StatusCode)
    95  		}
    96  
    97  		return fmt.Errorf("MalwareService returned with status code %d: %s", response.StatusCode, scanError.Message)
    98  	}
    99  
   100  	return c.unmarshalResponse(response, obj)
   101  }
   102  
   103  func (c *ClientImpl) readBody(response *http.Response) ([]byte, error) {
   104  	if response != nil && response.Body != nil {
   105  		defer response.Body.Close()
   106  		return io.ReadAll(response.Body)
   107  	}
   108  
   109  	return nil, fmt.Errorf("No response body available")
   110  }
   111  
   112  func (c *ClientImpl) unmarshalResponse(response *http.Response, obj interface{}) error {
   113  	body, err := c.readBody(response)
   114  
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	err = json.Unmarshal(body, obj)
   120  
   121  	if err != nil {
   122  		return errors.Wrap(err, fmt.Sprintf("Unmarshalling of response body failed. Body: '%s'", body))
   123  	}
   124  
   125  	return err
   126  }