113 lines
2.4 KiB
Go
113 lines
2.4 KiB
Go
package scanners
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/opencloud-eu/reva/v2/pkg/mime"
|
|
|
|
ic "github.com/opencloud-eu/icap-client"
|
|
)
|
|
|
|
// Scanner is the interface that wraps the basic Do method
|
|
type Scanner interface {
|
|
Do(req ic.Request) (ic.Response, error)
|
|
}
|
|
|
|
// NewICAP returns a Scanner talking to an ICAP server
|
|
func NewICAP(icapURL string, icapService string, timeout time.Duration) (ICAP, error) {
|
|
endpoint, err := url.Parse(icapURL)
|
|
if err != nil {
|
|
return ICAP{}, err
|
|
}
|
|
|
|
endpoint.Scheme = "icap"
|
|
endpoint.Path = icapService
|
|
|
|
client, err := ic.NewClient(
|
|
ic.WithICAPConnectionTimeout(timeout),
|
|
)
|
|
if err != nil {
|
|
return ICAP{}, err
|
|
}
|
|
|
|
return ICAP{Client: &client, URL: endpoint.String()}, nil
|
|
}
|
|
|
|
// ICAP is responsible for scanning files using an ICAP server
|
|
type ICAP struct {
|
|
Client Scanner
|
|
URL string
|
|
}
|
|
|
|
// Scan scans a file using the ICAP server
|
|
func (s ICAP) Scan(in Input) (Result, error) {
|
|
ctx := context.TODO()
|
|
result := Result{}
|
|
|
|
optReq, err := ic.NewRequest(ctx, ic.MethodOPTIONS, s.URL, nil, nil)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
optRes, err := s.Client.Do(optReq)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
httpReq, err := http.NewRequest(http.MethodPost, in.Url, in.Body)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
httpReq.ContentLength = in.Size
|
|
if mt := mime.Detect(path.Ext(in.Name) == "", in.Name); mt != "" {
|
|
httpReq.Header.Set("Content-Type", mt)
|
|
}
|
|
|
|
req, err := ic.NewRequest(ctx, ic.MethodREQMOD, s.URL, httpReq, nil)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
if optRes.PreviewBytes > 0 {
|
|
err = req.SetPreview(optRes.PreviewBytes)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
}
|
|
|
|
res, err := s.Client.Do(req)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
result.ScanTime = time.Now()
|
|
|
|
// TODO: make header configurable
|
|
if data, infected := res.Header["X-Infection-Found"]; infected {
|
|
result.Infected = infected
|
|
|
|
match := regexp.MustCompile(`Threat=(.*);`).FindStringSubmatch(fmt.Sprint(data))
|
|
|
|
if len(match) > 1 {
|
|
result.Description = match[1]
|
|
}
|
|
}
|
|
|
|
if result.Infected || res.ContentResponse == nil {
|
|
return result, nil
|
|
}
|
|
|
|
// mcafee forwards the scan result as HTML in the content response;
|
|
// status 403 indicates that the file is infected
|
|
result.Infected = res.ContentResponse.StatusCode == http.StatusForbidden
|
|
result.Description = res.ContentResponse.Status
|
|
|
|
return result, nil
|
|
}
|