Skip to content

v0.6.3 - #25

Merged
bethropolis merged 12 commits into
mainfrom
dev
Jul 15, 2026
Merged

v0.6.3#25
bethropolis merged 12 commits into
mainfrom
dev

Conversation

@bethropolis

@bethropolis bethropolis commented Jul 15, 2026

Copy link
Copy Markdown
Owner

v0.6.3

Features

  • Send clipboard text via Preview field, receiver responds 204 (follow up to Sending data via stdin #21 )
  • Send clipboard/stdin as in-memory content (no temp file)

Fixes

  • Reject empty filenames after sanitize
  • Only strip image files in private mode (non-image regression)
  • CloseSession release mutex before Progress.Wait()
  • Reject negative file sizes in prepare-upload (Serve should refuse files with negative size #24)
  • Sanitize control characters in filenames (Server should not interpret control sequences #23)
  • Non-destructive EXIF/metadata strip with temp+rename
  • Atomic ClaimFile for concurrent upload integrity
  • saveTextAsFile returns explicit HTTP status 400/500/200
  • Abort in-flight uploads on Ctrl+C, graceful shutdown
  • Self-discovery, ReadTimeout, fingerprint truncation, Wayland clipboard
  • Clipboard: capture stderr, treat empty clipboard as empty not error

Protocol

  • Clipboard text sent via Preview field (matching official LocalSend behavior)

saveTextAsFile now returns error instead of void. Caller maps:
- path traversal -> 400 Bad Request
- save failure -> 500 Internal Server Error
- success -> 200 OK (explicit, not implicit)

Tests added for all three branches.
New methods on ReceiveService:
- ClaimFile: validates session/senderIP/fileId/token under mutex, marks as uploading
- CompleteFile: removes file from session after success
- FailFile: resets file state to pending on failure
- GetSessionProgress: safe RLock read of progress bar pointer

UploadHandlerV2 now uses ClaimFile/CompleteFile/FailFile instead of
GetSessionByID + RemoveFileFromSession, preventing duplicate concurrent
uploads of the same token.

Tests: concurrent claim (one success, one ErrAlreadyUploading),
error cases (invalid session/file/token/IP), CompleteFile lifecycle
Refactored stripping to write to a separate destination path:
- StripTo(src, dest) writes stripped image to dest, never touches src
- Strip(path) uses temp-in-dir + rename for atomic in-place replacement
- stripJPEGTo/stripPNGTo: fail closed when SOS/IEND not found
- PNG: strip eXIf chunk in addition to tEXt/zTXt/iTXt
- Magic sniff (checks first 8 bytes), not just file extension
- writeAtomic helper for safe temp+rename writes

SendFile private mode: strips to temp file, uploads temp, defers cleanup.
Original file bytes are never modified.

Tests: JPEG EXIF removal, original unchanged, truncated JPEG error,
PNG eXIf stripping, non-image passthrough, nonexistent file error
sanitizeName strips ASCII control bytes 0x00-0x1F from FileName
in PrepareUploadHandlerV2 after decoding the request, preventing
UI spoofing and terminal escape injection via display prompts.

Test: control-char filename in prepare-upload returns 200 (sanitized)
Guard against f.Size < 0 in PrepareUploadHandlerV2's disk-space
loop. Without this check, a negative size reduces totalSize and
bypasses the free-space guard, and the value flows into uint64
conversion which wraps to a large positive number.

Test: prepare-upload with Size: -10 returns 400 Bad Request
Release sessionMutex before calling Progress.Wait() to match the
pattern used by cleanupLoop and CloseAllSessions. This prevents
stalling other operations (like concurrent ClaimFile calls) during
the progress bar Wait, which can block on terminal rendering.
Private mode previously created empty temp files for non-image files
(PDFs, zips, text files) because StripTo returns nil for non-images
(no error, no output). The empty temp then replaced the real file in
the upload map, causing 0-byte uploads.

Fix: export IsImageFile from metadata package; guard the strip loop
so non-images keep their original path unchanged.
After sanitizing control chars from filenames, reject names that
become empty with 400 Bad Request (prevents downstream confusion).

Strengthened TestPrepareUpload_SanitizesControlChars to assert the
stored session filename is 'bad.txt' after sanitization (not just
that prepare returns 200).
…pty not error

- Use CombinedOutput() instead of Output() to capture stderr
- Include tool name in both Read() and Write() error messages
- Treat tool exit with no output as empty clipboard (xclip/wl-paste exit 1
  when clipboard is empty) instead of returning a cryptic error
- Expand 'no tool found' message with actionable install hints
Remove the temp file creation for --clipboard and --stdin flags, and
instead thread raw byte content through the send pipeline via a new
SendOption/WithInMemoryFile mechanism.

Changes:
- pkg/send/send.go: Add SendOption, WithInMemoryFile, sendConfig, memFile
  types. SendToDevice and SendFiles now accept variadic SendOption.
  Process in-memory files alongside file-based files in the fileDto
  build loop and upload goroutines.
- pkg/send/upload.go: Add memReadSeekCloser (bytes.Reader wrapper with
  no-op Close), fileReader interface. Extract uploadStream() from
  uploadFile() so both file and in-memory uploads share the same stream
  logic.
- cmd/localgo/cmd/send.go: Replace os.CreateTemp/defer os.Remove with
  send.WithInMemoryFile() calls. Remove the localgo-clip- prefix hack
  entirely. Adjust file picker/empty checks to account for sendOpts.
Evaluate session.SessionID and file token before goroutines to
avoid concurrent map access without the session mutex.
@bethropolis
bethropolis merged commit 80473a5 into main Jul 15, 2026
3 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant