|
|
||
|---|---|---|
| .. | ||
| README.md | ||
| fileserver.go | ||
README.md
Fileserver Package
Overview
The fileserver package handles all binary file serving for Memos using native HTTP handlers. It was created to replace gRPC-based binary serving, which had limitations with HTTP range requests (required for Safari video/audio playback).
Responsibilities
- Serve attachment binary files (images, videos, audio, documents)
- Serve user avatar images
- Handle HTTP range requests for video/audio streaming
- Authenticate requests using session cookies or JWT tokens
- Check permissions for private content
- Generate and serve image thumbnails
- Prevent XSS attacks on uploaded content
- Support S3 external storage
Architecture
Design Principles
- Separation of Concerns: Binary files via HTTP, metadata via gRPC
- DRY: Imports auth constants from
api/v1package (single source of truth) - Security First: Authentication, authorization, and XSS prevention
- Performance: Native HTTP streaming with proper caching headers
Package Structure
fileserver/
├── fileserver.go # Main service and HTTP handlers
├── README.md # This file
└── fileserver_test.go # Tests (to be added)
API Endpoints
1. Attachment Binary
GET /file/attachments/:uid/:filename[?thumbnail=true]
Parameters:
uid- Attachment unique identifierfilename- Original filenamethumbnail(optional) - Return thumbnail for images
Authentication: Required for non-public memos
Response:
200 OK- File content with proper Content-Type206 Partial Content- For range requests (video/audio)401 Unauthorized- Authentication required403 Forbidden- User not authorized404 Not Found- Attachment not found
Headers:
Content-Type- MIME type of the fileCache-Control: public, max-age=3600Accept-Ranges: bytes- For video/audioContent-Range- For partial responses (206)
2. User Avatar
GET /file/users/:identifier/avatar
Parameters:
identifier- User ID (e.g.,1) or username (e.g.,steven)
Authentication: Not required (avatars are public)
Response:
200 OK- Avatar image (PNG/JPEG)404 Not Found- User not found or no avatar set
Headers:
Content-Type- image/png or image/jpegCache-Control: public, max-age=3600
Authentication
Supported Methods
The fileserver supports two authentication methods, checked in order:
-
Session Cookie (
user_session)- Cookie format:
{userID}-{sessionID} - Validates session exists and hasn't expired (14-day sliding window)
- Updates last accessed time on success
- Cookie format:
-
JWT Bearer Token (
Authorization: Bearer {token})- Validates JWT signature using server secret
- Checks token exists in user's access tokens (for revocation)
- Extracts user ID from token claims
Authentication Flow
Request → getCurrentUser()
├─→ Try Session Cookie
│ ├─→ Parse cookie value
│ ├─→ Get user from DB
│ ├─→ Validate session
│ └─→ Return user (if valid)
│
└─→ Try JWT Token
├─→ Parse Authorization header
├─→ Verify JWT signature
├─→ Get user from DB
├─→ Validate token in access tokens list
└─→ Return user (if valid)
Permission Model
Attachments:
- Unlinked: Public (no auth required)
- Public memo: Public (no auth required)
- Protected memo: Requires authentication
- Private memo: Creator only
Avatars:
- Always public (no auth required)
Key Functions
HTTP Handlers
serveAttachmentFile(c echo.Context) error
Main handler for attachment binary serving.
Flow:
- Extract UID from URL parameter
- Fetch attachment from database
- Check permissions (memo visibility)
- Get binary blob (local file, S3, or database)
- Handle thumbnail request (if applicable)
- Set security headers (XSS prevention)
- Serve with range request support (video/audio)
serveUserAvatar(c echo.Context) error
Main handler for user avatar serving.
Flow:
- Extract identifier (ID or username) from URL
- Lookup user in database
- Check if avatar exists
- Decode base64 data URI
- Serve with proper content type and caching
Authentication
getCurrentUser(ctx, c) (*store.User, error)
Authenticates request using session cookie or JWT token.
authenticateBySession(ctx, cookie) (*store.User, error)
Validates session cookie and returns authenticated user.
authenticateByJWT(ctx, token) (*store.User, error)
Validates JWT access token and returns authenticated user.
Permission Checks
checkAttachmentPermission(ctx, c, attachment) error
Validates user has permission to access attachment based on memo visibility.
File Operations
getAttachmentBlob(attachment) ([]byte, error)
Retrieves binary content from local storage, S3, or database.
getOrGenerateThumbnail(ctx, attachment) ([]byte, error)
Returns cached thumbnail or generates new one (with semaphore limiting).
Utilities
getUserByIdentifier(ctx, identifier) (*store.User, error)
Finds user by ID (int) or username (string).
extractImageInfo(dataURI) (type, base64, error)
Parses data URI to extract MIME type and base64 data.
Dependencies
External Packages
github.com/labstack/echo/v4- HTTP router and middlewaregithub.com/golang-jwt/jwt/v5- JWT parsing and validationgithub.com/disintegration/imaging- Image thumbnail generationgolang.org/x/sync/semaphore- Concurrency control for thumbnails
Internal Packages
server/router/api/v1- Auth constants (SessionCookieName, ClaimsMessage, etc.)store- Database operationsinternal/profile- Server configurationplugin/storage/s3- S3 storage client
Configuration
Constants
All auth-related constants are imported from server/router/api/v1/auth.go:
apiv1.SessionCookieName- "user_session"apiv1.SessionSlidingDuration- 14 daysapiv1.KeyID- "v1" (JWT key identifier)apiv1.ClaimsMessage- JWT claims struct
Package-specific constants:
ThumbnailCacheFolder- ".thumbnail_cache"thumbnailMaxSize- 600pxSupportedThumbnailMimeTypes- ["image/png", "image/jpeg"]
Error Handling
All handlers return Echo HTTP errors with appropriate status codes:
// Bad request
echo.NewHTTPError(http.StatusBadRequest, "message")
// Unauthorized (no auth)
echo.NewHTTPError(http.StatusUnauthorized, "message")
// Forbidden (auth but no permission)
echo.NewHTTPError(http.StatusForbidden, "message")
// Not found
echo.NewHTTPError(http.StatusNotFound, "message")
// Internal error
echo.NewHTTPError(http.StatusInternalServerError, "message").SetInternal(err)
Security Considerations
1. XSS Prevention
SVG and HTML files are served as application/octet-stream to prevent script execution:
if contentType == "image/svg+xml" ||
contentType == "text/html" ||
contentType == "application/xhtml+xml" {
contentType = "application/octet-stream"
}
2. Authentication
Private content requires valid session or JWT token.
3. Authorization
Memo visibility rules enforced before serving attachments.
4. Input Validation
- Attachment UID validated from database
- User identifier validated (ID or username)
- Range requests validated before processing
Performance Optimizations
1. Thumbnail Caching
Thumbnails cached on disk to avoid regeneration:
- Cache location:
{data_dir}/.thumbnail_cache/ - Filename:
{attachment_id}{extension} - Semaphore limits concurrent generation (max 3)
2. HTTP Range Requests
Video/audio files use http.ServeContent() for efficient streaming:
- Automatic range parsing
- Efficient memory usage (streaming, not loading full file)
- Safari-compatible partial content responses
3. Caching Headers
All responses include cache headers:
Cache-Control: public, max-age=3600
4. S3 External Links
S3 files served via presigned URLs (no server download).
Testing
Unit Tests (To Add)
See SAFARI_FIX.md for recommended test coverage.
Manual Testing
# Test attachment
curl "http://localhost:8081/file/attachments/{uid}/file.jpg"
# Test avatar by ID
curl "http://localhost:8081/file/users/1/avatar"
# Test avatar by username
curl "http://localhost:8081/file/users/steven/avatar"
# Test range request
curl -H "Range: bytes=0-999" "http://localhost:8081/file/attachments/{uid}/video.mp4"
Future Improvements
See SAFARI_FIX.md section "Future Improvements" for planned enhancements.
Related Documentation
- SAFARI_FIX.md - Full migration guide
- server/router/api/v1/auth.go - Auth constants source of truth
- RFC 7233 - HTTP Range Requests spec