s3tables: improve robustness, security, and error propagation in handlers

- Implement strict table name validation (prevention of path traversal and character enforcement)
- Add nil checks for entry.Entry in all listing loops to prevent panics
- Propagate backend errors instead of swallowing them or assuming 404
- Correctly map filer_pb.ErrNotFound to appropriate S3 error codes
- Standardize existence checks across bucket, namespace, and table handlers
This commit is contained in:
Chris Lu
2026-01-28 11:37:02 -08:00
parent da15ee3e49
commit 62a1178a0b
5 changed files with 96 additions and 54 deletions

View File

@@ -50,31 +50,34 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R
// Check if table bucket exists
bucketPath := getTableBucketPath(bucketName)
var bucketExists bool
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
_, err := h.getExtendedAttribute(r.Context(), client, bucketPath, ExtendedKeyMetadata)
bucketExists = err == nil
return nil
return err
})
if !bucketExists {
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, fmt.Sprintf("table bucket %s not found", bucketName))
return fmt.Errorf("bucket not found")
if err != nil {
if errors.Is(err, filer_pb.ErrNotFound) {
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, fmt.Sprintf("table bucket %s not found", bucketName))
} else {
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to check table bucket: %v", err))
}
return err
}
namespacePath := getNamespacePath(bucketName, namespaceName)
// Check if namespace already exists
exists := false
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
_, err := h.getExtendedAttribute(r.Context(), client, namespacePath, ExtendedKeyMetadata)
exists = err == nil
return nil
return err
})
if exists {
if err == nil {
h.writeError(w, http.StatusConflict, ErrCodeNamespaceAlreadyExists, fmt.Sprintf("namespace %s already exists", namespaceName))
return fmt.Errorf("namespace already exists")
} else if !errors.Is(err, filer_pb.ErrNotFound) {
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to check namespace: %v", err))
return err
}
// Create the namespace
@@ -231,10 +234,13 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re
if respErr != nil {
break
}
if entry.Entry == nil {
continue
}
hasMore = true
lastFileName = entry.Entry.Name
if entry.Entry == nil || !entry.Entry.IsDirectory {
if !entry.Entry.IsDirectory {
continue
}
@@ -278,7 +284,12 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re
})
if err != nil {
namespaces = []NamespaceSummary{}
if errors.Is(err, filer_pb.ErrNotFound) {
namespaces = []NamespaceSummary{}
} else {
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to list namespaces: %v", err))
return err
}
}
resp := &ListNamespacesResponse{
@@ -349,7 +360,7 @@ func (h *S3TablesHandler) handleDeleteNamespace(w http.ResponseWriter, r *http.R
})
if err != nil {
if !errors.Is(err, ErrNotFound) {
if !errors.Is(err, filer_pb.ErrNotFound) {
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to list namespace entries: %v", err))
return err
}