Skip to main content

mago_analyzer/plugin/libraries/stdlib/session/
session_set_cookie_params.rs

1use 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/// Hook for `session_set_cookie_params()`.
16///
17/// This function has two signatures:
18///   1. session_set_cookie_params(int $lifetime, ?string $path, ?string $domain,
19///      ?bool $secure, ?bool $httponly): bool
20///   2. session_set_cookie_params(array $lifetime_or_options): bool
21///
22/// When the 1st argument is an array, only 1 argument is allowed.
23#[derive(Default)]
24pub struct SessionSetCookieParamsHook;
25
26impl Provider for SessionSetCookieParamsHook {
27    fn meta() -> &'static ProviderMeta {
28        static META: ProviderMeta = ProviderMeta::new(
29            "php::session::session_set_cookie_params",
30            "session_set_cookie_params",
31            "Validates session_set_cookie_params argument combinations.",
32        );
33
34        &META
35    }
36}
37
38impl FunctionCallHook for SessionSetCookieParamsHook {
39    fn after_function_call(&self, call: &FunctionCall<'_>, context: &mut HookContext<'_, '_>) -> HookResult<()> {
40        let Expression::Identifier(identifier) = call.function else {
41            return Ok(());
42        };
43
44        if !identifier.value().eq_ignore_ascii_case(b"session_set_cookie_params") {
45            return Ok(());
46        }
47
48        let arguments = &call.argument_list.arguments;
49        if arguments.len() <= 1 {
50            return Ok(());
51        }
52
53        let Some(first_arg) = arguments.get(0) else {
54            return Ok(());
55        };
56
57        let first_arg_expr = match first_arg {
58            Argument::Positional(arg) => arg.value,
59            Argument::Named(arg) => arg.value,
60        };
61
62        let Some(first_arg_type) = context.get_expression_type(first_arg_expr) else {
63            return Ok(());
64        };
65
66        if !first_arg_type.has_array() {
67            return Ok(());
68        }
69
70        // The 1st argument is an array, only 1 argument is allowed.
71        let Some(second_arg) = arguments.get(1) else {
72            return Ok(());
73        };
74
75        let span = match second_arg {
76            Argument::Positional(arg) => arg.value.span(),
77            Argument::Named(arg) => arg.span(),
78        };
79
80        context.report(
81            IssueCode::TooManyArguments,
82            Issue::error("Too many arguments provided for function `session_set_cookie_params`.")
83                .with_annotation(Annotation::primary(span).with_message("Unexpected argument provided here"))
84                .with_annotation(
85                    Annotation::secondary(call.function.span()).with_message("For this function call"),
86                )
87                .with_note(format!(
88                    "When the first argument is an array, `session_set_cookie_params()` expects exactly 1 argument, but received {}.",
89                    arguments.len()
90                ))
91                .with_help("Remove the extra arguments and pass options in the array instead."),
92        );
93
94        Ok(())
95    }
96}