Initial QSfera import
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package scanners
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dutchcoders/go-clamd"
|
||||
)
|
||||
|
||||
// NewClamAV returns a Scanner talking to clamAV via socket
|
||||
func NewClamAV(socket string, timeout time.Duration) (*ClamAV, error) {
|
||||
c := clamd.NewClamd(socket)
|
||||
|
||||
if err := c.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrScannerNotReachable, err)
|
||||
}
|
||||
|
||||
return &ClamAV{
|
||||
clamd: clamd.NewClamd(socket),
|
||||
timeout: timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClamAV is a Scanner based on clamav
|
||||
type ClamAV struct {
|
||||
clamd *clamd.Clamd
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Scan to fulfill Scanner interface
|
||||
func (s ClamAV) Scan(in Input) (Result, error) {
|
||||
abort := make(chan bool, 1)
|
||||
defer close(abort)
|
||||
|
||||
ch, err := s.clamd.ScanStream(in.Body, abort)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(s.timeout):
|
||||
abort <- true
|
||||
return Result{}, fmt.Errorf("%w: %s", ErrScanTimeout, in.Url)
|
||||
case s := <-ch:
|
||||
return Result{
|
||||
Infected: s.Status == clamd.RES_FOUND,
|
||||
Description: s.Description,
|
||||
ScanTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package scanners_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/qsfera/server/services/antivirus/pkg/scanners"
|
||||
)
|
||||
|
||||
func newUnixListener(t testing.TB, lc net.ListenConfig, v ...string) net.Listener {
|
||||
d, err := os.MkdirTemp("", "")
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.RemoveAll(d))
|
||||
})
|
||||
|
||||
nl, err := lc.Listen(context.Background(), "unix", filepath.Join(d, "sock"))
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
i := 0
|
||||
for {
|
||||
if len(v) == i {
|
||||
break
|
||||
}
|
||||
|
||||
conn, err := nl.Accept()
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
_, err = conn.Write([]byte(v[i]))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, conn.Close())
|
||||
i++
|
||||
}
|
||||
}()
|
||||
|
||||
return nl
|
||||
}
|
||||
|
||||
func TestNewClamAV(t *testing.T) {
|
||||
t.Run("returns a scanner", func(t *testing.T) {
|
||||
ul := newUnixListener(t, net.ListenConfig{}, "PONG\n")
|
||||
defer func() {
|
||||
assert.NoError(t, ul.Close())
|
||||
}()
|
||||
|
||||
done := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
_, err := scanners.NewClamAV(ul.Addr().String(), 10*time.Second)
|
||||
assert.NoError(t, err)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
assert.True(t, <-done)
|
||||
})
|
||||
|
||||
t.Run("fails if scanner is not pingable", func(t *testing.T) {
|
||||
_, err := scanners.NewClamAV("", 0)
|
||||
assert.ErrorIs(t, err, scanners.ErrScannerNotReachable)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewClamAV_Scan(t *testing.T) {
|
||||
t.Run("returns a result", func(t *testing.T) {
|
||||
ul := newUnixListener(t, net.ListenConfig{}, "PONG\n", "stream: Win.Test.EICAR_HDB-1 FOUND\n")
|
||||
defer func() {
|
||||
assert.NoError(t, ul.Close())
|
||||
}()
|
||||
|
||||
done := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
scanner, err := scanners.NewClamAV(ul.Addr().String(), 10*time.Second)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := scanner.Scan(scanners.Input{Body: strings.NewReader("DATA")})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, result.Description, "Win.Test.EICAR_HDB-1")
|
||||
assert.True(t, result.Infected)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
assert.True(t, <-done)
|
||||
})
|
||||
|
||||
t.Run("aborts after a certain time", func(t *testing.T) {
|
||||
ul := newUnixListener(t, net.ListenConfig{}, "PONG\n", "stream: Win.Test.EICAR_HDB-1 FOUND\n")
|
||||
defer func() {
|
||||
assert.NoError(t, ul.Close())
|
||||
}()
|
||||
|
||||
done := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
scanner, err := scanners.NewClamAV(ul.Addr().String(), 10*time.Second)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := scanner.Scan(scanners.Input{Body: strings.NewReader("DATA")})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, result.Description, "Win.Test.EICAR_HDB-1")
|
||||
assert.True(t, result.Infected)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
assert.True(t, <-done)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package scanners_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
ic "github.com/opencloud-eu/icap-client"
|
||||
|
||||
"github.com/qsfera/server/services/antivirus/pkg/scanners"
|
||||
"github.com/qsfera/server/services/antivirus/pkg/scanners/mocks"
|
||||
)
|
||||
|
||||
func TestICAP_Scan(t *testing.T) {
|
||||
var (
|
||||
earlyExitErr = errors.New("stop here")
|
||||
testUrl = "icap://test"
|
||||
client = mocks.NewScanner(t)
|
||||
scanner = &scanners.ICAP{Client: client, URL: testUrl}
|
||||
)
|
||||
|
||||
t.Run("it sends a OPTIONS request to determine details", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, ic.MethodOPTIONS, request.Method)
|
||||
assert.Equal(t, testUrl, request.URL.String())
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{})
|
||||
assert.ErrorIs(t, earlyExitErr, err) // we can exit early, just in case check the error to be identical to the early exit error
|
||||
})
|
||||
|
||||
t.Run("it sends a REQMOD request with all the details", func(t *testing.T) {
|
||||
|
||||
t.Run("request with ContentLength", func(t *testing.T) {
|
||||
t.Run("with size", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, ic.MethodREQMOD, request.Method)
|
||||
assert.Equal(t, testUrl, request.URL.String())
|
||||
assert.EqualValues(t, 999, request.HTTPRequest.ContentLength)
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Size: 999})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
|
||||
t.Run("without size", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, ic.MethodREQMOD, request.Method)
|
||||
assert.Equal(t, testUrl, request.URL.String())
|
||||
assert.EqualValues(t, 0, request.HTTPRequest.ContentLength)
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("request with Content-Type header", func(t *testing.T) {
|
||||
t.Run("name contains known extension", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, "application/pdf", request.HTTPRequest.Header.Get("Content-Type"))
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Name: "report.pdf"})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
|
||||
t.Run("name with unknown extension", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, "application/octet-stream", request.HTTPRequest.Header.Get("Content-Type"))
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Name: "report.unknown"})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
|
||||
t.Run("name without extension", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, "httpd/unix-directory", request.HTTPRequest.Header.Get("Content-Type"))
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Name: "report"})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
|
||||
t.Run("with PreviewBytes set", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, 444, request.PreviewBytes)
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
|
||||
t.Run("without PreviewBytes set", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, 0, request.PreviewBytes)
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
|
||||
t.Run("with PreviewBytes set", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
|
||||
|
||||
client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
|
||||
assert.Equal(t, 444, request.PreviewBytes)
|
||||
return ic.Response{}, earlyExitErr
|
||||
}).Once()
|
||||
|
||||
_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
|
||||
assert.ErrorIs(t, earlyExitErr, err)
|
||||
})
|
||||
|
||||
t.Run("it handles virus scan results", func(t *testing.T) {
|
||||
t.Run("no virus", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
|
||||
result, err := scanner.Scan(scanners.Input{})
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, result.Infected)
|
||||
})
|
||||
|
||||
// clamav returns an X-Infection-Found header with the threat description
|
||||
t.Run("X-Infection-Found header ", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{Header: http.Header{"X-Infection-Found": []string{"Threat=bad threat;"}}}, nil).Once()
|
||||
|
||||
result, err := scanner.Scan(scanners.Input{})
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, result.Infected)
|
||||
assert.Equal(t, "bad threat", result.Description)
|
||||
})
|
||||
|
||||
// skyhigh returns the information via the content response
|
||||
t.Run("X-Infection-Found header", func(t *testing.T) {
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusForbidden, Status: "some status"}}, nil).Once()
|
||||
|
||||
result, err := scanner.Scan(scanners.Input{})
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, result.Infected)
|
||||
assert.Equal(t, "some status", result.Description)
|
||||
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
|
||||
client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusOK}}, nil).Once()
|
||||
|
||||
result, err = scanner.Scan(scanners.Input{})
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, result.Infected)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/opencloud-eu/icap-client"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewScanner creates a new instance of Scanner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewScanner(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Scanner {
|
||||
mock := &Scanner{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Scanner is an autogenerated mock type for the Scanner type
|
||||
type Scanner struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Scanner_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Scanner) EXPECT() *Scanner_Expecter {
|
||||
return &Scanner_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Do provides a mock function for the type Scanner
|
||||
func (_mock *Scanner) Do(req icapclient.Request) (icapclient.Response, error) {
|
||||
ret := _mock.Called(req)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Do")
|
||||
}
|
||||
|
||||
var r0 icapclient.Response
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(icapclient.Request) (icapclient.Response, error)); ok {
|
||||
return returnFunc(req)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(icapclient.Request) icapclient.Response); ok {
|
||||
r0 = returnFunc(req)
|
||||
} else {
|
||||
r0 = ret.Get(0).(icapclient.Response)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(icapclient.Request) error); ok {
|
||||
r1 = returnFunc(req)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Scanner_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'
|
||||
type Scanner_Do_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Do is a helper method to define mock.On call
|
||||
// - req icapclient.Request
|
||||
func (_e *Scanner_Expecter) Do(req interface{}) *Scanner_Do_Call {
|
||||
return &Scanner_Do_Call{Call: _e.mock.On("Do", req)}
|
||||
}
|
||||
|
||||
func (_c *Scanner_Do_Call) Run(run func(req icapclient.Request)) *Scanner_Do_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 icapclient.Request
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(icapclient.Request)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Scanner_Do_Call) Return(response icapclient.Response, err error) *Scanner_Do_Call {
|
||||
_c.Call.Return(response, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Scanner_Do_Call) RunAndReturn(run func(req icapclient.Request) (icapclient.Response, error)) *Scanner_Do_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package scanners
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrScanTimeout is returned when a scan times out
|
||||
ErrScanTimeout = errors.New("time out waiting for clamav to respond while scanning")
|
||||
// ErrScannerNotReachable is returned when the scanner is not reachable
|
||||
ErrScannerNotReachable = errors.New("failed to reach the scanner")
|
||||
)
|
||||
|
||||
type (
|
||||
// The Result is the common scan result to all scanners
|
||||
Result struct {
|
||||
Infected bool
|
||||
ScanTime time.Time
|
||||
Description string
|
||||
}
|
||||
|
||||
// The Input is the common input to all scanners
|
||||
Input struct {
|
||||
Body io.Reader
|
||||
Size int64
|
||||
Url string
|
||||
Name string
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user