mago_analyzer/plugin/libraries/stdlib/cookie/
setcookie.rs1use mago_reporting::Annotation;
2use mago_reporting::Issue;
3use mago_span::HasSpan;
4use mago_syntax::cst::Argument;
5use mago_syntax::cst::Expression;
6use mago_syntax::cst::FunctionCall;
7
8use crate::code::IssueCode;
9use crate::plugin::context::HookContext;
10use crate::plugin::hook::FunctionCallHook;
11use crate::plugin::hook::HookResult;
12use crate::plugin::provider::Provider;
13use crate::plugin::provider::ProviderMeta;
14
15#[derive(Default)]
25pub struct SetCookieHook;
26
27impl Provider for SetCookieHook {
28 fn meta() -> &'static ProviderMeta {
29 static META: ProviderMeta = ProviderMeta::new(
30 "php::cookie::setcookie",
31 "setcookie",
32 "Validates setcookie/setrawcookie argument combinations.",
33 );
34
35 &META
36 }
37}
38
39impl FunctionCallHook for SetCookieHook {
40 fn after_function_call(&self, call: &FunctionCall<'_>, context: &mut HookContext<'_, '_>) -> HookResult<()> {
41 let Expression::Identifier(identifier) = call.function else {
42 return Ok(());
43 };
44
45 let name_bytes = identifier.value();
46 let name = mago_bytes::BytesDisplay(name_bytes);
47 if !name_bytes.eq_ignore_ascii_case(b"setcookie") && !name_bytes.eq_ignore_ascii_case(b"setrawcookie") {
48 return Ok(());
49 }
50
51 let arguments = &call.argument_list.arguments;
52 if arguments.len() <= 3 {
53 return Ok(());
54 }
55
56 let Some(third_arg) = arguments.get(2) else {
58 return Ok(());
59 };
60
61 let third_arg_expr = match third_arg {
62 Argument::Positional(arg) => arg.value,
63 Argument::Named(arg) => {
64 let param_name = arg.name.value;
65 if param_name != b"expires_or_options" && param_name != b"options" {
66 return Ok(());
67 }
68 arg.value
69 }
70 };
71
72 let Some(third_arg_type) = context.get_expression_type(third_arg_expr) else {
73 return Ok(());
74 };
75
76 if !third_arg_type.has_array() {
77 return Ok(());
78 }
79
80 let Some(fourth_arg) = arguments.get(3) else {
83 return Ok(());
84 };
85 let fourth_arg_span = match fourth_arg {
86 Argument::Positional(arg) => arg.value.span(),
87 Argument::Named(arg) => arg.span(),
88 };
89
90 context.report(
91 IssueCode::TooManyArguments,
92 Issue::error(format!("Too many arguments provided for function `{name}`."))
93 .with_annotation(
94 Annotation::primary(fourth_arg_span).with_message("Unexpected argument provided here"),
95 )
96 .with_annotation(
97 Annotation::secondary(call.function.span()).with_message("For this function call"),
98 )
99 .with_note(format!(
100 "When argument #3 (`$expires_or_options`) is an array, `{name}()` expects exactly 3 arguments, but received {}.",
101 arguments.len()
102 ))
103 .with_help("Remove the extra arguments and pass options in the array instead."),
104 );
105
106 Ok(())
107 }
108}