gnostr_cat/
lints.rs

1#![cfg_attr(
2    feature = "cargo-clippy",
3    allow(collapsible_if, needless_pass_by_value)
4)]
5
6use std::ops::Not;
7use std::rc::Rc;
8use std::str::FromStr;
9
10use super::specifier::SpecifierNode;
11use super::{Options, Result, SpecifierClass, SpecifierStack, WebsocatConfiguration2};
12
13extern crate hyper;
14extern crate url;
15
16use std::net::{IpAddr, SocketAddr};
17
18use super::socks5_peer::{SocksHostAddr, SocksSocketAddr};
19
20#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
21pub enum StdioUsageStatus {
22    /// Does not use standard input or output at all
23    None,
24    /// Uses a reuser for connecting multiple peers at stdio, not distinguishing
25    /// between IsItself and Indirectly
26    WithReuser,
27    /// Stdio wrapped into something (but not the reuser)
28    Indirectly,
29    /// Is the `-` or `stdio:` or `threadedstdio:` itself.
30    IsItself,
31}
32
33trait ClassExt {
34    fn is_stdio(&self) -> bool;
35    fn is_reuser(&self) -> bool;
36}
37
38pub type OnWarning = Box<dyn for<'a> Fn(&'a str) -> () + 'static>;
39
40#[cfg_attr(rustfmt, rustfmt_skip)]
41impl ClassExt for Rc<dyn SpecifierClass> {
42    fn is_stdio(&self) -> bool {
43        [
44            "StdioClass",
45            "ThreadedStdioClass",
46            "ThreadedStdioSubstituteClass",
47        ].contains(&self.get_name())
48    }
49    fn is_reuser(&self) -> bool {
50        [
51            "ReuserClass",
52            "BroadcastReuserClass",
53        ].contains(&self.get_name())
54    }
55}
56
57pub trait SpecifierStackExt {
58    fn stdio_usage_status(&self) -> StdioUsageStatus;
59    fn reuser_count(&self) -> usize;
60    fn contains(&self, t: &'static str) -> bool;
61    fn is_multiconnect(&self) -> bool;
62    fn is_stream_oriented(&self) -> bool;
63    fn autotoreconn_misuse(&self) -> bool;
64    fn insert_line_class_in_proper_place(&mut self, x: Rc<dyn SpecifierClass>);
65}
66impl SpecifierStackExt for SpecifierStack {
67    fn stdio_usage_status(&self) -> StdioUsageStatus {
68        use self::StdioUsageStatus::*;
69
70        if !self.addrtype.cls.is_stdio() {
71            return None;
72        }
73
74        let mut sus: StdioUsageStatus = IsItself;
75
76        for overlay in self.overlays.iter().rev() {
77            if overlay.cls.is_reuser() {
78                sus = WithReuser;
79            } else if sus == IsItself {
80                sus = Indirectly;
81            }
82        }
83        sus
84    }
85    fn reuser_count(&self) -> usize {
86        let mut c = 0;
87        for overlay in &self.overlays {
88            if overlay.cls.is_reuser() {
89                c += 1;
90            }
91        }
92        c
93    }
94    fn contains(&self, t: &'static str) -> bool {
95        for overlay in &self.overlays {
96            if overlay.cls.get_name() == t {
97                return true;
98            }
99        }
100        self.addrtype.cls.get_name() == t
101    }
102    fn autotoreconn_misuse(&self) -> bool {
103        let mut autoreconnect_found = false;
104        for overlay in &self.overlays {
105            if overlay.cls.get_name() == "AutoReconnectClass" {
106                autoreconnect_found = true;
107            }
108            if overlay.cls.get_name() == "BroadcastReuserClass"
109                || overlay.cls.get_name() == "ReuserClass"
110            {
111                if autoreconnect_found {
112                    return true;
113                }
114            }
115        }
116        false
117    }
118    fn is_multiconnect(&self) -> bool {
119        use super::ClassMulticonnectStatus::*;
120        match self.addrtype.cls.multiconnect_status() {
121            MultiConnect => (),
122            SingleConnect => return false,
123            MulticonnectnessDependsOnInnerType => unreachable!(),
124        }
125        for overlay in self.overlays.iter().rev() {
126            match overlay.cls.multiconnect_status() {
127                MultiConnect => (),
128                SingleConnect => return false,
129                MulticonnectnessDependsOnInnerType => (),
130            }
131        }
132        true
133    }
134    fn is_stream_oriented(&self) -> bool {
135        use super::ClassMessageBoundaryStatus::*;
136        let mut q = match self.addrtype.cls.message_boundary_status() {
137            StreamOriented => true,
138            MessageOriented => false,
139            MessageBoundaryStatusDependsOnInnerType => unreachable!(),
140        };
141        for overlay in self.overlays.iter().rev() {
142            match overlay.cls.message_boundary_status() {
143                StreamOriented => q = true,
144                MessageOriented => q = false,
145                MessageBoundaryStatusDependsOnInnerType => (),
146            }
147        }
148        q
149    }
150    fn insert_line_class_in_proper_place(&mut self, x: Rc<dyn SpecifierClass>) {
151        use super::ClassMessageBoundaryStatus::*;
152        let mut insert_idx = 0;
153        for overlay in &self.overlays {
154            match overlay.cls.message_boundary_status() {
155                StreamOriented => break,
156                MessageOriented => break,
157                MessageBoundaryStatusDependsOnInnerType => insert_idx += 1,
158            }
159        }
160        self.overlays.insert(insert_idx, SpecifierNode { cls: x });
161    }
162}
163
164impl WebsocatConfiguration2 {
165    pub fn inetd_mode(&self) -> bool {
166        self.contains_class("InetdClass")
167    }
168
169    #[cfg_attr(rustfmt, rustfmt_skip)]
170    #[cfg_attr(feature="cargo-clippy", allow(nonminimal_bool))]
171    pub fn websocket_used(&self) -> bool {
172        false 
173        || self.contains_class("WsConnectClass")
174        || self.contains_class("WsClientClass")
175        || self.contains_class("WsClientSecureClass")
176        || self.contains_class("WsServerClass")
177    }
178
179    #[cfg_attr(rustfmt, rustfmt_skip)]
180    #[cfg_attr(feature="cargo-clippy", allow(nonminimal_bool))]
181    pub fn exec_used(&self) -> bool {
182        false 
183        || self.contains_class("ExecClass")
184        || self.contains_class("CmdClass")
185        || self.contains_class("ShCClass")
186    }
187
188    pub fn contains_class(&self, x: &'static str) -> bool {
189        self.s1.contains(x) || self.s2.contains(x)
190    }
191
192    pub fn get_exec_parameter(&self) -> Option<&str> {
193        if self.s1.addrtype.cls.get_name() == "ExecClass" {
194            return Some(self.s1.addr.as_str());
195        }
196        if self.s2.addrtype.cls.get_name() == "ExecClass" {
197            return Some(self.s2.addr.as_str());
198        }
199        None
200    }
201
202    fn l_stdio(
203        &mut self,
204        multiconnect: bool,
205        reuser_has_been_inserted: &mut bool,
206        r#async: bool,
207    ) -> Result<()> {
208        use self::StdioUsageStatus::{Indirectly, IsItself, None, WithReuser};
209        match (self.s1.stdio_usage_status(), self.s2.stdio_usage_status()) {
210            (_, None) => (),
211            (None, WithReuser) => (),
212            (None, IsItself) | (None, Indirectly) => {
213                if multiconnect {
214                    self.s2.overlays.insert(
215                        0,
216                        SpecifierNode {
217                            cls: Rc::new(super::broadcast_reuse_peer::BroadcastReuserClass),
218                        },
219                    );
220                    *reuser_has_been_inserted = true;
221                }
222            }
223            (IsItself, IsItself) => {
224                info!("Special mode, exception from usual one-stdio rule. Acting like `cat(1)`");
225                self.s2 = SpecifierStack::from_str("mirror:")?;
226                if self.opts.unidirectional ^ self.opts.unidirectional_reverse {
227                    self.opts.unidirectional = false;
228                    self.opts.unidirectional_reverse = false;
229                }
230                return Ok(());
231            }
232            (_, _) => {
233                Err(
234                    "Too many usages of stdin/stdout. Specify it either on left or right address, \
235                     not on both.",
236                )?;
237            }
238        }
239
240        #[cfg(all(unix, feature = "unix_stdio"))]
241        {
242            if r#async {
243                if self.s1.addrtype.cls.get_name() == "StdioClass" {
244                    debug!("Substituting StdioClass with AsyncStdioClass at the left");
245                    self.s1.addrtype = SpecifierNode {
246                        cls: Rc::new(crate::stdio_peer::AsyncStdioClass),
247                    };
248                }
249                if self.s2.addrtype.cls.get_name() == "StdioClass" {
250                    debug!("Substituting StdioClass with AsyncStdioClass at the right");
251                    self.s2.addrtype = SpecifierNode {
252                        cls: Rc::new(crate::stdio_peer::AsyncStdioClass),
253                    };
254                }
255            }
256        }
257
258        Ok(())
259    }
260
261    fn l_reuser(&mut self, reuser_has_been_inserted: bool) -> Result<()> {
262        if self.s1.reuser_count() + self.s2.reuser_count() > 1 {
263            if reuser_has_been_inserted {
264                error!(
265                    "The reuser you specified conflicts with automatically inserted reuser based \
266                     on usage of stdin/stdout in multiconnect mode."
267                );
268            }
269            Err("Too many usages of connection reuser. Please limit to only one instance.")?;
270        }
271        Ok(())
272    }
273
274    fn l_linemode(&mut self) -> Result<()> {
275        if !self.opts.no_auto_linemode && self.opts.websocket_text_mode {
276            match (self.s1.is_stream_oriented(), self.s2.is_stream_oriented()) {
277                (false, false) => {}
278                (true, true) => {}
279                (true, false) => {
280                    info!("Auto-inserting the line mode");
281                    self.s1.insert_line_class_in_proper_place(Rc::new(
282                        super::line_peer::Line2MessageClass,
283                    ));
284                    self.s2.insert_line_class_in_proper_place(Rc::new(
285                        super::line_peer::Message2LineClass,
286                    ));
287                }
288                (false, true) => {
289                    info!("Auto-inserting the line mode");
290                    self.s2.insert_line_class_in_proper_place(Rc::new(
291                        super::line_peer::Line2MessageClass,
292                    ));
293                    self.s1.insert_line_class_in_proper_place(Rc::new(
294                        super::line_peer::Message2LineClass,
295                    ));
296                }
297            }
298        };
299        Ok(())
300    }
301    fn l_listener_on_the_right(&mut self, on_warning: &OnWarning) -> Result<()> {
302        if !self.opts.oneshot && self.s2.is_multiconnect() && !self.s1.is_multiconnect() {
303            on_warning(
304                "You have specified a listener on the right (as the second positional argument) \
305                 instead of on the left. It will only serve one connection.\nChange arguments \
306                 order to enable multiple parallel connections or use --oneshot argument to make \
307                 single connection explicit.",
308            );
309        }
310        Ok(())
311    }
312    fn l_reuser_for_append(&mut self, multiconnect: bool) -> Result<()> {
313        if multiconnect
314            && (self.s2.addrtype.cls.get_name() == "WriteFileClass"
315                || self.s2.addrtype.cls.get_name() == "AppendFileClass")
316            && self.s2.reuser_count() == 0
317        {
318            info!("Auto-inserting the reuser");
319            self.s2.overlays.push(SpecifierNode {
320                cls: Rc::new(super::primitive_reuse_peer::ReuserClass),
321            });
322        };
323        Ok(())
324    }
325    fn l_exec(&mut self, on_warning: &OnWarning) -> Result<()> {
326        if self.s1.addrtype.cls.get_name() == "ExecClass"
327            && self.s2.addrtype.cls.get_name() == "ExecClass"
328        {
329            Err("Can't use exec: more than one time. Replace one of them with sh-c: or cmd:.")?;
330        }
331
332        if let Some(x) = self.get_exec_parameter() {
333            if self.opts.exec_args.is_empty() && x.contains(' ') {
334                on_warning(
335                    "Warning: you specified exec: without the corresponding --exec-args at the \
336                     end of command line. Unlike in cmd: or sh-c:, spaces inside exec:'s direct \
337                     parameter are interpreted as part of program name, not as separator.",
338                );
339            }
340        }
341        Ok(())
342    }
343    fn l_uri_staticfiles(&mut self, on_warning: &OnWarning) -> Result<()> {
344        if self.opts.restrict_uri.is_some() && !self.contains_class("WsServerClass") {
345            on_warning("--restrict-uri is meaningless without a WebSocket server");
346        }
347
348        if !self.opts.serve_static_files.is_empty() && !self.contains_class("WsServerClass") {
349            on_warning("--static-file (-F) is meaningless without a WebSocket server");
350        }
351
352        for sf in &self.opts.serve_static_files {
353            if !sf.uri.starts_with('/') {
354                on_warning(&format!(
355                    "Static file's URI `{}` should begin with `/`?",
356                    sf.uri
357                ));
358            }
359            if !sf.file.exists() {
360                on_warning(&format!("File {:?} does not exist", sf.file));
361            }
362            if !sf.content_type.contains('/') {
363                on_warning(&format!(
364                    "Content-Type `{}` lacks `/` character",
365                    sf.content_type
366                ));
367            }
368        }
369        Ok(())
370    }
371    fn l_environ(&mut self, on_warning: &OnWarning) -> Result<()> {
372        if self.opts.exec_set_env {
373            if !self.exec_used() {
374                on_warning(
375                    "-e (--set-environment) is meaningless without a exec: or sh-c: or cmd: \
376                     address",
377                );
378            }
379            if !self.contains_class("TcpListenClass") && !self.contains_class("WsServerClass") {
380                on_warning(
381                    "-e (--set-environment) is currently meaningless without a websocket server \
382                     and/or TCP listener",
383                );
384            }
385        }
386
387        if !self.opts.headers_to_env.is_empty() && !self.opts.exec_set_env {
388            on_warning("--header-to-env is meaningless without -e (--set-environment)");
389        }
390
391        Ok(())
392    }
393    fn l_closebug(&mut self, on_warning: &OnWarning) -> Result<()> {
394        if !self.opts.oneshot && self.s1.is_multiconnect() {
395            if self.s1.contains("TcpListenClass")
396                || self.s1.contains("UnixListenClass")
397                || self.s1.contains("SeqpacketListenClass")
398            {
399                if !self.opts.unidirectional
400                    && (self.opts.unidirectional_reverse || !self.opts.exit_on_eof)
401                {
402                    on_warning(
403                        "Unfortunately, serving multiple clients without --exit-on-eof (-E) or \
404                         with -U option is prone to socket leak in this websocat version",
405                    );
406                }
407            }
408        }
409        Ok(())
410    }
411
412    fn l_socks5_c(
413        s: &mut SpecifierStack,
414        opts: &mut Options,
415        on_warning: &OnWarning,
416        secure: bool,
417    ) -> Result<()> {
418        let url = if secure {
419            #[cfg(not(feature = "ssl"))]
420            {
421                Err("SSL support not compiled in")?;
422            }
423            format!("wss://{}", s.addr)
424        } else {
425            format!("ws://{}", s.addr)
426        };
427
428        // Overwrite WsClientClass
429        s.addrtype = SpecifierNode {
430            cls: Rc::new(super::net_peer::TcpConnectClass),
431        };
432
433        match opts.auto_socks5.unwrap() {
434            SocketAddr::V4(sa4) => {
435                s.addr = format!("{}:{}", sa4.ip(), sa4.port());
436            }
437            SocketAddr::V6(sa6) => {
438                s.addr = format!("[{}]:{}", sa6.ip(), sa6.port());
439            }
440        }
441
442        use self::hyper::Url;
443        use self::url::Host;
444        let u = Url::parse(&url)?;
445
446        if !u.has_host() {
447            Err("WebSocket URL has no host")?;
448        }
449
450        let port = u.port_or_known_default().unwrap_or(80);
451        let host = u.host().unwrap();
452
453        let host = match host {
454            Host::Domain(dom) => SocksHostAddr::Name(dom.to_string()),
455            Host::Ipv4(ip4) => SocksHostAddr::Ip(IpAddr::V4(ip4)),
456            Host::Ipv6(ip6) => SocksHostAddr::Ip(IpAddr::V6(ip6)),
457        };
458        if opts.socks_destination.is_none() {
459            opts.socks_destination = Some(SocksSocketAddr { host, port });
460        }
461        if secure && opts.tls_domain.is_none() {
462            opts.tls_domain = u.host_str().map(|x| x.to_string());
463        }
464
465        if opts.ws_c_uri != "ws://0.0.0.0/" {
466            on_warning(
467                "Looks like you've overridden ws-c-uri. We are overwriting it for --socks5 option.",
468            );
469        }
470
471        opts.ws_c_uri = url;
472
473        s.overlays.push(SpecifierNode {
474            cls: Rc::new(super::ws_client_peer::WsConnectClass),
475        });
476        if secure {
477            #[cfg(feature = "ssl")]
478            s.overlays.push(SpecifierNode {
479                cls: Rc::new(super::ssl_peer::TlsConnectClass),
480            });
481        }
482        s.overlays.push(SpecifierNode {
483            cls: Rc::new(super::socks5_peer::SocksProxyClass),
484        });
485
486        Ok(())
487    }
488
489    fn l_socks5(&mut self, on_warning: &OnWarning) -> Result<()> {
490        if self.opts.socks_destination.is_some()
491            ^ (self.contains_class("SocksProxyClass") || self.contains_class("SocksBindClass"))
492        {
493            on_warning(
494                "--socks5-destination option and socks5-connect: overlay should go together",
495            );
496        }
497
498        if self.opts.socks5_bind_script.is_some() ^ self.contains_class("SocksBindClass") {
499            on_warning("--socks5-bind-script option and socks5-bind: overlay should go together");
500        }
501
502        if self.opts.auto_socks5.is_some() {
503            if !((self.s1.addrtype.cls.get_name() == "WsClientClass"
504                || self.s1.addrtype.cls.get_name() == "WsClientSecureClass")
505                ^ (self.s2.addrtype.cls.get_name() == "WsClientClass"
506                    || self.s2.addrtype.cls.get_name() == "WsClientSecureClass"))
507            {
508                Err(
509                    "User-friendly --socks5 option supports socksifying exactly one non-raw \
510                     websocket client connection. You are using two or none.",
511                )?;
512            }
513
514            if self.s1.addrtype.cls.get_name() == "WsClientClass" {
515                WebsocatConfiguration2::l_socks5_c(
516                    &mut self.s1,
517                    &mut self.opts,
518                    on_warning,
519                    false,
520                )?;
521            }
522            if self.s1.addrtype.cls.get_name() == "WsClientSecureClass" {
523                WebsocatConfiguration2::l_socks5_c(&mut self.s1, &mut self.opts, on_warning, true)?;
524            }
525            if self.s2.addrtype.cls.get_name() == "WsClientClass" {
526                WebsocatConfiguration2::l_socks5_c(
527                    &mut self.s2,
528                    &mut self.opts,
529                    on_warning,
530                    false,
531                )?;
532            }
533            if self.s2.addrtype.cls.get_name() == "WsClientSecureClass" {
534                WebsocatConfiguration2::l_socks5_c(&mut self.s2, &mut self.opts, on_warning, true)?;
535            }
536        }
537        Ok(())
538    }
539
540    #[cfg(feature = "ssl")]
541    fn l_ssl(&mut self, _on_warning: &OnWarning) -> Result<()> {
542        if self.opts.pkcs12_der.is_some() && !self.contains_class("TlsAcceptClass") {
543            Err("--pkcs12-der makes no sense without an TLS connections acceptor")?;
544        }
545        if self.opts.pkcs12_der.is_none() && self.contains_class("TlsAcceptClass") {
546            Err(
547                "You need to specify server key and certificate using the --pkcs12-der option to \
548                 use the TLS connections acceptor",
549            )?;
550        }
551        if self.opts.client_pkcs12_der.is_some()
552            && !self.contains_class("WsClientSecureClass")
553            && !self.contains_class("TlsConnectClass")
554        {
555            Err("--client-pkcs12-der makes no sense without wss:// or ssl: connectors")?;
556        }
557        #[cfg(target_os = "macos")]
558        {
559            if (self.opts.pkcs12_der.is_some() && self.opts.pkcs12_passwd.is_none())
560                || (self.opts.client_pkcs12_der.is_some()
561                    && self.opts.client_pkcs12_passwd.is_none())
562            {
563                _on_warning("PKCS12 archives without password may be unsupported on Mac");
564
565                for x in ::std::env::args() {
566                    if x.contains("test.pkcs12") {
567                        _on_warning(
568                            "If you want a pre-made test certificate, use other file: \
569                             `--pkcs12-der 1234.pkcs12 --pkcs12-passwd 1234`",
570                        );
571                        break;
572                    }
573                }
574            }
575        }
576        Ok(())
577    }
578
579    fn l_ping(&mut self, _on_warning: &OnWarning) -> Result<()> {
580        if self.opts.ws_ping_interval.is_some() || self.opts.ws_ping_timeout.is_some() {
581            if !self.websocket_used() {
582                _on_warning(
583                    "--ping-interval or --ping-timeout options are not effective if no WebSocket \
584                     usage is specified",
585                )
586            }
587        }
588        if self.opts.ws_ping_timeout.is_some() && self.opts.ws_ping_interval.is_none() {
589            _on_warning(
590                "--ping-timeout specified without --ping-interval. This will probably lead to \
591                 unconditional disconnection after that interval.",
592            )
593        }
594        if let (Some(t), Some(i)) = (self.opts.ws_ping_timeout, self.opts.ws_ping_interval) {
595            if t <= i {
596                _on_warning(
597                    "--ping-timeout's value is not more than --ping-interval. Expect spurious \
598                     disconnections.",
599                );
600            }
601        }
602        if self.opts.ws_ping_timeout.is_some() {
603            if self.opts.unidirectional_reverse || self.opts.exit_on_eof {
604                // OK
605            } else {
606                _on_warning("--ping-interval is currently not very effective without -E or -U")
607            }
608        }
609        if self.opts.print_ping_rtts && self.opts.ws_ping_interval.is_none() {
610            _on_warning("--print-ping-rtts is not effective without --ping-interval");
611        }
612        Ok(())
613    }
614
615    fn l_proto(&mut self, _on_warning: &OnWarning) -> Result<()> {
616        if self.opts.websocket_protocol.is_some() {
617            if false
618                || self.contains_class("WsConnectClass")
619                || self.contains_class("WsClientClass")
620                || self.contains_class("WsClientSecureClass")
621            {
622                // OK
623            } else {
624                if self.contains_class("WsServerClass") {
625                    _on_warning("--protocol option is unused. Maybe you want --server-protocol?")
626                } else {
627                    _on_warning("--protocol option is unused.")
628                }
629            }
630        }
631        if self.opts.websocket_reply_protocol.is_some() {
632            if !self.contains_class("WsServerClass") {
633                _on_warning("--server-protocol option is unused.")
634            }
635        }
636        Ok(())
637    }
638
639    fn l_eeof_unidir(&mut self, _on_warning: &OnWarning) -> Result<()> {
640        if self.opts.exit_on_eof {
641            if self.opts.unidirectional || self.opts.unidirectional_reverse {
642                _on_warning(
643                    "--exit-on-eof and --unidirectional[-reverse] options are now useless together",
644                );
645                _on_warning(
646                    "You may want to remove --exit-on-eof. If you are happy with what happens, \
647                     consider `-uU` instead of `-uE`.",
648                );
649            }
650        }
651        Ok(())
652    }
653
654    fn l_udp(&mut self, _on_warning: &OnWarning) -> Result<()> {
655        if self.opts.udp_join_multicast_addr.is_empty().not() {
656            if self.opts.udp_broadcast {
657                _on_warning(
658                    "Both --udp-broadcast and a multicast address is set. This is strange.",
659                );
660            }
661            let ifs = self.opts.udp_join_multicast_iface_v4.len()
662                + self.opts.udp_join_multicast_iface_v6.len();
663            if ifs != 0 {
664                let mut v4_multicasts = 0;
665                let mut v6_multicasts = 0;
666                for i in &self.opts.udp_join_multicast_addr {
667                    match i {
668                        std::net::IpAddr::V4(_) => v4_multicasts += 1,
669                        std::net::IpAddr::V6(_) => v6_multicasts += 1,
670                    }
671                }
672                if v4_multicasts != self.opts.udp_join_multicast_iface_v4.len() {
673                    return Err(
674                        "--udp-multicast-iface-v4 option mush be specified the same number of \
675                         times as IPv4 addresses for --udp-multicast (alternatively \
676                         --udp-multicast-iface-* options should be not specified at all)",
677                    )?;
678                }
679                if v6_multicasts != self.opts.udp_join_multicast_iface_v6.len() {
680                    return Err(
681                        "--udp-multicast-iface-v6 option mush be specified the same number of \
682                         times as IPv6 addresses for --udp-multicast (alternatively \
683                         --udp-multicast-iface-* options should be not specified at all)",
684                    )?;
685                }
686            }
687        } else {
688            if self.opts.udp_multicast_loop {
689                return Err("--udp-multicast-loop is not applicable without --udp-multicast")?;
690            }
691        }
692        Ok(())
693    }
694    fn l_crypto(&mut self, _on_warning: &OnWarning) -> Result<()> {
695        #[cfg(feature = "crypto_peer")]
696        if self.opts.crypto_key.is_some() {
697            if !self.contains_class("CryptoClass") {
698                _on_warning("--crypto-key option is meaningless without a `crypto:` overlay");
699            }
700        }
701        Ok(())
702    }
703    fn l_prometheus(&mut self, _on_warning: &OnWarning) -> Result<()> {
704        #[cfg(feature = "prometheus_peer")]
705        if self.opts.prometheus.is_some() {
706            if !self.contains_class("PrometheusClass") {
707                self.s2.overlays.insert(
708                    0,
709                    SpecifierNode {
710                        cls: Rc::new(crate::prometheus_peer::PrometheusClass),
711                    },
712                );
713            }
714        } else {
715            if self.contains_class("PrometheusClass") {
716                _on_warning(
717                    "Using `prometheus:` overlay without `--prometheus` option is meaningless",
718                );
719            }
720        }
721        Ok(())
722    }
723    fn l_sizelimits(&mut self, _on_warning: &OnWarning) -> Result<()> {
724        if self.opts.max_ws_message_length < self.opts.max_ws_frame_length {
725            _on_warning(
726                "Lowering --max-ws-message-length without also lowering --max-ws-frame-length may \
727                 be meaningless, as the former only affects whether to begin accept a new frame \
728                 or not, given accumulated message size. Succesfully accepted frames within the \
729                 frame size limit may exceed the message size.",
730            )
731        }
732        Ok(())
733    }
734    fn l_compress(&mut self, _on_warning: &OnWarning) -> Result<()> {
735        let mut cn = 0;
736        let mut un = 0;
737        if self.opts.compress_deflate {
738            cn += 1
739        }
740        if self.opts.compress_gzip {
741            cn += 1
742        }
743        if self.opts.compress_zlib {
744            cn += 1
745        }
746        if self.opts.uncompress_deflate {
747            un += 1
748        }
749        if self.opts.uncompress_gzip {
750            un += 1
751        }
752        if self.opts.uncompress_zlib {
753            un += 1
754        }
755        if cn > 1 {
756            return Err("Multiple --compress-* options specifed")?;
757        }
758        if un > 1 {
759            return Err("Multiple --uncompress-* options specifed")?;
760        }
761
762        #[cfg(not(feature = "compression"))]
763        {
764            if cn > 0 || un > 0 {
765                return Err("Compression support is not selected during Websocat compilation")?;
766            }
767        }
768        Ok(())
769    }
770    #[cfg(feature = "native_plugins")]
771    fn l_plugins(&mut self, _on_warning: &OnWarning) -> Result<()> {
772        if self.contains_class("NativeTransformAClass") && self.opts.native_transform_a.is_none() {
773            return Err("--native-plugin-a must be specified to use `native_plugin_transform_a:`")?;
774        }
775        if self.contains_class("NativeTransformBClass") && self.opts.native_transform_b.is_none() {
776            return Err("--native-plugin-b must be specified to use `native_plugin_transform_b:`")?;
777        }
778        if self.contains_class("NativeTransformCClass") && self.opts.native_transform_c.is_none() {
779            return Err("--native-plugin-c must be specified to use `native_plugin_transform_c:`")?;
780        }
781        if self.contains_class("NativeTransformDClass") && self.opts.native_transform_d.is_none() {
782            return Err("--native-plugin-d must be specified to use `native_plugin_transform_d:`")?;
783        }
784        Ok(())
785    }
786    #[cfg(not(feature = "native_plugins"))]
787    fn l_plugins(&mut self, _on_warning: &OnWarning) -> Result<()> {
788        Ok(())
789    }
790
791    #[cfg(feature = "wasm_plugins")]
792    fn l_wasm(&mut self, _on_warning: &OnWarning) -> Result<()> {
793        if self.contains_class("WasmTransformAClass") && self.opts.wasm_transform_a.is_none() {
794            return Err("--wasm-plugin-a must be specified to use `wasm_plugin_transform_a:`")?;
795        }
796        if self.contains_class("WasmTransformBClass") && self.opts.wasm_transform_b.is_none() {
797            return Err("--wasm-plugin-b must be specified to use `wasm_plugin_transform_b:`")?;
798        }
799        if self.contains_class("WasmTransformCClass") && self.opts.wasm_transform_c.is_none() {
800            return Err("--wasm-plugin-c must be specified to use `wasm_plugin_transform_c:`")?;
801        }
802        if self.contains_class("WasmTransformDClass") && self.opts.wasm_transform_d.is_none() {
803            return Err("--wasm-plugin-d must be specified to use `wasm_plugin_transform_d:`")?;
804        }
805        Ok(())
806    }
807    #[cfg(not(feature = "wasm_plugins"))]
808    fn l_wasm(&mut self, _on_warning: &OnWarning) -> Result<()> {
809        Ok(())
810    }
811
812    fn l_autoreconn_reuse(&mut self, _on_warning: &OnWarning) -> Result<()> {
813        if self.s1.autotoreconn_misuse() || self.s2.autotoreconn_misuse() {
814            _on_warning(
815                "Warning: `autoreconnect:reuse:` is a bad overlay combination. Maybe you want \
816                 `reuse:autoreconnect:",
817            );
818        }
819        Ok(())
820    }
821
822    pub fn lint_and_fixup(&mut self, on_warning: OnWarning) -> Result<()> {
823        let multiconnect = !self.opts.oneshot && self.s1.is_multiconnect();
824        let mut reuser_has_been_inserted = false;
825
826        self.l_prometheus(&on_warning)?;
827        self.l_stdio(
828            multiconnect,
829            &mut reuser_has_been_inserted,
830            self.opts.asyncstdio,
831        )?;
832        self.l_reuser(reuser_has_been_inserted)?;
833        self.l_linemode()?;
834        self.l_listener_on_the_right(&on_warning)?;
835        self.l_reuser_for_append(multiconnect)?;
836        self.l_exec(&on_warning)?;
837        self.l_uri_staticfiles(&on_warning)?;
838        self.l_environ(&on_warning)?;
839        self.l_closebug(&on_warning)?;
840        self.l_socks5(&on_warning)?;
841        #[cfg(feature = "ssl")]
842        self.l_ssl(&on_warning)?;
843        self.l_ping(&on_warning)?;
844        self.l_proto(&on_warning)?;
845        self.l_eeof_unidir(&on_warning)?;
846        self.l_udp(&on_warning)?;
847        self.l_crypto(&on_warning)?;
848        self.l_sizelimits(&on_warning)?;
849        self.l_compress(&on_warning)?;
850        self.l_plugins(&on_warning)?;
851        self.l_wasm(&on_warning)?;
852        self.l_autoreconn_reuse(&on_warning)?;
853
854        // TODO: UDP connect oneshot mode
855        // TODO: tests for the linter
856        Ok(())
857    }
858}