mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-01 15:14:56 +00:00
core: fix /start remote host parser when iface name contains a space (#7025)
* core: fix /start remote host parser when iface name contains a space The iface= field used jsonP (which calls takeByteString and strict-decodes the entire remaining input as JSON). When port= followed iface=, the strict decode failed on the trailing data and the text1P fallback stopped at the first space inside the JSON-quoted interface name (e.g. "Ethernet 2"), leaving unparseable junk and producing "Failed reading: empty". Replace jsonP with a bounded quotedP that consumes only up to the closing quote, leaving port=… for the next parser. * plan: document fix for /start remote host iface-with-space parser bug
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Fix `/start remote host` parser when iface name contains a space
|
||||
|
||||
## Problem
|
||||
|
||||
Picking a non-default interface (e.g. Windows `Ethernet 2`) on the "Link a mobile" screen and refreshing the QR code returns `error chat: error chat commandError Failed reading: empty`. The desktop UI sends `/start remote host new addr=… iface="Ethernet 2" port=…`; the chat backend rejects it as an unparseable command. Without a workaround the user can't pin a specific local interface for the mobile-link controller.
|
||||
|
||||
## Cause
|
||||
|
||||
`rcCtrlAddressP` parses the iface value with `jsonP <|> text1P` (`src/Simplex/Chat/Library/Commands.hs:5549`). `jsonP` calls `A.takeByteString`, consuming *all* remaining input, then runs `eitherDecodeStrict'`. When `port=…` follows `iface=…` the strict decode fails because the JSON value `"Ethernet 2"` has trailing junk after it, so attoparsec backtracks to `text1P` (`takeTill (== ' ')`). `text1P` stops at the first space — inside the JSON quotes — leaving `2" port=12345` which nothing downstream can consume, `A.endOfInput` fails, the whole `A.choice` exhausts and surfaces attoparsec's `empty` message. With an iface name that has no space (`"lo"`) the bug is invisible: text1P swallows the full quoted token and the rest parses, but the interface name is stored with literal quotes so the iface preference silently never matches a real adapter anyway.
|
||||
|
||||
## Fix
|
||||
|
||||
Replace `jsonP` with a bounded `quotedP` that consumes only the bytes between `"…"` and leaves trailing fields for the next parser. `text1P` is kept as the unquoted fallback. Two-line change in `Commands.hs` plus a pure regression test in `tests/RemoteTests.hs` that asserts `parseChatCommand` of `/start remote host new addr=192.168.1.5 iface="Ethernet 2" port=12345` produces `RCCtrlAddress _ "Ethernet 2"` with port `12345`.
|
||||
@@ -5526,7 +5526,8 @@ chatCommandP =
|
||||
addressAA = AddressSettings False <$> (Just . AutoAccept <$> (" incognito=" *> onOffP <|> pure False)) <*> autoReply
|
||||
businessAA = " business" *> (AddressSettings True (Just $ AutoAccept False) <$> autoReply)
|
||||
autoReply = optional (A.space *> msgContentP)
|
||||
rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P))
|
||||
rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (quotedP <|> text1P))
|
||||
quotedP = safeDecodeUtf8 <$> (A.char '"' *> A.takeTill (== '"') <* A.char '"')
|
||||
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
|
||||
char_ = optional . A.char
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.List (find, isPrefixOf)
|
||||
import qualified Data.Map.Strict as M
|
||||
import Simplex.Chat.Controller (ChatConfig (..), versionNumber)
|
||||
import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), versionNumber)
|
||||
import Simplex.Chat.Library.Commands (parseChatCommand)
|
||||
import qualified Simplex.Chat.Controller as Controller
|
||||
import Simplex.Chat.Mobile.File
|
||||
import Simplex.Chat.Remote (remoteFilesFolder)
|
||||
@@ -24,6 +25,7 @@ import Simplex.Chat.Remote.Types
|
||||
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
|
||||
import Simplex.Messaging.Encoding.String (strEncode)
|
||||
import Simplex.Messaging.Util
|
||||
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
|
||||
import System.FilePath ((</>))
|
||||
import Test.Hspec hiding (it)
|
||||
import UnliftIO
|
||||
@@ -32,6 +34,12 @@ import UnliftIO.Directory
|
||||
|
||||
remoteTests :: SpecWith TestParams
|
||||
remoteTests = describe "Remote" $ do
|
||||
describe "/start remote host parser" $ do
|
||||
it "parses iface name with a space followed by port=" $ \_ ->
|
||||
parseChatCommand "/start remote host new addr=192.168.1.5 iface=\"Ethernet 2\" port=12345"
|
||||
`shouldSatisfy` \case
|
||||
Right (StartRemoteHost Nothing (Just (RCCtrlAddress _ "Ethernet 2")) (Just 12345)) -> True
|
||||
_ -> False
|
||||
xdescribe "No compression" $ aroundWith (. ((False, False),)) runRemoteTests
|
||||
xdescribe "Mobile offers compression" $ aroundWith (. ((True, False),)) runRemoteTests
|
||||
xdescribe "Desktop offers compression" $ aroundWith (. ((False, True),)) runRemoteTests
|
||||
|
||||
Reference in New Issue
Block a user