diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 9f823dde9d06..b2a953d11591 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'Bad request: {{ .detail }}' ErrTokenParse: 'Token error: {{ .detail }}' ErrInitialPassword: 'Original password is incorrect' ErrInternalServer: 'Internal server error: {{ .detail }}' +ErrWgetRemoteFailed: 'Remote download failed or URL is unreachable: {{ .detail }}' +ErrWgetInvalidContentType: 'Remote URL did not return a file (possibly an error page), {{ .detail }}' ErrRecordExist: 'Record already exists' ErrRecordNotFound: 'Record not found' ErrStructTransform: 'Type conversion failed: {{ .err }}' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 6b833a0f6163..585d176c0bd4 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'Solicitud inválida: {{ .detail }}' ErrTokenParse: 'Token inválido: {{ .detail }}' ErrInitialPassword: 'Contraseña original incorrecta' ErrInternalServer: 'Error interno: {{ .detail }}' +ErrWgetRemoteFailed: 'La descarga remota falló o la URL no es accesible: {{ .detail }}' +ErrWgetInvalidContentType: 'La URL remota no devolvió un archivo (posiblemente una página de error), {{ .detail }}' ErrRecordExist: 'Registro ya existe' ErrRecordNotFound: 'Registro no encontrado' ErrStructTransform: 'Error de conversión: {{ .err }}' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 9390d4915a59..ec8749a215dc 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: '不正なリクエスト: {{ .detail }}' ErrTokenParse: 'トークンエラー: {{ .detail }}' ErrInitialPassword: '元のパスワードが違います' ErrInternalServer: '内部エラー: {{ .detail }}' +ErrWgetRemoteFailed: 'リモートのダウンロード URL が無効か、到達できません:{{ .detail }}' +ErrWgetInvalidContentType: 'リモート URL がファイルではなくエラーページ等を返しました、{{ .detail }}' ErrRecordExist: 'レコードが既に存在します' ErrRecordNotFound: 'レコードが見つかりません' ErrStructTransform: '型変換エラー: {{ .err }}' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index fbe1d4ba10b6..a34cb7a2e407 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: '잘못된 요청: {{ .detail }}' ErrTokenParse: '토큰 오류: {{ .detail }}' ErrInitialPassword: '원래 비밀번호가 틀립니다' ErrInternalServer: '서버 오류: {{ .detail }}' +ErrWgetRemoteFailed: '원격 다운로드 주소가 잘못되었거나 접근할 수 없습니다: {{ .detail }}' +ErrWgetInvalidContentType: '원격 주소가 파일이 아닌 콘텐츠를 반환했습니다 (오류 페이지일 수 있음), {{ .detail }}' ErrRecordExist: '레코드가 이미 있습니다' ErrRecordNotFound: '레코드를 찾을 수 없습니다' ErrStructTransform: '형 변환 실패: {{ .err }}' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 917b5c3afd9f..9b44c02dc496 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'Permintaan tidak sah: {{ .detail }}' ErrTokenParse: 'Token tidak sah: {{ .detail }}' ErrInitialPassword: 'Kata laluan asal salah' ErrInternalServer: 'Ralat pelayan: {{ .detail }}' +ErrWgetRemoteFailed: 'Muat turun jauh gagal atau URL tidak boleh diakses: {{ .detail }}' +ErrWgetInvalidContentType: 'URL jauh tidak mengembalikan fail (mungkin halaman ralat), {{ .detail }}' ErrRecordExist: 'Rekod sudah wujud' ErrRecordNotFound: 'Rekod tidak ditemui' ErrStructTransform: 'Ralat penukaran: {{ .err }}' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 4c9c07431a1d..87a4d9aa69c4 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'Requisição inválida: {{ .detail }}' ErrTokenParse: 'Erro de token: {{ .detail }}' ErrInitialPassword: 'Senha original incorreta' ErrInternalServer: 'Erro interno: {{ .detail }}' +ErrWgetRemoteFailed: 'Falha no download remoto ou URL inacessível: {{ .detail }}' +ErrWgetInvalidContentType: 'A URL remota não retornou um arquivo (possivelmente uma página de erro), {{ .detail }}' ErrRecordExist: 'Registro já existe' ErrRecordNotFound: 'Registro não encontrado' ErrStructTransform: 'Erro de conversão: {{ .err }}' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 6834850a19a6..99394d31feb3 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'Неверный запрос: {{ .detail }}' ErrTokenParse: 'Ошибка токена: {{ .detail }}' ErrInitialPassword: 'Исходный пароль неверен' ErrInternalServer: 'Внутренняя ошибка: {{ .detail }}' +ErrWgetRemoteFailed: 'Неверный или недоступный URL удалённой загрузки: {{ .detail }}' +ErrWgetInvalidContentType: 'Удалённый сервер вернул не файл (возможно, страницу ошибки), {{ .detail }}' ErrRecordExist: 'Запись уже существует' ErrRecordNotFound: 'Запись не найдена' ErrStructTransform: 'Ошибка преобразования: {{ .err }}' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 923da8d06bf0..e2f85786a740 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: 'İstek geçersiz: {{ .detail }}' ErrTokenParse: 'Token hatası: {{ .detail }}' ErrInitialPassword: 'Orijinal şifre yanlış' ErrInternalServer: 'Sunucu hatası: {{ .detail }}' +ErrWgetRemoteFailed: 'Uzak indirme başarısız veya URL erişilemiyor: {{ .detail }}' +ErrWgetInvalidContentType: 'Uzak URL bir dosya değil, muhtemelen bir hata sayfası döndürdü, {{ .detail }}' ErrRecordExist: 'Kayıt zaten var' ErrRecordNotFound: 'Kayıt bulunamadı' ErrStructTransform: 'Dönüşüm hatası: {{ .err }}' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index ad7f778f54c7..4f04bfc7e18c 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: '請求參數錯誤: {{ .detail }}' ErrTokenParse: 'Token 產生錯誤: {{ .detail }}' ErrInitialPassword: '原密碼錯誤' ErrInternalServer: '服務內部錯誤: {{ .detail }}' +ErrWgetRemoteFailed: '遠端下載網址無效或無法存取:{{ .detail }}' +ErrWgetInvalidContentType: '遠端網址未回傳檔案內容(可能是錯誤頁),{{ .detail }}' ErrRecordExist: '記錄已存在' ErrRecordNotFound: '記錄未能找到' ErrStructTransform: '型別轉換失敗: {{ .err }}' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index bfd6784ba05a..3fb9bdf4e533 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -2,6 +2,8 @@ ErrInvalidParams: "参数错误: {{ .detail }}" ErrTokenParse: "Token 错误: {{ .detail }}" ErrInitialPassword: "原密码错误" ErrInternalServer: "服务错误: {{ .detail }}" +ErrWgetRemoteFailed: "远程下载地址无效或无法访问:{{ .detail }}" +ErrWgetInvalidContentType: "远程地址返回的不是文件内容(可能是错误页),{{ .detail }}" ErrRecordExist: "记录已存在" ErrRecordNotFound: "记录不存在" ErrStructTransform: "类型转换失败: {{ .err }}" diff --git a/agent/utils/files/file_op.go b/agent/utils/files/file_op.go index 3788f509a04d..3ff7b6781452 100644 --- a/agent/utils/files/file_op.go +++ b/agent/utils/files/file_op.go @@ -397,14 +397,32 @@ func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate request, err := http.NewRequest("GET", url, nil) if err != nil { - return nil + return buserr.WithDetail("ErrWgetRemoteFailed", err.Error(), err) } request.Header.Set("Accept-Encoding", "identity") resp, err := client.Do(request) if err != nil { global.LOG.Errorf("get download file [%s] error, err %s", dst, err.Error()) - return err + return buserr.WithDetail("ErrWgetRemoteFailed", err.Error(), err) + } + + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64*1024)) + _ = resp.Body.Close() + global.LOG.Errorf("wget remote returned non-success status %s for url %s", resp.Status, url) + return buserr.WithDetail("ErrWgetRemoteFailed", resp.StatusCode, nil) + } + + ct := strings.ToLower(resp.Header.Get("Content-Type")) + dstExt := strings.ToLower(filepath.Ext(dst)) + if (strings.Contains(ct, "text/html") || strings.Contains(ct, "text/xml")) && + dstExt != ".html" && dstExt != ".htm" && dstExt != ".xml" && dstExt != ".svg" { + _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64*1024)) + _ = resp.Body.Close() + detail := fmt.Sprintf("Content-Type: %s", ct) + global.LOG.Errorf("wget got html/xml response for non-html file %s, url %s, %s", dst, url, detail) + return buserr.WithDetail("ErrWgetInvalidContentType", detail, nil) } out, err := os.Create(dst) diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 95e7ad63dcc9..76f9dbb18d82 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1642,6 +1642,7 @@ const message = { selectFile: 'Select file', downloadUrl: 'Remote URL', downloadStart: 'Download started', + wgetUrlInvalid: 'Please enter a valid http(s) download URL', moveSuccess: 'Successfully moved', copySuccess: 'Successfully copied', pasteMsg: 'Click the [Paste] button at the top right of the target directory', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 3b76b45f852a..52e8a4e1ac3a 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1674,6 +1674,7 @@ const message = { selectFile: 'Seleccionar archivo', downloadUrl: 'URL remota', downloadStart: 'Descarga iniciada', + wgetUrlInvalid: 'Introduzca una URL de descarga http(s) válida', moveSuccess: 'Movido correctamente', copySuccess: 'Copiado correctamente', pasteMsg: 'Por favor haz clic en el botón [Pegar] en la parte superior derecha del directorio de destino', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index fc304189f05a..95c8e7b8a5e5 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1653,6 +1653,7 @@ const message = { selectFile: '[ファイル]を選択します', downloadUrl: 'リモートURL', downloadStart: 'ダウンロードが始まりました', + wgetUrlInvalid: '有効な http(s) のダウンロード URL を入力してください', moveSuccess: '正常に移動しました', copySuccess: '正常にコピーされました', pasteMsg: '対象ディレクトリの右上にある「貼り付け」ボタンをクリックしてください', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index eedd782c1f16..7b0d4d5277bf 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1622,6 +1622,7 @@ const message = { selectFile: '파일 선택', downloadUrl: '원격 URL', downloadStart: '다운로드 시작됨', + wgetUrlInvalid: '유효한 http(s) 다운로드 주소를 입력하세요', moveSuccess: '이동 성공', copySuccess: '복사 성공', pasteMsg: '대상 디렉토리의 오른쪽 상단에 있는 [붙여넣기] 버튼을 클릭하세요', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 3398da21f9b3..83f320c529a9 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1677,6 +1677,7 @@ const message = { selectFile: 'Pilih fail', downloadUrl: 'URL Jarak Jauh', downloadStart: 'Muat turun bermula', + wgetUrlInvalid: 'Masukkan URL muat turun http(s) yang sah', moveSuccess: 'Berjaya dipindahkan', copySuccess: 'Berjaya disalin', pasteMsg: 'Sila klik butang "Tampal" di bahagian kanan atas direktori sasaran', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 87a667418a7b..7ee23e343309 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1790,6 +1790,7 @@ const message = { selectFile: 'Selecionar arquivo', downloadUrl: 'URL remota', downloadStart: 'Download iniciado', + wgetUrlInvalid: 'Informe um URL de download http(s) válido', moveSuccess: 'Movido com sucesso', copySuccess: 'Copiado com sucesso', pasteMsg: 'Clique no botão "Colar" no canto superior direito do diretório de destino', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 2f594487b14c..592f46dde108 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1665,6 +1665,7 @@ const message = { selectFile: 'Выбрать файл', downloadUrl: 'Удаленный URL', downloadStart: 'Загрузка начата', + wgetUrlInvalid: 'Введите корректный URL загрузки (http/https)', moveSuccess: 'Успешно перемещено', copySuccess: 'Успешно скопировано', pasteMsg: 'Нажмите кнопку «Вставить» в правом верхнем углу целевой директории', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index ce45d63ecc98..82a8121108e2 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1668,6 +1668,7 @@ const message = { selectFile: 'Dosya seç', downloadUrl: 'Uzak URL', downloadStart: 'İndirme başladı', + wgetUrlInvalid: 'Geçerli bir http(s) indirme adresi girin', moveSuccess: 'Başarıyla taşındı', copySuccess: 'Başarıyla kopyalandı', pasteMsg: 'Hedef dizinin sağ üst köşesindeki "Yapıştır" düğmesine tıklayın', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index ec794a775bd0..1d89f4325161 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1550,6 +1550,7 @@ const message = { downloadSuccess: '下載成功', downloadUrl: '下載網址', downloadStart: '下載開始!', + wgetUrlInvalid: '請輸入有效的 http(s) 下載網址', moveSuccess: '移動成功', copySuccess: '複製成功', pasteMsg: '請在目標目錄點選右上角【貼上】按鈕', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8c17607e54f9..c1aa7191f435 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1551,6 +1551,7 @@ const message = { downloadSuccess: '下载成功', downloadUrl: '下载地址', downloadStart: '下载开始!', + wgetUrlInvalid: '请输入有效的 http(s) 下载地址', moveSuccess: '移动成功', copySuccess: '复制成功', pasteMsg: '请在目标目录点击右上角【粘贴】按钮', diff --git a/frontend/src/views/host/file-management/wget/index.vue b/frontend/src/views/host/file-management/wget/index.vue index 599779e6e346..a3cc9f8773b1 100644 --- a/frontend/src/views/host/file-management/wget/index.vue +++ b/frontend/src/views/host/file-management/wget/index.vue @@ -59,10 +59,28 @@ let open = ref(false); let submitData = ref(false); const fileRef = ref(); +const validateWgetUrl = (_rule: unknown, value: string, callback: (e?: Error) => void) => { + const v = (value || '').trim(); + if (!v) { + callback(); + return; + } + try { + const u = new URL(v); + if (u.protocol !== 'http:' && u.protocol !== 'https:') { + callback(new Error(i18n.global.t('file.wgetUrlInvalid'))); + return; + } + callback(); + } catch { + callback(new Error(i18n.global.t('file.wgetUrlInvalid'))); + } +}; + const rules = reactive({ name: [Rules.requiredInput], path: [Rules.requiredInput], - url: [Rules.requiredInput], + url: [Rules.requiredInput, { validator: validateWgetUrl, trigger: 'blur' }], }); const addForm = reactive({ @@ -99,6 +117,9 @@ const submit = async (formEl: FormInstance | undefined) => { submitData.value = true; handleClose(); }) + .catch(() => { + submitData.value = false; + }) .finally(() => { loading.value = false; });