package malwarescan import ( "encoding/json" "fmt" piperhttp "github.com/SAP/jenkins-library/pkg/http" "github.com/pkg/errors" "io" "io/ioutil" "net/http" ) // ScanResult : Returned by the scan endpoint of the malwarescan api of SAP CP type ScanResult struct { MalwareDetected bool `json:"malwareDetected"` EncryptedContentDetected bool `json:"encryptedContentDetected"` ScanSize int `json:"scanSize"` Finding string `json:"finding,omitempty"` MimeType string `json:"mimeType"` SHA256 string `json:"SHA256"` } // Info : Returned by the info endpoint of the malwarescan api of SAP CP type Info struct { MaxScanSize int SignatureTimestamp string EngineVersion string } // ScanError : Returned by the malwarescan api of SAP CP in case of an error type ScanError struct { Message string } // Client : Interface for the malwarescan api provided by SAP CP (see https://api.sap.com/api/MalwareScanAPI/overview) type Client interface { Scan(candidate io.Reader) (*ScanResult, error) Info() (*Info, error) } // ClientImpl : Client implementation of the malwarescan api provided by SAP CP (see https://api.sap.com/api/MalwareScanAPI/overview) type ClientImpl struct { HTTPClient piperhttp.Sender Host string } // Scan : Triggers a malwarescan in SAP CP for the given content. func (c *ClientImpl) Scan(candidate io.Reader) (*ScanResult, error) { var scanResult ScanResult headers := http.Header{} headers.Add("Content-Type", "application/octet-stream") err := c.sendAPIRequest("POST", "/scan", candidate, headers, &scanResult) if err != nil { return nil, err } return &scanResult, nil } // Info : Returns some information about the scanengine used by the malwarescan service. func (c *ClientImpl) Info() (*Info, error) { var info Info err := c.sendAPIRequest("GET", "/info", nil, nil, &info) if err != nil { return nil, err } return &info, nil } func (c *ClientImpl) sendAPIRequest(method, endpoint string, body io.Reader, header http.Header, obj interface{}) error { // piper http utils mashall some http response codes into errors. We wan't to check the status code // ourselves hence we wait with returning that error (maybe also related to errors others than http status codes) // sendRequest results in any combination of nil and non-nil response and error. // a response body could even be already closed. response, err := c.HTTPClient.SendRequest(method, c.Host+endpoint, body, header, nil) if response.StatusCode != 200 { var scanError ScanError err = c.unmarshalResponse(response, &scanError) if err != nil { return fmt.Errorf("MalwareService returned with status code %d, no further information available", response.StatusCode) } return fmt.Errorf("MalwareService returned with status code %d: %s", response.StatusCode, scanError.Message) } return c.unmarshalResponse(response, obj) } func (c *ClientImpl) readBody(response *http.Response) ([]byte, error) { if response != nil && response.Body != nil { defer response.Body.Close() return ioutil.ReadAll(response.Body) } return nil, fmt.Errorf("No response body available") } func (c *ClientImpl) unmarshalResponse(response *http.Response, obj interface{}) error { body, err := c.readBody(response) if err != nil { return err } err = json.Unmarshal(body, obj) if err != nil { return errors.Wrap(err, fmt.Sprintf("Unmarshalling of response body failed. Body: '%s'", body)) } return err }