Skip to content

Commit 5d78269

Browse files
fclairambclaude
andauthored
docs: update CLAUDE.md and fix README.md typos (#598)
- Rewrote CLAUDE.md with comprehensive documentation for AI assistants: - Detailed project architecture and file organization - Build, test, and lint commands - Coding conventions and design principles - Linter configuration details - Testing infrastructure and patterns - Common tasks (adding commands, extensions) - CI/CD workflow documentation - Fixed README.md issues: - Corrected AVLB -> AVBL typo in supported extensions - Updated PassiveTransferPortRange type from *PortRange to PasvPortGetter Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3960a3a commit 5d78269

2 files changed

Lines changed: 215 additions & 56 deletions

File tree

CLAUDE.md

Lines changed: 213 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,242 @@
11
# CLAUDE.md
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3+
This file provides guidance for AI assistants working with the ftpserverlib codebase.
44

55
## Project Overview
66

7-
This is a **Go FTP server library** (`ftpserver` package) that provides a complete RFC 959-compliant FTP server implementation with TLS, IPv6, and extended command support. The library uses a **driver-based architecture** where users implement interfaces to customize file system operations and authentication.
7+
**ftpserverlib** is a Go library for building FTP servers using [afero](https://github.com/spf13/afero) as the backend filesystem. It implements RFC 959 and numerous extensions, providing a clean, driver-based architecture for customization.
88

9-
## Architecture
9+
**Repository**: `github.com/fclairamb/ftpserverlib`
1010

11-
### Core Driver Pattern
12-
The library centers around three main interfaces:
13-
- **MainDriver**: Authentication, client lifecycle, TLS configuration
14-
- **ClientDriver**: File system operations (based on `afero.Fs`)
15-
- **ClientContext**: Client connection metadata and context
11+
## Build and Test Commands
1612

17-
### Key Components
18-
- **Server Core** (`server.go`): Main server with command mapping system
19-
- **Client Handler** (`client_handler.go`): Per-client connection management and protocol state machine
20-
- **Command Handlers**: Organized by functionality (`handle_*.go` files)
21-
- **Transfer System**: Separate active (`transfer_active.go`) and passive (`transfer_pasv.go`) mode implementations
22-
23-
## Common Commands
24-
25-
### Development
2613
```bash
27-
# Build the library
14+
# Build
2815
go build -v ./...
2916

30-
# Run full test suite with race detection and coverage
17+
# Run tests (standard)
18+
go test -v ./...
19+
20+
# Run tests with race detection (as CI does)
3121
go test -parallel 20 -v -race -coverprofile=coverage.txt -covermode=atomic ./...
3222

33-
# Lint code
23+
# Run linter (requires golangci-lint v2.4.0+)
3424
golangci-lint run
25+
26+
# Format code
27+
gofmt -w .
28+
goimports -w -local github.com/fclairamb/ftpserverlib .
29+
```
30+
31+
## Project Architecture
32+
33+
### Single Package Design
34+
35+
The entire library is a single `ftpserver` package with files organized by responsibility:
36+
37+
| File(s) | Purpose |
38+
|---------|---------|
39+
| `server.go` | Main `FtpServer` struct, initialization, listener management |
40+
| `client_handler.go` | Per-client connection state machine and command parsing |
41+
| `handle_auth.go` | USER, PASS, AUTH, PROT, PBSZ commands |
42+
| `handle_dirs.go` | CWD, CDUP, MKD, RMD, PWD commands |
43+
| `handle_files.go` | STOR, RETR, LIST, NLST, MLST, MLSD, DELE, SIZE, etc. |
44+
| `handle_misc.go` | SYST, FEAT, NOOP, QUIT, SITE, STAT, HELP commands |
45+
| `transfer_pasv.go` | Passive mode data connections (PASV, EPSV) |
46+
| `transfer_active.go` | Active mode data connections (PORT, EPRT) |
47+
| `driver.go` | All interface definitions (MainDriver, ClientDriver, extensions) |
48+
| `consts.go` | FTP status codes and constants |
49+
| `errors.go` | Custom error types (DriverError, NetworkError, FileAccessError) |
50+
| `asciiconverter.go` | ASCII mode CRLF/LF conversion |
51+
52+
### Driver-Based Architecture
53+
54+
Users implement interfaces to customize server behavior:
55+
56+
```go
57+
// Required: Main authentication and configuration
58+
type MainDriver interface {
59+
GetSettings() (*Settings, error)
60+
ClientConnected(cc ClientContext) (string, error)
61+
ClientDisconnected(cc ClientContext)
62+
AuthUser(cc ClientContext, user, pass string) (ClientDriver, error)
63+
GetTLSConfig() (*tls.Config, error)
64+
}
65+
66+
// Required: Filesystem operations (wraps afero.Fs)
67+
type ClientDriver interface {
68+
afero.Fs
69+
}
70+
```
71+
72+
### Extension Pattern
73+
74+
Optional features use interface assertion:
75+
76+
```go
77+
// In handler code:
78+
if hasher, ok := c.driver.(ClientDriverExtensionHasher); ok {
79+
// Extension is supported, use it
80+
hash, err := hasher.ComputeHash(name, algo, start, end)
81+
}
82+
```
83+
84+
Available extensions:
85+
- `MainDriverExtensionTLSVerifier` - TLS certificate authentication
86+
- `MainDriverExtensionUserVerifier` - Pre-auth user validation
87+
- `MainDriverExtensionPostAuthMessage` - Custom post-auth messages
88+
- `MainDriverExtensionPassiveWrapper` - Wrap passive listeners
89+
- `MainDriverExtensionQuitMessage` - Custom quit messages
90+
- `ClientDriverExtensionAllocate` - ALLO command support
91+
- `ClientDriverExtensionSymlink` - SITE SYMLINK support
92+
- `ClientDriverExtensionFileList` - Custom directory listing
93+
- `ClientDriverExtentionFileTransfer` - Custom file transfer handles
94+
- `ClientDriverExtensionRemoveDir` - Distinguish RMD from DELE
95+
- `ClientDriverExtensionHasher` - Custom hash implementations
96+
- `ClientDriverExtensionAvailableSpace` - AVBL command support
97+
- `ClientDriverExtensionSite` - Custom SITE subcommands
98+
99+
## Coding Conventions
100+
101+
### Naming
102+
- Exported: `PascalCase` (e.g., `FtpServer`, `MainDriver`, `Settings`)
103+
- Unexported: `camelCase` (e.g., `clientHandler`, `transferHandler`)
104+
- Command handlers: `handle{COMMAND}` (e.g., `handleUSER`, `handleRETR`)
105+
- Test files: `*_test.go` colocated with implementation
106+
107+
### Enums
108+
Use `type X int8` with `iota`:
109+
```go
110+
type HASHAlgo int8
111+
const (
112+
HASHAlgoCRC32 HASHAlgo = iota
113+
HASHAlgoMD5
114+
HASHAlgoSHA1
115+
HASHAlgoSHA256
116+
HASHAlgoSHA512
117+
)
35118
```
36119

37-
### Testing Specific Components
120+
### Error Handling
121+
- Use custom error types: `DriverError`, `NetworkError`, `FileAccessError`
122+
- Wrap errors with context: `fmt.Errorf("operation failed: %w", err)`
123+
- Check errors with `errors.Is()`: `errors.Is(err, ErrStorageExceeded)`
124+
- Use `getErrorCode()` to map Go errors to FTP status codes
125+
126+
### Synchronization
127+
- No global mutexes (only per-client)
128+
- `paramsMutex` (RWMutex) protects public API fields in `clientHandler`
129+
- `transferMu` protects transfer connection state
130+
- `sync.WaitGroup` for command-to-transfer coordination
131+
132+
### Design Principles
133+
- **No sleep**: Use proper synchronization, not time delays
134+
- **No panic**: Propagate errors, don't crash
135+
- **No global sync**: Each client manages its own state
136+
137+
## Linter Configuration
138+
139+
The project uses golangci-lint v2 with strict settings (`.golangci.yml`):
140+
141+
- **Line length**: 120 characters max
142+
- **Function length**: 80 lines / 40 statements max
143+
- **Cyclomatic complexity**: 15 max
144+
- **Cognitive complexity**: 30 max
145+
- **Import organization**: stdlib, third-party, then local (`github.com/fclairamb/ftpserverlib`)
146+
147+
Key enabled linters: `gosec`, `errcheck`, `errorlint`, `gocyclo`, `gocognit`, `funlen`, `dupl`, `unparam`, `staticcheck`
148+
149+
## Testing
150+
151+
### Test Infrastructure
152+
- Tests use `github.com/stretchr/testify` (both `assert` and `require`)
153+
- Reference driver implementation in `driver_test.go` (`TestServerDriver`)
154+
- Setup helpers: `NewTestServer()`, `NewTestServerWithTestDriver()`, `NewTestServerWithDriver()`
155+
- FTP client for integration tests: `github.com/secsy/goftp` (replaced with fork)
156+
157+
### Running Tests
38158
```bash
39-
# Test individual handlers
40-
go test -v -run TestHandle
159+
# Standard test run
160+
go test -v ./...
41161

42-
# Test transfers specifically
43-
go test -v -run TestTransfer
162+
# With race detection (recommended)
163+
go test -race ./...
44164

45-
# Run benchmarks
46-
go test -bench=.
165+
# Specific test
166+
go test -v -run TestNamePattern ./...
47167
```
48168

49-
## File Organization
169+
### Test Patterns
170+
- Table-driven tests for multiple scenarios
171+
- Real filesystem via `afero.NewBasePathFs` with temp directories
172+
- Integration tests using actual FTP protocol
173+
- Concurrent client testing (100+ simultaneous connections)
174+
175+
## Dependencies
176+
177+
**Go Version**: 1.24.0 minimum, toolchain 1.25.5
178+
179+
**Direct Dependencies**:
180+
- `github.com/spf13/afero` - Filesystem abstraction
181+
- `log/slog` - Go standard library structured logging (no external dependencies)
182+
- `golang.org/x/sys` - Platform syscalls
183+
184+
**Test Dependencies**:
185+
- `github.com/stretchr/testify` - Assertions
186+
- `github.com/secsy/goftp` (replaced with `github.com/drakkan/goftp`) - FTP client
187+
188+
## Common Tasks
189+
190+
### Adding a New FTP Command
191+
192+
1. Add handler method in appropriate `handle_*.go` file:
193+
```go
194+
func (c *clientHandler) handleNEWCMD(param string) error {
195+
// Implementation
196+
return c.writeMessage(StatusOK, "Command successful")
197+
}
198+
```
199+
200+
2. Register in `commandsMap` in `consts.go`:
201+
```go
202+
"NEWCMD": {Fn: (*clientHandler).handleNEWCMD, Open: false, TransferRelated: false},
203+
```
204+
205+
3. Add to FEAT response if applicable (in `handleFEAT`)
206+
207+
4. Write tests in corresponding `handle_*_test.go`
208+
209+
### Adding a Driver Extension
50210

51-
### Command Handler Structure
52-
- `handle_auth.go`: Authentication commands (USER, PASS, AUTH, PBSZ, PROT)
53-
- `handle_files.go`: File operations (STOR, RETR, LIST, NLST, MLST, MLSD)
54-
- `handle_dirs.go`: Directory operations (CWD, CDUP, MKD, RMD, PWD)
55-
- `handle_misc.go`: System commands (SYST, FEAT, NOOP, QUIT, HELP)
211+
1. Define interface in `driver.go`:
212+
```go
213+
type ClientDriverExtensionNewFeature interface {
214+
NewFeatureMethod(args) (result, error)
215+
}
216+
```
56217

57-
### Core Files
58-
- `driver.go`: Interface definitions and driver extensions
59-
- `client_handler.go`: Main protocol implementation and state management
60-
- `server.go`: Server initialization and command routing
61-
- `errors.go`: FTP-specific error codes and handling
218+
2. Check for extension in handler:
219+
```go
220+
if ext, ok := c.driver.(ClientDriverExtensionNewFeature); ok {
221+
result, err := ext.NewFeatureMethod(args)
222+
}
223+
```
62224

63-
## Testing Architecture
225+
### Modifying Server Settings
64226

65-
The test suite uses a **reference driver implementation** (`driver_test.go`) with:
66-
- Mock file system using `afero.NewBasePathFs` with temp directories
67-
- Integration tests that simulate real FTP client interactions
68-
- Comprehensive coverage of all command handlers and transfer modes
69-
- Race condition testing for concurrent operations
227+
Settings are defined in `driver.go` (`Settings` struct) and returned via `MainDriver.GetSettings()`. Add new fields there and handle them appropriately in server/client code.
70228

71-
## Key Dependencies
229+
## CI/CD
72230

73-
- `github.com/spf13/afero`: File system abstraction for driver implementations
74-
- `log/slog`: Go standard library structured logging (no external logging dependencies)
231+
GitHub Actions workflow (`.github/workflows/build.yml`):
232+
- Runs on: `ubuntu-24.04`
233+
- Go versions: 1.25 (with linting), 1.24
234+
- Steps: Lint -> Build -> Test (with race detection) -> Codecov upload
75235

76-
## Code Conventions
236+
## Key Files Reference
77237

78-
- **No global state**: All server instances are isolated
79-
- **Interface-based design**: Extensive use of optional interfaces for extensibility
80-
- **Error handling**: Custom FTP error types with appropriate status codes
81-
- **Concurrency**: Clean goroutine management without sleep/panic patterns
82-
- **Line length**: 120 characters maximum (enforced by linter)
83-
- **Function length**: 80 lines maximum (enforced by linter)
238+
- `driver.go` - All public interfaces
239+
- `server.go` - Server initialization and lifecycle
240+
- `client_handler.go` - Client state machine (largest file)
241+
- `consts.go` - FTP status codes and command registration
242+
- `driver_test.go` - Reference driver implementation for testing

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ If you're interested in a fully featured FTP server, you should use [sftpgo](htt
4040
* [MLST](https://tools.ietf.org/html/rfc3659#page-23) - Simple file listing for machine processing
4141
* [MLSD](https://tools.ietf.org/html/rfc3659#page-23) - Directory listing for machine processing
4242
* [HASH](https://tools.ietf.org/html/draft-bryan-ftpext-hash-02) - Hashing of files
43-
* [AVLB](https://tools.ietf.org/html/draft-peterson-streamlined-ftp-command-extensions-10#section-4) - Available space
43+
* [AVBL](https://tools.ietf.org/html/draft-peterson-streamlined-ftp-command-extensions-10#section-4) - Available space
4444
* [COMB](https://help.globalscape.com/help/archive/eft6-4/mergedprojects/eft/allowingmultiparttransferscomb_command.htm) - Combine files
4545

4646
## Quick test
@@ -124,7 +124,7 @@ type Settings struct {
124124
ListenAddr string // Listening address
125125
PublicHost string // Public IP to expose (only an IP address is accepted at this stage)
126126
PublicIPResolver PublicIPResolver // (Optional) To fetch a public IP lookup
127-
PassiveTransferPortRange *PortRange // (Optional) Port Range for data connections. Random if not specified
127+
PassiveTransferPortRange PasvPortGetter // (Optional) Port Range for data connections. Random if not specified
128128
ActiveTransferPortNon20 bool // Do not impose the port 20 for active data transfer (#88, RFC 1579)
129129
IdleTimeout int // Maximum inactivity time before disconnecting (#58)
130130
ConnectionTimeout int // Maximum time to establish passive or active transfer connections

0 commit comments

Comments
 (0)