diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 6f8f182e..36aba8d1 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -614,7 +614,6 @@ jobs: go-version: '1.24' - uses: actions/checkout@v4 - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: - args: --timeout=10m version: latest diff --git a/.golangci.yml b/.golangci.yml index ac545673..15fce082 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,52 +1,54 @@ +version: "2" run: - timeout: 10m issues-exit-code: 1 tests: true - - -linters-settings: - dupl: - threshold: 150 - errcheck: - check-type-assertions: false - check-blank: false - goconst: - min-len: 3 - min-occurrences: 3 - gocyclo: - min-complexity: 15 - gofmt: - simplify: true - goimports: - local-prefixes: github.com/drakkan/sftpgo - #govet: - # report about shadowed variables - #check-shadowing: true - #enable: - # - fieldalignment - -issues: - include: - - EXC0002 - - EXC0012 - - EXC0013 - - EXC0014 - - EXC0015 - linters: enable: - - goconst - - errcheck - - gofmt - - goimports - - revive - - unconvert - - unparam - bodyclose + - dogsled + - dupl + - goconst - gocyclo - misspell - - whitespace - - dupl + - revive - rowserrcheck - - dogsled - - govet + - unconvert + - unparam + - whitespace + settings: + dupl: + threshold: 150 + errcheck: + check-type-assertions: false + check-blank: false + goconst: + min-len: 3 + min-occurrences: 3 + gocyclo: + min-complexity: 15 + exclusions: + generated: lax + presets: + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: true + goimports: + local-prefixes: + - github.com/drakkan/sftpgo + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/internal/cmd/initprovider.go b/internal/cmd/initprovider.go index c61ad228..f5620a5e 100644 --- a/internal/cmd/initprovider.go +++ b/internal/cmd/initprovider.go @@ -78,11 +78,12 @@ Please take a look at the usage below to customize the options.`, providerConf.Actions.ExecuteOn = nil logger.InfoToConsole("Initializing provider: %q config file: %q", providerConf.Driver, viper.ConfigFileUsed()) err = dataprovider.InitializeDatabase(providerConf, configDir) - if err == nil { + switch err { + case nil: logger.InfoToConsole("Data provider successfully initialized/updated") - } else if err == dataprovider.ErrNoInitRequired { + case dataprovider.ErrNoInitRequired: logger.InfoToConsole("%v", err.Error()) - } else { + default: logger.ErrorToConsole("Unable to initialize/update the data provider: %v", err) os.Exit(1) } diff --git a/internal/common/common_test.go b/internal/common/common_test.go index 8a14af7b..cc85af0d 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -64,7 +64,7 @@ func (c *fakeConnection) AddUser(user dataprovider.User) error { if err != nil { return err } - c.BaseConnection.User = user + c.User = user return nil } @@ -999,9 +999,10 @@ func TestConnectionStatus(t *testing.T) { assert.Len(t, stats, 3) for _, stat := range stats { assert.Equal(t, stat.Username, username) - if stat.ConnectionID == "SFTP_id1" { + switch stat.ConnectionID { + case "SFTP_id1": assert.Len(t, stat.Transfers, 2) - } else if stat.ConnectionID == "DAV_id3" { + case "DAV_id3": assert.Len(t, stat.Transfers, 1) } } diff --git a/internal/common/connection.go b/internal/common/connection.go index da453e1e..4358779a 100644 --- a/internal/common/connection.go +++ b/internal/common/connection.go @@ -771,10 +771,7 @@ func (c *BaseConnection) Copy(virtualSourcePath, virtualTargetPath string) error return err } } - createTargetDir := true - if dstInfo != nil && dstInfo.IsDir() { - createTargetDir = false - } + createTargetDir := dstInfo == nil || !dstInfo.IsDir() if err := c.checkCopy(srcInfo, dstInfo, virtualSourcePath, destPath); err != nil { return err } diff --git a/internal/common/defenderdb.go b/internal/common/defenderdb.go index 3f0a4849..63995862 100644 --- a/internal/common/defenderdb.go +++ b/internal/common/defenderdb.go @@ -62,7 +62,7 @@ func (d *dbDefender) GetHost(ip string) (dataprovider.DefenderEntry, error) { // and increase ban time if the IP is found. // This method must be called as soon as the client connects func (d *dbDefender) IsBanned(ip, protocol string) bool { - if d.baseDefender.isBanned(ip, protocol) { + if d.isBanned(ip, protocol) { return true } @@ -95,15 +95,15 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool { return true } - score := d.baseDefender.getScore(event) + score := d.getScore(event) host, err := dataprovider.AddDefenderEvent(ip, score, d.getStartObservationTime()) if err != nil { return false } - d.baseDefender.logEvent(ip, protocol, event, host.Score) + d.logEvent(ip, protocol, event, host.Score) if host.Score > d.config.Threshold { - d.baseDefender.logBan(ip, protocol) + d.logBan(ip, protocol) banTime := time.Now().Add(time.Duration(d.config.BanTime) * time.Minute) err = dataprovider.SetDefenderBanTime(ip, util.GetTimeAsMsSinceEpoch(banTime)) if err == nil { diff --git a/internal/common/defendermem.go b/internal/common/defendermem.go index b5642c4e..0f59b37a 100644 --- a/internal/common/defendermem.go +++ b/internal/common/defendermem.go @@ -148,7 +148,7 @@ func (d *memoryDefender) IsBanned(ip, protocol string) bool { defer d.RUnlock() - return d.baseDefender.isBanned(ip, protocol) + return d.isBanned(ip, protocol) } // DeleteHost removes the specified IP from the defender lists @@ -188,7 +188,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool { delete(d.banned, ip) } - score := d.baseDefender.getScore(event) + score := d.getScore(event) ev := hostEvent{ dateTime: time.Now(), @@ -207,11 +207,11 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool { idx++ } } - d.baseDefender.logEvent(ip, protocol, event, hs.TotalScore) + d.logEvent(ip, protocol, event, hs.TotalScore) hs.Events = hs.Events[:idx] if hs.TotalScore >= d.config.Threshold { - d.baseDefender.logBan(ip, protocol) + d.logBan(ip, protocol) d.banned[ip] = time.Now().Add(time.Duration(d.config.BanTime) * time.Minute) delete(d.hosts, ip) d.cleanupBanned() @@ -225,7 +225,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool { d.hosts[ip] = hs } } else { - d.baseDefender.logEvent(ip, protocol, event, ev.score) + d.logEvent(ip, protocol, event, ev.score) d.hosts[ip] = hostScore{ TotalScore: ev.score, Events: []hostEvent{ev}, diff --git a/internal/common/transfer.go b/internal/common/transfer.go index 594768c8..df9638b1 100644 --- a/internal/common/transfer.go +++ b/internal/common/transfer.go @@ -35,7 +35,7 @@ var ( ) // BaseTransfer contains protocols common transfer details for an upload or a download. -type BaseTransfer struct { //nolint:maligned +type BaseTransfer struct { ID int64 BytesSent atomic.Int64 BytesReceived atomic.Int64 diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index 2b7aeada..bbdba29f 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -3700,7 +3700,8 @@ func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) } func getSSLMode() string { - if config.Driver == PGSQLDataProviderName || config.Driver == CockroachDataProviderName { + switch config.Driver { + case PGSQLDataProviderName, CockroachDataProviderName: switch config.SSLMode { case 0: return "disable" @@ -3715,7 +3716,7 @@ func getSSLMode() string { case 5: return "allow" } - } else if config.Driver == MySQLDataProviderName { + case MySQLDataProviderName: if config.requireCustomTLSForMySQL() { return "custom" } diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go index b11778f5..9ba113b6 100644 --- a/internal/ftpd/handler.go +++ b/internal/ftpd/handler.go @@ -287,7 +287,7 @@ func (c *Connection) RemoveDir(name string) error { func (c *Connection) Symlink(oldname, newname string) error { c.UpdateLastActivity() - return c.BaseConnection.CreateSymlink(oldname, newname) + return c.CreateSymlink(oldname, newname) } // ReadDir implements ClientDriverExtensionFilelist diff --git a/internal/ftpd/transfer.go b/internal/ftpd/transfer.go index ed0ce660..071f8b1e 100644 --- a/internal/ftpd/transfer.go +++ b/internal/ftpd/transfer.go @@ -136,7 +136,7 @@ func (t *transfer) closeIO() error { } else if t.reader != nil { err = t.reader.Close() if metadater, ok := t.reader.(vfs.Metadater); ok { - t.BaseTransfer.SetMetadata(metadater.Metadata()) + t.SetMetadata(metadater.Metadata()) } } return err diff --git a/internal/httpd/api_configs.go b/internal/httpd/api_configs.go index 1a208aac..9f4bd153 100644 --- a/internal/httpd/api_configs.go +++ b/internal/httpd/api_configs.go @@ -66,7 +66,7 @@ func testSMTPConfig(w http.ResponseWriter, r *http.Request) { } } if req.AuthType == 3 { - if err := req.Config.OAuth2.Validate(); err != nil { + if err := req.OAuth2.Validate(); err != nil { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } @@ -106,10 +106,10 @@ func (s *httpdServer) handleSMTPOAuth2TokenRequestPost(w http.ResponseWriter, r } configs.SetNilsToEmpty() if err := configs.SMTP.TryDecrypt(); err == nil { - req.OAuth2Config.ClientSecret = configs.SMTP.OAuth2.ClientSecret.GetPayload() + req.ClientSecret = configs.SMTP.OAuth2.ClientSecret.GetPayload() } } - cfg := req.OAuth2Config.GetOAuth2() + cfg := req.GetOAuth2() cfg.RedirectURL = req.BaseRedirectURL + webOAuth2RedirectPath clientSecret := kms.NewPlainSecret(cfg.ClientSecret) clientSecret.SetAdditionalData(xid.New().String()) diff --git a/internal/httpd/file.go b/internal/httpd/file.go index 5bc4627b..daa70b49 100644 --- a/internal/httpd/file.go +++ b/internal/httpd/file.go @@ -126,7 +126,7 @@ func (f *httpdFile) closeIO() error { } else if f.reader != nil { err = f.reader.Close() if metadater, ok := f.reader.(vfs.Metadater); ok { - f.BaseTransfer.SetMetadata(metadater.Metadata()) + f.SetMetadata(metadater.Metadata()) } } return err diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index cca108cb..e8a306c9 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -1364,19 +1364,19 @@ func TestGroupSettingsOverride(t *testing.T) { switch f.Name { case folderName1: assert.Equal(t, mappedPath1, f.MappedPath) - assert.Equal(t, 3, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize) - assert.Equal(t, 5, f.BaseVirtualFolder.FsConfig.OSConfig.WriteBufferSize) + assert.Equal(t, 3, f.FsConfig.OSConfig.ReadBufferSize) + assert.Equal(t, 5, f.FsConfig.OSConfig.WriteBufferSize) assert.True(t, slices.Contains([]string{"/vdir1", "/vdir2"}, f.VirtualPath)) case folderName2: assert.Equal(t, mappedPath2, f.MappedPath) assert.Equal(t, "/vdir3", f.VirtualPath) - assert.Equal(t, 0, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize) - assert.Equal(t, 0, f.BaseVirtualFolder.FsConfig.OSConfig.WriteBufferSize) + assert.Equal(t, 0, f.FsConfig.OSConfig.ReadBufferSize) + assert.Equal(t, 0, f.FsConfig.OSConfig.WriteBufferSize) case folderName3: assert.Equal(t, mappedPath3, f.MappedPath) assert.Equal(t, "/vdir4", f.VirtualPath) - assert.Equal(t, 1, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize) - assert.Equal(t, 2, f.BaseVirtualFolder.FsConfig.OSConfig.WriteBufferSize) + assert.Equal(t, 1, f.FsConfig.OSConfig.ReadBufferSize) + assert.Equal(t, 2, f.FsConfig.OSConfig.WriteBufferSize) } } } diff --git a/internal/sftpd/server.go b/internal/sftpd/server.go index 41ca48aa..16652a47 100644 --- a/internal/sftpd/server.go +++ b/internal/sftpd/server.go @@ -857,11 +857,12 @@ func (c *Configuration) generateDefaultHostKeys(configDir string) error { if _, err = os.Stat(autoFile); errors.Is(err, fs.ErrNotExist) { logger.Info(logSender, "", "No host keys configured and %q does not exist; try to create a new host key", autoFile) logger.InfoToConsole("No host keys configured and %q does not exist; try to create a new host key", autoFile) - if k == defaultPrivateRSAKeyName { + switch k { + case defaultPrivateRSAKeyName: err = util.GenerateRSAKeys(autoFile) - } else if k == defaultPrivateECDSAKeyName { + case defaultPrivateECDSAKeyName: err = util.GenerateECDSAKeys(autoFile) - } else { + default: err = util.GenerateEd25519Keys(autoFile) } if err != nil { diff --git a/internal/sftpd/ssh_cmd.go b/internal/sftpd/ssh_cmd.go index 02768fe0..b0243d09 100644 --- a/internal/sftpd/ssh_cmd.go +++ b/internal/sftpd/ssh_cmd.go @@ -201,15 +201,16 @@ func (c *sshCommand) updateQuota(sshDestPath string, filesNum int, filesSize int func (c *sshCommand) handleHashCommands() error { var h hash.Hash - if c.command == "md5sum" { + switch c.command { + case "md5sum": h = md5.New() - } else if c.command == "sha1sum" { + case "sha1sum": h = sha1.New() - } else if c.command == "sha256sum" { + case "sha256sum": h = sha256.New() - } else if c.command == "sha384sum" { + case "sha384sum": h = sha512.New384() - } else { + default: h = sha512.New() } var response string diff --git a/internal/sftpd/transfer.go b/internal/sftpd/transfer.go index d3acc202..23ce72d2 100644 --- a/internal/sftpd/transfer.go +++ b/internal/sftpd/transfer.go @@ -177,7 +177,7 @@ func (t *transfer) closeIO() error { } else if t.readerAt != nil { err = t.readerAt.Close() if metadater, ok := t.readerAt.(vfs.Metadater); ok { - t.BaseTransfer.SetMetadata(metadater.Metadata()) + t.SetMetadata(metadater.Metadata()) } } return err diff --git a/internal/util/timeoutlistener.go b/internal/util/timeoutlistener.go index 643f7799..fce93cca 100644 --- a/internal/util/timeoutlistener.go +++ b/internal/util/timeoutlistener.go @@ -60,7 +60,7 @@ func (c *Conn) Read(b []byte) (n int, err error) { c.BytesReadFromDeadline.Store(0) // we set both read and write deadlines here otherwise after the request // is read writing the response fails with an i/o timeout error - err = c.Conn.SetDeadline(time.Now().Add(c.ReadTimeout)) + err = c.SetDeadline(time.Now().Add(c.ReadTimeout)) if err != nil { return 0, err } @@ -75,7 +75,7 @@ func (c *Conn) Write(b []byte) (n int, err error) { c.BytesWrittenFromDeadline.Store(0) // we extend the read deadline too, not sure it's necessary, // but it doesn't hurt - err = c.Conn.SetDeadline(time.Now().Add(c.WriteTimeout)) + err = c.SetDeadline(time.Now().Add(c.WriteTimeout)) if err != nil { return } diff --git a/internal/util/util.go b/internal/util/util.go index 9f4f14dc..1fe1492b 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -235,7 +235,7 @@ func ParseBytes(s string) (int64, error) { lastDigit := 0 hasComma := false for _, r := range s { - if !(unicode.IsDigit(r) || r == '.' || r == ',') { + if !unicode.IsDigit(r) && r != '.' && r != ',' { break } if r == ',' { @@ -246,7 +246,7 @@ func ParseBytes(s string) (int64, error) { num := s[:lastDigit] if hasComma { - num = strings.Replace(num, ",", "", -1) + num = strings.ReplaceAll(num, ",", "") } f, err := strconv.ParseFloat(num, 64) @@ -489,10 +489,7 @@ func GetDirsForVirtualPath(virtualPath string) []string { } } dirsForPath := []string{virtualPath} - for { - if virtualPath == "/" { - break - } + for virtualPath != "/" { virtualPath = path.Dir(virtualPath) dirsForPath = append(dirsForPath, virtualPath) } diff --git a/internal/vfs/azblobfs.go b/internal/vfs/azblobfs.go index 590ec5d9..cc71d3bd 100644 --- a/internal/vfs/azblobfs.go +++ b/internal/vfs/azblobfs.go @@ -561,7 +561,7 @@ func (fs *AzureBlobFs) GetDirSize(dirname string) (int, int64, error) { metric.AZListObjectsCompleted(err) return numFiles, size, err } - for _, blobItem := range resp.ListBlobsFlatSegmentResponse.Segment.BlobItems { + for _, blobItem := range resp.Segment.BlobItems { if blobItem.Properties != nil { contentType := util.GetStringFromPointer(blobItem.Properties.ContentType) isDir := checkDirectoryMarkers(contentType, blobItem.Metadata) @@ -629,7 +629,7 @@ func (fs *AzureBlobFs) Walk(root string, walkFn filepath.WalkFunc) error { metric.AZListObjectsCompleted(err) return err } - for _, blobItem := range resp.ListBlobsFlatSegmentResponse.Segment.BlobItems { + for _, blobItem := range resp.Segment.BlobItems { name := util.GetStringFromPointer(blobItem.Name) if fs.isEqual(name, prefix) { continue @@ -886,7 +886,7 @@ func (fs *AzureBlobFs) hasContents(name string) (bool, error) { return result, err } - result = len(resp.ListBlobsFlatSegmentResponse.Segment.BlobItems) > 0 + result = len(resp.Segment.BlobItems) > 0 } metric.AZListObjectsCompleted(nil) @@ -909,9 +909,9 @@ func (fs *AzureBlobFs) downloadPart(ctx context.Context, blockBlob *blockblob.Cl if err != nil { return err } - defer resp.DownloadResponse.Body.Close() + defer resp.Body.Close() - _, err = io.ReadAtLeast(resp.DownloadResponse.Body, buf, int(count)) + _, err = io.ReadAtLeast(resp.Body, buf, int(count)) if err != nil { return err } @@ -1280,7 +1280,7 @@ func (l *azureBlobDirLister) Next(limit int) ([]os.FileInfo, error) { return l.cache, err } - for _, blobPrefix := range page.ListBlobsHierarchySegmentResponse.Segment.BlobPrefixes { + for _, blobPrefix := range page.Segment.BlobPrefixes { name := util.GetStringFromPointer(blobPrefix.Name) // we don't support prefixes == "/" this will be sent if a key starts with "/" if name == "" || name == "/" { @@ -1295,7 +1295,7 @@ func (l *azureBlobDirLister) Next(limit int) ([]os.FileInfo, error) { l.prefixes[strings.TrimSuffix(name, "/")] = true } - for _, blobItem := range page.ListBlobsHierarchySegmentResponse.Segment.BlobItems { + for _, blobItem := range page.Segment.BlobItems { name := util.GetStringFromPointer(blobItem.Name) name = strings.TrimPrefix(name, l.prefix) size := int64(0) diff --git a/internal/vfs/cryptfs.go b/internal/vfs/cryptfs.go index 7b2082f8..8e76da30 100644 --- a/internal/vfs/cryptfs.go +++ b/internal/vfs/cryptfs.go @@ -59,8 +59,8 @@ func NewCryptFs(connectionID, rootDir, mountPath string, config CryptFsConfig) ( connectionID: connectionID, rootDir: rootDir, mountPath: getMountPath(mountPath), - readBufferSize: config.OSFsConfig.ReadBufferSize * 1024 * 1024, - writeBufferSize: config.OSFsConfig.WriteBufferSize * 1024 * 1024, + readBufferSize: config.ReadBufferSize * 1024 * 1024, + writeBufferSize: config.WriteBufferSize * 1024 * 1024, }, masterKey: []byte(config.Passphrase.GetPayload()), } diff --git a/internal/vfs/gcsfs.go b/internal/vfs/gcsfs.go index d34c0a46..677f06c4 100644 --- a/internal/vfs/gcsfs.go +++ b/internal/vfs/gcsfs.go @@ -730,10 +730,10 @@ func (fs *GCSFs) setWriterAttrs(objectWriter *storage.Writer, flag int, name str contentType = mime.TypeByExtension(path.Ext(name)) } if contentType != "" { - objectWriter.ObjectAttrs.ContentType = contentType + objectWriter.ContentType = contentType } if fs.config.StorageClass != "" { - objectWriter.ObjectAttrs.StorageClass = fs.config.StorageClass + objectWriter.StorageClass = fs.config.StorageClass } if fs.config.ACL != "" { objectWriter.PredefinedACL = fs.config.ACL diff --git a/internal/webdavd/file.go b/internal/webdavd/file.go index be08c554..88cc6bde 100644 --- a/internal/webdavd/file.go +++ b/internal/webdavd/file.go @@ -165,7 +165,7 @@ func (f *webDavFile) checkFirstRead() error { if !f.Connection.User.HasPerm(dataprovider.PermDownload, path.Dir(f.GetVirtualPath())) { return f.Connection.GetPermissionDeniedError() } - transferQuota := f.BaseTransfer.GetTransferQuota() + transferQuota := f.GetTransferQuota() if !transferQuota.HasDownloadSpace() { f.Connection.Log(logger.LevelInfo, "denying file read due to quota limits") return f.Connection.GetReadQuotaExceededError() @@ -212,7 +212,7 @@ func (f *webDavFile) Read(p []byte) (n int, err error) { } else if r != nil { f.reader = r } - f.BaseTransfer.SetCancelFn(cancelFn) + f.SetCancelFn(cancelFn) } f.ErrTransfer = e f.startOffset = 0 @@ -322,9 +322,10 @@ func (f *webDavFile) Seek(offset int64, whence int) (int64, error) { if f.GetType() == common.TransferDownload { readOffset := f.startOffset + f.BytesSent.Load() if offset == 0 && readOffset == 0 { - if whence == io.SeekStart { + switch whence { + case io.SeekStart: return 0, nil - } else if whence == io.SeekEnd { + case io.SeekEnd: if err := f.updateStatInfo(); err != nil { return 0, err } @@ -363,7 +364,7 @@ func (f *webDavFile) Seek(offset int64, whence int) (int64, error) { f.reader = r } f.ErrTransfer = err - f.BaseTransfer.SetCancelFn(cancelFn) + f.SetCancelFn(cancelFn) f.Unlock() return startByte, err @@ -403,7 +404,7 @@ func (f *webDavFile) closeIO() error { } else if f.reader != nil { err = f.reader.Close() if metadater, ok := f.reader.(vfs.Metadater); ok { - f.BaseTransfer.SetMetadata(metadater.Metadata()) + f.SetMetadata(metadater.Metadata()) } } return err