Add file upload functionality to the agent. Implement API endpoint for uploading files, including validation and error handling. Update web interface to support file selection and display upload status. Enhance input handling with configurable key delay for text input.

This commit is contained in:
2026-08-28 12:52:26 +03:00
parent d7274f34e0
commit fe560c6a65
16 changed files with 408 additions and 24 deletions
+70 -2
View File
@@ -44,6 +44,7 @@ func (a *Agent) handleOpenAPI(w http.ResponseWriter, r *http.Request) {
"/api/v1/status": map[string]any{"get": map[string]string{"summary": "Agent status"}},
"/api/v1/files": map[string]any{"get": map[string]string{"summary": "List files"}},
"/api/v1/download": map[string]any{"get": map[string]string{"summary": "Download file"}},
"/api/v1/upload": map[string]any{"post": map[string]string{"summary": "Upload a file to a directory"}},
"/api/v1/screenshot": map[string]any{"get": map[string]string{"summary": "Capture desktop"}},
"/api/v1/exec": map[string]any{"post": map[string]string{"summary": "Run a command"}},
"/api/v1/startup": map[string]any{"post": map[string]string{"summary": "Add to Windows startup"}, "delete": map[string]string{"summary": "Remove from Windows startup"}},
@@ -170,6 +171,65 @@ func (a *Agent) handleDownload(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, name, info.ModTime(), f)
}
func (a *Agent) handleUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
r.Body = http.MaxBytesReader(w, r.Body, config.MaxUploadSize)
if err := r.ParseMultipartForm(config.MaxUploadSize); err != nil {
helpers.WriteError(w, http.StatusBadRequest, "upload body is too large or invalid")
return
}
dir := strings.TrimSpace(r.FormValue("path"))
if dir == "" {
helpers.WriteError(w, http.StatusBadRequest, "path is required")
return
}
upload, header, err := r.FormFile("file")
if err != nil {
helpers.WriteError(w, http.StatusBadRequest, "file is required")
return
}
defer upload.Close()
target, err := files.UploadTarget(a.root, dir, header.Filename)
if err != nil {
switch {
case errors.Is(err, files.ErrBadUploadName):
helpers.WriteError(w, http.StatusBadRequest, err.Error())
default:
files.WritePathError(w, err)
}
return
}
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
if err != nil {
files.WritePathError(w, err)
return
}
written, err := io.Copy(out, upload)
closeErr := out.Close()
if err != nil {
helpers.Log.Printf("upload: %v", err)
helpers.WriteError(w, http.StatusInternalServerError, "could not save file")
return
}
if closeErr != nil {
helpers.Log.Printf("upload close: %v", closeErr)
helpers.WriteError(w, http.StatusInternalServerError, "could not save file")
return
}
helpers.WriteJSON(w, http.StatusOK, map[string]any{
"ok": true,
"path": target,
"size": written,
"name": filepath.Base(target),
})
}
func (a *Agent) handleScreenshot(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
@@ -281,12 +341,20 @@ func (a *Agent) handleText(w http.ResponseWriter, r *http.Request) {
helpers.WriteError(w, http.StatusBadRequest, "text is too long")
return
}
if err := input.TypeText(request.Text); err != nil {
delayMs := config.DefaultKeyDelayMs
if request.DelayMs != nil {
delayMs = input.ResolveKeyDelay(*request.DelayMs)
}
if err := input.TypeText(request.Text, delayMs); err != nil {
helpers.Log.Printf("text: %v", err)
helpers.WriteError(w, http.StatusInternalServerError, "could not type text")
return
}
helpers.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "length": len(request.Text)})
helpers.WriteJSON(w, http.StatusOK, map[string]any{
"ok": true,
"length": len(request.Text),
"delay_ms": delayMs,
})
}
func (a *Agent) handleExec(w http.ResponseWriter, r *http.Request) {