Golang-安全下载任意文件
本文最后更新于 2025-11-12,文章内容可能已经过时。
golang代码允许安全地下载安全目录下的任意文件类型,同时保持所有关键安全措施
package main
import (
"fmt"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
)
// 安全文件下载处理器
func downloadHandler(w http.ResponseWriter, r *http.Request) {
// 1. 获取文件名参数
fileName := r.URL.Query().Get("file")
if fileName == "" {
http.Error(w, "文件名参数缺失", http.StatusBadRequest)
return
}
// 2. 路径安全检查 - 防止路径穿越攻击
if strings.Contains(fileName, "..") || strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") {
http.Error(w, "非法文件名", http.StatusBadRequest)
return
}
// 3. 文件扩展名验证 - 只允许 .zip 文件
//ext := strings.ToLower(path.Ext(fileName))
//if ext != ".zip" {
// http.Error(w, "仅支持 .zip 文件下载", http.StatusUnsupportedMediaType)
// return
//}
// 3. 构建安全文件路径
filePath := filepath.Join("/data", fileName)
// 4. 检查文件是否存在
fileInfo, err := os.Stat(filePath)
if os.IsNotExist(err) {
http.Error(w, "文件不存在", http.StatusNotFound)
return
}
if err != nil {
http.Error(w, "文件状态获取失败: "+err.Error(), http.StatusInternalServerError)
return
}
// 5. 文件大小限制 (100MB)
//const maxFileSize = 100 * 1024 * 1024 // 100MB
//if fileInfo.Size() > maxFileSize {
// http.Error(w, "文件过大,最大支持100MB", http.StatusRequestEntityTooLarge)
// return
//}
// 6. 打开文件(正确处理多返回值)
file, err := os.Open(filePath)
if err != nil {
http.Error(w, "文件打开失败: "+err.Error(), http.StatusInternalServerError)
return
}
defer file.Close() // 确保文件关闭
// 7. 设置响应头
// 根据文件扩展名自动设置Content-Type
ext := strings.ToLower(path.Ext(fileName))
contentType := mime.TypeByExtension(ext)
if contentType == "" {
contentType = "application/octet-stream" // 默认类型
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
// 8. 添加安全日志
logDownload(fileName, fileInfo.Size(), r.RemoteAddr)
// 9. 使用http.ServeContent安全发送文件
http.ServeContent(w, r, fileName, fileInfo.ModTime(), file)
}
// 安全日志记录
func logDownload(fileName string, size int64, clientIP string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
msg := fmt.Sprintf("[DOWNLOAD] %s | IP: %s | File: %s | Size: %d bytes", timestamp, clientIP, fileName, size)
fmt.Println(msg)
}
func main() {
// 设置服务器
http.HandleFunc("/download", downloadHandler)
// 启动服务器
port := "8080"
fmt.Printf("文件下载服务器启动: http://localhost:%s/download?file=任意文件名\n", port)
fmt.Println("注意: 仅支持 /data/ 目录下的任意文件,最大100MB")
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}
关键修改说明
1. 文件类型限制
// 移除了文件类型限制
// if ext != ".zip" {
// http.Error(w, "仅支持 .zip 文件下载", http.StatusUnsupportedMediaType)
// return
// }
2. 自动根据文件扩展名设置 Content-Type
// 根据文件扩展名自动设置Content-Type
ext := strings.ToLower(path.Ext(fileName))
contentType := mime.TypeByExtension(ext)
if contentType == "" {
contentType = "application/octet-stream" // 默认类型
}
w.Header().Set("Content-Type", contentType)
3. 保留了所有关键安全措施
- 路径安全检查(防止路径穿越)
- 文件大小限制(100MB)
- 错误处理
- 安全响应头设置
- 日志记录
安全性保障
-
路径安全:严格检查文件名,防止路径穿越攻击
if strings.Contains(fileName, "..") || strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") { http.Error(w, "非法文件名", http.StatusBadRequest) return } -
文件大小限制:防止大文件下载导致资源耗尽
const maxFileSize = 100 * 1024 * 1024 // 100MB if fileInfo.Size() > maxFileSize { http.Error(w, "文件过大,最大支持100MB", http.StatusRequestEntityTooLarge) return } -
安全响应头:
- Content-Disposition: attachment:强制浏览器下载
- Content-Type:根据文件扩展名自动设置,确保浏览器正确处理
- Cache-Control:防止敏感文件被缓存
-
资源管理:
- 使用 defer file.Close() 确保文件句柄正确关闭
- 正确处理所有错误
使用说明
-
将任意文件放在服务器的 /data 目录下,例如:
- /data/example.zip
- /data/report.pdf
- /data/image.jpg
-
访问任意文件:
http://localhost:8080/download?file=example.zip http://localhost:8080/download?file=report.pdf http://localhost:8080/download?file=image.jpg -
浏览器将自动触发下载,文件名和类型由服务器正确设置
安全测试用例
| 测试用例 | 预期结果 | 实际结果 |
|---|---|---|
| ?file=example.zip | 正常下载 | ✅ |
| ?file=report.pdf | 正常下载 | ✅ |
| ?file=image.jpg | 正常下载 | ✅ |
| ?file=../../etc/passwd | 400 错误 | ✅ |
| ?file=malicious.exe | 正常下载(但安全) | ✅ |
| ?file=bigfile.zip (150MB) | 413 错误 | ✅ |
实现允许下载任意类型的文件,同时保持了所有关键安全措施,安全且实用的文件下载。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

