1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! v2.0.16 — `CLIENT SETNAME` / `CLIENT GETNAME` interception
//! (closes v1.52.x finding).
//!
//! These two subcommands need per-connection state which the
//! stateless `cmd_client` dispatch in `kevy` can't access. We
//! intercept them at the reactor level — `handle_command` already
//! owns `&mut Conn` via `self.conns.get_mut(conn_id)` — and emit
//! the reply directly with `immediate_reply`.
//!
//! All other CLIENT subcommands (`ID`, `LIST`, `INFO`, `KILL`,
//! `NO-EVICT`, etc.) fall through to the standard dispatch path.
//!
//! Lives outside `exec.rs` to keep that file under the 500-LOC
//! house rule.
use kevy_resp::ArgvView;
use crate::shard::Shard;
use crate::Commands;
impl<C: Commands> Shard<C> {
/// Return `true` when `args` is `CLIENT SETNAME <name>` or
/// `CLIENT GETNAME` and the intercept emitted a reply.
pub(crate) fn try_intercept_client<A: ArgvView + ?Sized>(
&mut self,
conn_id: u64,
args: &A,
) -> bool {
if args.len() < 2 {
return false;
}
let Some(verb) = args.get(0) else { return false };
if !verb.eq_ignore_ascii_case(b"CLIENT") {
return false;
}
let Some(sub) = args.get(1) else { return false };
let sub_upper = sub.to_ascii_uppercase();
match sub_upper.as_slice() {
b"SETNAME" => {
if args.len() != 3 {
self.immediate_reply(
conn_id,
b"-ERR wrong number of arguments for 'client|setname'\r\n".to_vec(),
);
return true;
}
let name = args.get(2).unwrap_or(&[]);
// Redis disallows whitespace + control bytes in the
// name (the LIST output would be ambiguous otherwise).
if name.iter().any(|b| b.is_ascii_whitespace() || *b < 0x20) {
self.immediate_reply(
conn_id,
b"-ERR Client names cannot contain spaces, newlines or special characters.\r\n".to_vec(),
);
return true;
}
if let Some(c) = self.conns.get_mut(&conn_id) {
c.client_name.clear();
c.client_name.extend_from_slice(name);
}
self.immediate_reply(conn_id, b"+OK\r\n".to_vec());
true
}
b"GETNAME" => {
if args.len() != 2 {
self.immediate_reply(
conn_id,
b"-ERR wrong number of arguments for 'client|getname'\r\n".to_vec(),
);
return true;
}
if let Some(c) = self.conns.get(&conn_id) {
let name = c.client_name.clone();
let mut out = Vec::with_capacity(8 + name.len());
out.extend_from_slice(format!("${}\r\n", name.len()).as_bytes());
out.extend_from_slice(&name);
out.extend_from_slice(b"\r\n");
self.immediate_reply(conn_id, out);
} else {
self.immediate_reply(conn_id, b"$0\r\n\r\n".to_vec());
}
true
}
_ => false,
}
}
}