Skip to content

Commit 49bf434

Browse files
authored
Merge branch 'master' into settings
2 parents 83ec1c4 + 695e05f commit 49bf434

File tree

6 files changed

+374
-38
lines changed

6 files changed

+374
-38
lines changed

client_handler.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ func getHashMapping() map[string]HASHAlgo {
4141
return mapping
4242
}
4343

44+
func getHashName(algo HASHAlgo) string {
45+
hashName := ""
46+
hashMapping := getHashMapping()
47+
48+
for k, v := range hashMapping {
49+
if v == algo {
50+
hashName = k
51+
}
52+
}
53+
54+
return hashName
55+
}
56+
4457
// nolint: maligned
4558
type clientHandler struct {
4659
id uint32 // ID of the client

driver.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,5 @@ type Settings struct {
188188
EnableHASH bool // Enable support for calculating hash value of files
189189
DisableSTAT bool // Disable Server STATUS, STAT on files and directories will still work
190190
DisableSYST bool // Disable SYST
191+
EnableCOMB bool // Enable COMB support
191192
}

handle_files.go

Lines changed: 136 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"crypto/sha1" //nolint:gosec
77
"crypto/sha256"
88
"crypto/sha512"
9+
"encoding/csv"
910
"encoding/hex"
11+
"errors"
1012
"fmt"
1113
"hash"
1214
"hash/crc32"
@@ -39,7 +41,6 @@ func (c *clientHandler) transferFile(write bool, append bool) {
3941
var file FileTransfer
4042
var err error
4143
var fileFlag int
42-
var filePerm os.FileMode = 0777
4344

4445
path := c.absPath(c.param)
4546

@@ -60,11 +61,7 @@ func (c *clientHandler) transferFile(write bool, append bool) {
6061
fileFlag = os.O_RDONLY
6162
}
6263

63-
if fileTransfer, ok := c.driver.(ClientDriverExtentionFileTransfer); ok {
64-
file, err = fileTransfer.GetHandle(path, fileFlag, c.ctxRest)
65-
} else {
66-
file, err = c.driver.OpenFile(path, fileFlag, filePerm)
67-
}
64+
file, err = c.getFileHandle(path, fileFlag, c.ctxRest)
6865

6966
// If this fail, can stop right here and reset the seek position
7067
if err != nil {
@@ -84,7 +81,7 @@ func (c *clientHandler) transferFile(write bool, append bool) {
8481
// if we are unable to seek we can stop right here and close the file
8582
c.writeMessage(StatusActionNotTaken, "Could not seek file: "+err.Error())
8683
// we can ignore the close error here
87-
file.Close() //nolint:errcheck,gosec
84+
c.closeUnchecked(file)
8885

8986
return
9087
}
@@ -94,7 +91,7 @@ func (c *clientHandler) transferFile(write bool, append bool) {
9491
if err != nil {
9592
// an error is already returned to the FTP client
9693
// we can stop right here and close the file ignoring close error if any
97-
file.Close() //nolint:errcheck,gosec
94+
c.closeUnchecked(file)
9895

9996
return
10097
}
@@ -144,6 +141,92 @@ func (c *clientHandler) doFileTransfer(tr net.Conn, file io.ReadWriter, write bo
144141
return err
145142
}
146143

144+
func (c *clientHandler) handleCOMB() error {
145+
if !c.server.settings.EnableCOMB {
146+
// if disabled the client should not arrive here as COMB support is not declared in the FEAT response
147+
c.writeMessage(StatusCommandNotImplemented, "COMB support is disabled")
148+
return nil
149+
}
150+
151+
relativePaths, err := unquoteSpaceSeparatedParams(c.param)
152+
if err != nil || len(relativePaths) < 2 {
153+
c.writeMessage(StatusSyntaxErrorParameters, fmt.Sprintf("invalid COMB parameters: %v", c.param))
154+
return nil
155+
}
156+
157+
targetPath := c.absPath(relativePaths[0])
158+
159+
sourcePaths := make([]string, 0, len(relativePaths)-1)
160+
for _, src := range relativePaths[1:] {
161+
sourcePaths = append(sourcePaths, c.absPath(src))
162+
}
163+
// if targetPath exists we have append to it
164+
// partial files will be deleted if COMB succeeded
165+
_, err = c.driver.Stat(targetPath)
166+
if err != nil && !errors.Is(err, os.ErrNotExist) {
167+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could not access file %#v: %v", targetPath, err))
168+
return nil
169+
}
170+
171+
fileFlag := os.O_WRONLY
172+
if errors.Is(err, os.ErrNotExist) {
173+
fileFlag |= os.O_CREATE
174+
} else {
175+
fileFlag |= os.O_APPEND
176+
}
177+
178+
c.combineFiles(targetPath, fileFlag, sourcePaths)
179+
180+
return nil
181+
}
182+
183+
func (c *clientHandler) combineFiles(targetPath string, fileFlag int, sourcePaths []string) {
184+
file, err := c.getFileHandle(targetPath, fileFlag, 0)
185+
if err != nil {
186+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could not access file %#v: %v", targetPath, err))
187+
return
188+
}
189+
190+
for _, partial := range sourcePaths {
191+
var src FileTransfer
192+
193+
src, err = c.getFileHandle(partial, os.O_RDONLY, 0)
194+
if err != nil {
195+
c.closeUnchecked(file)
196+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could not access file %#v: %v", partial, err))
197+
198+
return
199+
}
200+
201+
_, err = io.Copy(file, src)
202+
if err != nil {
203+
c.closeUnchecked(src)
204+
c.closeUnchecked(file)
205+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could combine file %#v: %v", partial, err))
206+
207+
return
208+
}
209+
210+
c.closeUnchecked(src)
211+
212+
err = c.driver.Remove(partial)
213+
if err != nil {
214+
c.closeUnchecked(file)
215+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could delete file %#v after combine: %v", partial, err))
216+
217+
return
218+
}
219+
}
220+
221+
err = file.Close()
222+
if err != nil {
223+
c.writeMessage(StatusActionNotTaken, fmt.Sprintf("Could close combined file %#v: %v", targetPath, err))
224+
return
225+
}
226+
227+
c.writeMessage(StatusFileOK, "COMB succeeded!")
228+
}
229+
147230
func (c *clientHandler) handleCHMOD(params string) {
148231
spl := strings.SplitN(params, " ", 2)
149232
modeNb, err := strconv.ParseUint(spl[0], 8, 32)
@@ -435,6 +518,12 @@ func (c *clientHandler) handleSHA512() error {
435518
}
436519

437520
func (c *clientHandler) handleGenericHash(algo HASHAlgo, isCustomMode bool) error {
521+
if !c.server.settings.EnableHASH {
522+
// if disabled the client should not arrive here as HASH support is not declared in the FEAT response
523+
c.writeMessage(StatusCommandNotImplemented, "File hash support is disabled")
524+
return nil
525+
}
526+
438527
args := strings.SplitN(c.param, " ", 3)
439528
info, err := c.driver.Stat(args[0])
440529

@@ -451,6 +540,9 @@ func (c *clientHandler) handleGenericHash(algo HASHAlgo, isCustomMode bool) erro
451540
start := int64(0)
452541
end := info.Size()
453542

543+
// to support partial hash also for the HASH command we should implement RANG too,
544+
// but this apply also to uploads/downloads and so complicat the things, we'll add
545+
// this support in future improvements
454546
if isCustomMode {
455547
// for custom command the range can be specified in this way:
456548
// XSHA1 <file> <start> <end>
@@ -470,9 +562,7 @@ func (c *clientHandler) handleGenericHash(algo HASHAlgo, isCustomMode bool) erro
470562
}
471563
}
472564
}
473-
// to support partial hash also for the HASH command we should implement RANG too,
474-
// but this apply also to uploads/downloads and so complicat the things, we'll add
475-
// this support in future improvements
565+
476566
var result string
477567
if hasher, ok := c.driver.(ClientDriverExtensionHasher); ok {
478568
result, err = hasher.ComputeHash(c.absPath(args[0]), algo, start, end)
@@ -485,15 +575,7 @@ func (c *clientHandler) handleGenericHash(algo HASHAlgo, isCustomMode bool) erro
485575
return nil
486576
}
487577

488-
hashMapping := getHashMapping()
489-
hashName := ""
490-
491-
for k, v := range hashMapping {
492-
if v == algo {
493-
hashName = k
494-
}
495-
}
496-
578+
hashName := getHashName(algo)
497579
firstLine := fmt.Sprintf("Computing %v digest", hashName)
498580

499581
if isCustomMode {
@@ -527,11 +609,7 @@ func (c *clientHandler) computeHashForFile(filePath string, algo HASHAlgo, start
527609
return "", errUnknowHash
528610
}
529611

530-
if fileTransfer, ok := c.driver.(ClientDriverExtentionFileTransfer); ok {
531-
file, err = fileTransfer.GetHandle(filePath, os.O_RDONLY, start)
532-
} else {
533-
file, err = c.driver.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
534-
}
612+
file, err = c.getFileHandle(filePath, os.O_RDONLY, start)
535613

536614
if err != nil {
537615
return "", err
@@ -545,11 +623,43 @@ func (c *clientHandler) computeHashForFile(filePath string, algo HASHAlgo, start
545623
}
546624

547625
_, err = io.CopyN(h, file, end-start)
548-
defer file.Close() //nolint:errcheck // we ignore close error here
626+
defer c.closeUnchecked(file) // we ignore close error here
549627

550628
if err != nil && err != io.EOF {
551629
return "", err
552630
}
553631

554632
return hex.EncodeToString(h.Sum(nil)), nil
555633
}
634+
635+
func (c *clientHandler) getFileHandle(name string, flags int, offset int64) (FileTransfer, error) {
636+
if fileTransfer, ok := c.driver.(ClientDriverExtentionFileTransfer); ok {
637+
return fileTransfer.GetHandle(name, flags, offset)
638+
}
639+
640+
return c.driver.OpenFile(name, flags, os.ModePerm)
641+
}
642+
643+
func (c *clientHandler) closeUnchecked(file io.Closer) {
644+
if err := file.Close(); err != nil {
645+
c.logger.Warn(
646+
"Problem closing a file",
647+
"err", err,
648+
)
649+
}
650+
}
651+
652+
// This method split params by spaces, expect when the space is inside quotes.
653+
// It was introduced to support COMB command. Supported COMB examples:
654+
//
655+
// - Append a single part onto an existing (or new) file: e.g., COMB "final.log" "132.log".
656+
// - Target and source files do not require enclosing quotes UNLESS the filename includes spaces:
657+
// - COMB final5.log 64.log 65.log
658+
// - COMB "final5.log" "64.log" "65.log"
659+
// - COMB final7.log "6 6.log" 67.log
660+
func unquoteSpaceSeparatedParams(params string) ([]string, error) {
661+
reader := csv.NewReader(strings.NewReader(params))
662+
reader.Comma = ' ' // space
663+
664+
return reader.Read()
665+
}

0 commit comments

Comments
 (0)