mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-21 23:33:42 +08:00
FIX #46
This commit is contained in:
@@ -31,12 +31,42 @@ func TestValidateATCommandBlocksTrafficMessagingAndDialActions(t *testing.T) {
|
||||
"AT+CSQ;+CMSS=7",
|
||||
"AT+CSQ;D12345;",
|
||||
} {
|
||||
if err := validateATCommand(command); err == nil {
|
||||
if err := validateATCommand(command, false); err == nil {
|
||||
t.Errorf("validateATCommand(%q) permitted a guarded mutation", command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateATCommandForceBypassesGuard(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, command := range []string{
|
||||
"AT+CGATT=1",
|
||||
"AT+CFUN=1",
|
||||
"AT+CGACT=1,1",
|
||||
"AT+CUSD=1,\"*100#\"",
|
||||
"ATD12345;",
|
||||
} {
|
||||
if err := validateATCommand(command, true); err != nil {
|
||||
t.Errorf("validateATCommand(%q, true): %v", command, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateATCommandForceKeepsSyntaxChecks(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, command := range []string{
|
||||
"A",
|
||||
"",
|
||||
"AT\r",
|
||||
"AT\n",
|
||||
string(make([]byte, 513)),
|
||||
} {
|
||||
if err := validateATCommand(command, true); err == nil {
|
||||
t.Errorf("validateATCommand(%q, true) skipped syntax check", command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateATCommandAllowsReadOnlyStatusQueries(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, command := range []string{
|
||||
@@ -48,7 +78,7 @@ func TestValidateATCommandAllowsReadOnlyStatusQueries(t *testing.T) {
|
||||
"AT+CIMI",
|
||||
"AT+CCID",
|
||||
} {
|
||||
if err := validateATCommand(command); err != nil {
|
||||
if err := validateATCommand(command, false); err != nil {
|
||||
t.Errorf("validateATCommand(%q): %v", command, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,13 +1045,14 @@ func (s *Server) handleAT(w http.ResponseWriter, r *http.Request, id string) boo
|
||||
var request struct {
|
||||
Command string `json:"cmd"`
|
||||
TimeoutMs int `json:"timeout_ms"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
if err := s.decodeJSON(w, r, &request); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
|
||||
return true
|
||||
}
|
||||
command := strings.TrimSpace(request.Command)
|
||||
if err := validateATCommand(command); err != nil {
|
||||
if err := validateATCommand(command, request.Force); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "unsafe_at_command", err.Error())
|
||||
return true
|
||||
}
|
||||
@@ -1100,7 +1101,7 @@ func (s *Server) handleAT(w http.ResponseWriter, r *http.Request, id string) boo
|
||||
return true
|
||||
}
|
||||
|
||||
func validateATCommand(command string) error {
|
||||
func validateATCommand(command string, force bool) error {
|
||||
upper := strings.ToUpper(command)
|
||||
if len(command) < 2 || len(command) > 512 || !strings.HasPrefix(upper, "AT") {
|
||||
return errors.New("AT command must start with AT and contain at most 512 characters")
|
||||
@@ -1108,6 +1109,9 @@ func validateATCommand(command string) error {
|
||||
if strings.ContainsAny(command, "\r\n\x00") {
|
||||
return errors.New("AT command must contain exactly one line")
|
||||
}
|
||||
if force {
|
||||
return nil
|
||||
}
|
||||
canonical := strings.NewReplacer(" ", "", "\t", "").Replace(upper)
|
||||
for _, blocked := range []string{
|
||||
`+QCFG="USBNET"`,
|
||||
|
||||
@@ -1981,7 +1981,7 @@ func (bot *telegramBot) handleATCommand(ctx context.Context, config telegramRunt
|
||||
|
||||
func (bot *telegramBot) executeATCommand(ctx context.Context, deviceID, command string) (string, error) {
|
||||
command = strings.TrimSpace(command)
|
||||
if err := validateATCommand(command); err != nil {
|
||||
if err := validateATCommand(command, false); err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, _, physicalID, err := bot.device(deviceID)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { WindowConsoleRegular, WarningRegular } from "@fluentui/react-icons";
|
||||
import { api } from "../../api";
|
||||
import { Button, Input, Select } from "../ui";
|
||||
import { Button, Input, Select, Switch } from "../ui";
|
||||
import { AT_COMMAND_GROUPS } from "./atCommands";
|
||||
import { AtLogEntry, AtTypingBubble, type AtLogItem } from "./AtLogEntry";
|
||||
import { useI18n } from "../../lib/i18n";
|
||||
@@ -19,6 +19,7 @@ export function DeviceAtTab({ deviceId, backendMode, atPort, running }: DeviceAt
|
||||
const [template, setTemplate] = useState("");
|
||||
const [timeoutMs, setTimeoutMs] = useState<number>(10000);
|
||||
const [sending, setSending] = useState(false);
|
||||
const [force, setForce] = useState(false);
|
||||
const [log, setLog] = useState<AtLogItem[]>([]);
|
||||
|
||||
const hasAtPort = String(atPort || "").trim().length > 0;
|
||||
@@ -40,7 +41,7 @@ export function DeviceAtTab({ deviceId, backendMode, atPort, running }: DeviceAt
|
||||
try {
|
||||
const res = await api<{ ok?: boolean; response?: string; result?: string }>(`/devices/${deviceId}/actions/at`, {
|
||||
method: "POST",
|
||||
body: { cmd: command, timeoutMs: timeoutMs || 10000 },
|
||||
body: { cmd: command, timeoutMs: timeoutMs || 10000, force },
|
||||
});
|
||||
setLog((prev) => [
|
||||
...prev,
|
||||
@@ -120,6 +121,13 @@ export function DeviceAtTab({ deviceId, backendMode, atPort, running }: DeviceAt
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-end gap-3">
|
||||
<div className="flex items-center gap-2 text-sm text-orange-600 dark:text-orange-400">
|
||||
<WarningRegular className="text-base" />
|
||||
<span>{t("强制模式允许发送默认被拦截的 AT 指令(如切网、拨号、短信、USSD),误操作可能导致断网或费用扣除。")}</span>
|
||||
</div>
|
||||
<Switch checked={force} onChange={setForce} ariaLabel={t("强制发送 AT 指令")} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="mt-4 flex flex-col items-center justify-center rounded-xl border border-orange-100 bg-orange-50 p-8 dark:border-orange-900/50 dark:bg-orange-900/20">
|
||||
|
||||
@@ -685,6 +685,8 @@ export const EN_DICT: Record<string, string> = {
|
||||
"AT 终端暂不可用": "AT terminal unavailable",
|
||||
"AT=串口 / QMI=纯 QMI": "AT=serial / QMI=pure QMI",
|
||||
"AT=传统串口 / QMI=纯 QMI": "AT=legacy serial / QMI=pure QMI",
|
||||
"强制发送 AT 指令": "Force-send AT command",
|
||||
"强制模式允许发送默认被拦截的 AT 指令(如切网、拨号、短信、USSD),误操作可能导致断网或费用扣除。": "Force mode allows sending AT commands that are normally blocked (e.g. mode switching, dialing, SMS, USSD). Mistakes may disconnect the network or incur charges.",
|
||||
"E911地址": "E911 Address",
|
||||
"E911地址设置页面打开失败": "Failed to open the E911 address setup page",
|
||||
"IMEI 绑定": "IMEI Binding",
|
||||
|
||||
Reference in New Issue
Block a user