@@ -655,9 +655,11 @@ async def run_client() -> None:
655655
656656 always_will : set [bytes ] = args ["always_will" ]
657657 always_do : set [bytes ] = args ["always_do" ]
658+ always_wont : set [bytes ] = args ["always_wont" ]
659+ always_dont : set [bytes ] = args ["always_dont" ]
658660
659- # Wrap client factory to inject always_will/always_do and encoding
660- # flags before negotiation starts.
661+ # Wrap client factory to inject always_will/always_do/always_wont/always_dont
662+ # and encoding flags before negotiation starts.
661663 encoding_explicit = args ["encoding" ] not in ("utf8" , "utf-8" , False )
662664 gmcp_modules : Optional [List [str ]] = args .get ("gmcp_modules" )
663665
@@ -675,6 +677,10 @@ def _patched_connection_made(transport: asyncio.BaseTransport) -> None:
675677 if always_will :
676678 client .writer .always_will = always_will
677679 client .writer .always_do = always_do
680+ if always_wont :
681+ client .writer .always_wont = always_wont
682+ if always_dont :
683+ client .writer .always_dont = always_dont
678684 from .telopt import GMCP as _GMCP
679685
680686 client .writer .passive_do = {_GMCP }
@@ -779,14 +785,32 @@ def _get_argument_parser() -> argparse.ArgumentParser:
779785 action = "append" ,
780786 default = [],
781787 metavar = "OPT" ,
782- help = "always send DO for this option (name like GMCP or number, repeatable)" ,
788+ help = "always send DO for this option (comma-separated, named like GMCP"
789+ " or numeric like 201, repeatable)" ,
790+ )
791+ parser .add_argument (
792+ "--always-dont" ,
793+ action = "append" ,
794+ default = [],
795+ metavar = "OPT" ,
796+ help = "always send DONT for this option, refusing even natively supported"
797+ " options (comma-separated, named or numeric, repeatable)" ,
783798 )
784799 parser .add_argument (
785800 "--always-will" ,
786801 action = "append" ,
787802 default = [],
788803 metavar = "OPT" ,
789- help = "always send WILL for this option (name like MXP or number, repeatable)" ,
804+ help = "always send WILL for this option (comma-separated, named like MXP"
805+ " or numeric like 91, repeatable)" ,
806+ )
807+ parser .add_argument (
808+ "--always-wont" ,
809+ action = "append" ,
810+ default = [],
811+ metavar = "OPT" ,
812+ help = "always send WONT for this option, refusing even natively supported"
813+ " options (comma-separated, named or numeric, repeatable)" ,
790814 )
791815 parser .add_argument (
792816 "--ansi-keys" ,
@@ -925,6 +949,22 @@ def _parse_option_arg(value: str) -> bytes:
925949 return bytes ([int (value )])
926950
927951
952+ def _parse_option_list (values : List [str ]) -> set [bytes ]:
953+ """
954+ Parse a list of option arguments, splitting comma-separated values.
955+
956+ :param values: List of option strings, each may be comma-separated.
957+ :returns: Set of parsed option bytes.
958+ """
959+ result : set [bytes ] = set ()
960+ for v in values :
961+ for item in v .split ("," ):
962+ item = item .strip ()
963+ if item :
964+ result .add (_parse_option_arg (item ))
965+ return result
966+
967+
928968def _transform_args (args : argparse .Namespace ) -> Dict [str , Any ]:
929969 # Auto-enable force_binary for any non-ASCII encoding that uses high-bit bytes.
930970 from .encodings import FORCE_BINARY_ENCODINGS
@@ -971,8 +1011,10 @@ def _transform_args(args: argparse.Namespace) -> Dict[str, Any]:
9711011 "connect_minwait" : args .connect_minwait ,
9721012 "connect_timeout" : args .connect_timeout or None ,
9731013 "send_environ" : tuple (v .strip () for v in args .send_environ .split ("," ) if v .strip ()),
974- "always_will" : {_parse_option_arg (v ) for v in args .always_will },
975- "always_do" : {_parse_option_arg (v ) for v in args .always_do },
1014+ "always_will" : _parse_option_list (args .always_will ),
1015+ "always_do" : _parse_option_list (args .always_do ),
1016+ "always_wont" : _parse_option_list (args .always_wont ),
1017+ "always_dont" : _parse_option_list (args .always_dont ),
9761018 "raw_mode" : raw_mode ,
9771019 "ascii_eol" : args .ascii_eol ,
9781020 "ansi_keys" : args .ansi_keys ,
@@ -1012,14 +1054,32 @@ def _get_fingerprint_argument_parser() -> argparse.ArgumentParser:
10121054 action = "append" ,
10131055 default = [],
10141056 metavar = "OPT" ,
1015- help = "always send DO for this option (name like GMCP or number, repeatable)" ,
1057+ help = "always send DO for this option (comma-separated, named like GMCP"
1058+ " or numeric like 201, repeatable)" ,
1059+ )
1060+ parser .add_argument (
1061+ "--always-dont" ,
1062+ action = "append" ,
1063+ default = [],
1064+ metavar = "OPT" ,
1065+ help = "always send DONT for this option, refusing even natively supported"
1066+ " options (comma-separated, named or numeric, repeatable)" ,
10161067 )
10171068 parser .add_argument (
10181069 "--always-will" ,
10191070 action = "append" ,
10201071 default = [],
10211072 metavar = "OPT" ,
1022- help = "always send WILL for this option (name like MXP or number, repeatable)" ,
1073+ help = "always send WILL for this option (comma-separated, named like MXP"
1074+ " or numeric like 91, repeatable)" ,
1075+ )
1076+ parser .add_argument (
1077+ "--always-wont" ,
1078+ action = "append" ,
1079+ default = [],
1080+ metavar = "OPT" ,
1081+ help = "always send WONT for this option, refusing even natively supported"
1082+ " options (comma-separated, named or numeric, repeatable)" ,
10231083 )
10241084 parser .add_argument (
10251085 "--banner-max-bytes" , default = 65536 , type = int , help = "max bytes per banner read call"
@@ -1141,9 +1201,11 @@ async def run_fingerprint_client() -> None:
11411201 banner_max_bytes = args .banner_max_bytes ,
11421202 )
11431203
1144- # Parse --always-will/--always-do option names/numbers
1145- fp_always_will = {_parse_option_arg (v ) for v in args .always_will }
1146- fp_always_do = {_parse_option_arg (v ) for v in args .always_do }
1204+ # Parse --always-will/--always-do/--always-wont/--always-dont option names/numbers
1205+ fp_always_will = _parse_option_list (args .always_will )
1206+ fp_always_do = _parse_option_list (args .always_do )
1207+ fp_always_wont = _parse_option_list (args .always_wont )
1208+ fp_always_dont = _parse_option_list (args .always_dont )
11471209
11481210 # Parse --send-env KEY=VALUE pairs
11491211 extra_env : Dict [str , str ] = {}
@@ -1178,6 +1240,10 @@ def patched_connection_made(transport: asyncio.BaseTransport) -> None:
11781240 mud_opts = {opt for opt , _ , _ in fingerprinting .EXTENDED_OPTIONS }
11791241 client .writer .always_will = fp_always_will | mud_opts
11801242 client .writer .always_do = fp_always_do | mud_opts
1243+ if fp_always_wont :
1244+ client .writer .always_wont = fp_always_wont
1245+ if fp_always_dont :
1246+ client .writer .always_dont = fp_always_dont
11811247
11821248 def patched_send_env (keys : Sequence [str ]) -> Dict [str , Any ]:
11831249 result = orig_send_env (keys )
0 commit comments