now_proto_pdu/exec/
shell.rs1use alloc::borrow::Cow;
2
3use bitflags::bitflags;
4use ironrdp_core::{
5 cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned,
6 ReadCursor, WriteCursor,
7};
8
9use crate::{NowExecMessage, NowExecMsgKind, NowHeader, NowMessage, NowMessageClass, NowVarStr};
10
11bitflags! {
12 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14 struct NowExecShellFlags: u16 {
15 const PARAMETERS_SET = 0x0001;
19
20 const DIRECTORY_SET = 0x0002;
24
25 const IO_REDIRECTION = 0x1000;
29
30 const DETACHED = 0x8000;
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct NowExecShellMsg<'a> {
42 flags: NowExecShellFlags,
43 session_id: u32,
44 command: NowVarStr<'a>,
45 shell: NowVarStr<'a>,
46 directory: NowVarStr<'a>,
47}
48
49impl_pdu_borrowing!(NowExecShellMsg<'_>, OwnedNowExecShellMsg);
50
51impl IntoOwned for NowExecShellMsg<'_> {
52 type Owned = OwnedNowExecShellMsg;
53
54 fn into_owned(self) -> Self::Owned {
55 OwnedNowExecShellMsg {
56 flags: self.flags,
57 session_id: self.session_id,
58 command: self.command.into_owned(),
59 shell: self.shell.into_owned(),
60 directory: self.directory.into_owned(),
61 }
62 }
63}
64
65impl<'a> NowExecShellMsg<'a> {
66 const NAME: &'static str = "NOW_EXEC_SHELL_MSG";
67 const FIXED_PART_SIZE: usize = 4;
68
69 pub fn new(session_id: u32, command: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
70 let msg = Self {
71 flags: NowExecShellFlags::empty(),
72 session_id,
73 command: NowVarStr::new(command)?,
74 shell: NowVarStr::default(),
75 directory: NowVarStr::default(),
76 };
77
78 msg.ensure_message_size()?;
79
80 Ok(msg)
81 }
82
83 pub fn with_shell(mut self, shell: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
84 self.flags |= NowExecShellFlags::PARAMETERS_SET;
85 self.shell = NowVarStr::new(shell)?;
86
87 self.ensure_message_size()?;
88
89 Ok(self)
90 }
91
92 pub fn with_directory(mut self, directory: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
93 self.flags |= NowExecShellFlags::DIRECTORY_SET;
94 self.directory = NowVarStr::new(directory)?;
95
96 self.ensure_message_size()?;
97
98 Ok(self)
99 }
100
101 fn ensure_message_size(&self) -> EncodeResult<()> {
102 ensure_now_message_size!(
103 Self::FIXED_PART_SIZE,
104 self.command.size(),
105 self.shell.size(),
106 self.directory.size()
107 );
108
109 Ok(())
110 }
111
112 pub fn session_id(&self) -> u32 {
113 self.session_id
114 }
115
116 pub fn command(&self) -> &str {
117 &self.command
118 }
119
120 pub fn shell(&self) -> Option<&str> {
121 if self.flags.contains(NowExecShellFlags::PARAMETERS_SET) {
122 Some(&self.shell)
123 } else {
124 None
125 }
126 }
127
128 pub fn directory(&self) -> Option<&str> {
129 if self.flags.contains(NowExecShellFlags::DIRECTORY_SET) {
130 Some(&self.directory)
131 } else {
132 None
133 }
134 }
135
136 #[must_use]
137 pub fn with_io_redirection(mut self) -> Self {
138 self.flags |= NowExecShellFlags::IO_REDIRECTION;
139 self
140 }
141
142 pub fn is_with_io_redirection(&self) -> bool {
143 self.flags.contains(NowExecShellFlags::IO_REDIRECTION)
144 }
145
146 #[must_use]
147 pub fn with_detached(mut self) -> Self {
148 self.flags |= NowExecShellFlags::DETACHED;
149 self
150 }
151
152 pub fn is_detached(&self) -> bool {
153 self.flags.contains(NowExecShellFlags::DETACHED)
154 }
155
156 #[allow(clippy::arithmetic_side_effects)]
158 fn body_size(&self) -> usize {
159 Self::FIXED_PART_SIZE + self.command.size() + self.shell.size() + self.directory.size()
160 }
161
162 pub(super) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
163 ensure_fixed_part_size!(in: src);
164
165 let flags = NowExecShellFlags::from_bits_retain(header.flags);
166 let session_id = src.read_u32();
167 let command = NowVarStr::decode(src)?;
168 let shell = NowVarStr::decode(src)?;
169 let directory = NowVarStr::decode(src)?;
170
171 let msg = Self {
172 flags,
173 session_id,
174 command,
175 shell,
176 directory,
177 };
178
179 Ok(msg)
180 }
181}
182
183impl Encode for NowExecShellMsg<'_> {
184 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
185 let header = NowHeader {
186 size: cast_length!("size", self.body_size())?,
187 class: NowMessageClass::EXEC,
188 kind: NowExecMsgKind::SHELL.0,
189 flags: self.flags.bits(),
190 };
191
192 header.encode(dst)?;
193
194 ensure_fixed_part_size!(in: dst);
195 dst.write_u32(self.session_id);
196 self.command.encode(dst)?;
197 self.shell.encode(dst)?;
198 self.directory.encode(dst)?;
199
200 Ok(())
201 }
202
203 fn name(&self) -> &'static str {
204 Self::NAME
205 }
206
207 #[allow(clippy::arithmetic_side_effects)]
209 fn size(&self) -> usize {
210 NowHeader::FIXED_PART_SIZE + self.body_size()
211 }
212}
213
214impl<'de> Decode<'de> for NowExecShellMsg<'de> {
215 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
216 let header = NowHeader::decode(src)?;
217
218 match (header.class, NowExecMsgKind(header.kind)) {
219 (NowMessageClass::EXEC, NowExecMsgKind::SHELL) => Self::decode_from_body(header, src),
220 _ => Err(invalid_field_err!("type", "invalid message type")),
221 }
222 }
223}
224
225impl<'a> From<NowExecShellMsg<'a>> for NowMessage<'a> {
226 fn from(msg: NowExecShellMsg<'a>) -> Self {
227 NowMessage::Exec(NowExecMessage::Shell(msg))
228 }
229}