Skip to content

Commit 30d438f

Browse files
committed
fix: multiple cp improvements
- Add receivedFinal flag to detect incomplete transfers - Pass Dialer to recursive directory copy - Append to existing base URL path in buildWsURL - Handle root directory edge case in sanitizePath
1 parent 6970d69 commit 30d438f

File tree

1 file changed

+15
-4
lines changed

1 file changed

+15
-4
lines changed

lib/cp.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func CpToInstance(ctx context.Context, cfg CpConfig, opts CpToInstanceOptions) e
201201
}
202202

203203
if srcInfo.IsDir() {
204-
return copyDirToWs(ctx, cfg, ws, opts.SrcPath, opts.DstPath, opts.InstanceID, opts.Archive, opts.FollowLinks, opts.Callbacks)
204+
return copyDirToWs(ctx, cfg, ws, opts.SrcPath, opts.DstPath, opts.InstanceID, opts.Archive, opts.FollowLinks, opts.Dialer, opts.Callbacks)
205205
}
206206
return copyFileToWs(ws, opts.SrcPath, srcInfo.Size(), opts.Callbacks)
207207
}
@@ -271,7 +271,7 @@ func copyFileToWs(ws WsConn, srcPath string, size int64, callbacks *CpCallbacks)
271271
}
272272

273273
// copyDirToWs copies a directory to the WebSocket
274-
func copyDirToWs(ctx context.Context, cfg CpConfig, ws WsConn, srcPath, dstPath, instanceID string, archive, followLinks bool, callbacks *CpCallbacks) error {
274+
func copyDirToWs(ctx context.Context, cfg CpConfig, ws WsConn, srcPath, dstPath, instanceID string, archive, followLinks bool, dialer WsDialer, callbacks *CpCallbacks) error {
275275
// For directory copy, we just send the end marker
276276
// The server will create the directory
277277
endMsg, _ := json.Marshal(map[string]string{"type": "end"})
@@ -326,6 +326,7 @@ func copyDirToWs(ctx context.Context, cfg CpConfig, ws WsConn, srcPath, dstPath,
326326
Mode: info.Mode().Perm(),
327327
Archive: archive,
328328
FollowLinks: followLinks,
329+
Dialer: dialer,
329330
Callbacks: callbacks,
330331
})
331332
})
@@ -383,6 +384,7 @@ func CpFromInstance(ctx context.Context, cfg CpConfig, opts CpFromInstanceOption
383384
var currentFile *os.File
384385
var currentHeader *cpFileHeader
385386
var bytesReceived int64
387+
var receivedFinal bool
386388

387389
// Ensure any open file is closed on function exit (fixes file handle leak)
388390
defer func() {
@@ -493,6 +495,7 @@ func CpFromInstance(ctx context.Context, cfg CpConfig, opts CpFromInstanceOption
493495
}
494496

495497
if endMarker.Final {
498+
receivedFinal = true
496499
return nil
497500
}
498501

@@ -524,6 +527,10 @@ func CpFromInstance(ctx context.Context, cfg CpConfig, opts CpFromInstanceOption
524527
}
525528
}
526529

530+
// If connection closed without receiving final marker, the transfer was incomplete
531+
if !receivedFinal {
532+
return fmt.Errorf("copy stream ended without completion marker")
533+
}
527534
return nil
528535
}
529536

@@ -555,7 +562,9 @@ func sanitizePath(base, path string) (string, error) {
555562
}
556563

557564
// Ensure the result is under the base directory
558-
if !strings.HasPrefix(absResult, absBase+string(filepath.Separator)) && absResult != absBase {
565+
// Special case: if base is root ("/"), everything under it is valid
566+
isRoot := absBase == "/" || absBase == string(filepath.Separator)
567+
if !isRoot && !strings.HasPrefix(absResult, absBase+string(filepath.Separator)) && absResult != absBase {
559568
return "", fmt.Errorf("invalid path: path escapes destination: %s", path)
560569
}
561570

@@ -569,7 +578,9 @@ func buildWsURL(baseURL, instanceID string) (string, error) {
569578
return "", fmt.Errorf("invalid base URL: %w", err)
570579
}
571580

572-
u.Path = fmt.Sprintf("/instances/%s/cp", instanceID)
581+
// Append to existing path (preserves any path prefix like /api)
582+
// Use path.Join to handle trailing slashes and ensure clean paths
583+
u.Path = path.Join(u.Path, "instances", instanceID, "cp")
573584

574585
switch u.Scheme {
575586
case "https":

0 commit comments

Comments
 (0)