// CWE-770: Server started without body size limit.
// Only activated for crescent (which has max_request_body_bytes param).
// mocket's listen() has no body limit API — findings would be unactionable.

///|
priv struct Cwe770Visitor {
  findings : Array[Finding]
  file : String
  source : String
  severity : Severity
}

///|
pub fn cwe770_info() -> RuleInfo {
  {
    id: { cwe: 770, variant: "no-body-limit" },
    name: "Missing Request Body Size Limit",
    description: "Server listen()/serve() without max body size limit is vulnerable to memory exhaustion via large request bodies.",
    default_severity: Warning,
  }
}

///|
pub fn run_cwe770(
  impls : @list.List[@syntax.Impl],
  file : String,
  source : String,
  findings : Array[Finding],
  severity : Severity,
) -> Unit {
  if !source.contains(".post(") &&
    !source.contains(".put(") &&
    !source.contains(".patch(") {
    return
  }
  let visitor : Cwe770Visitor = { findings, file, source, severity }
  impls.each(fn(impl_) { @syntax.IterVisitor::visit_Impl(visitor, impl_) })
}

///|
fn is_server_start_method(name : String) -> Bool {
  name == "serve" || name == "listen" || name == "serve_async"
}

///|
impl @syntax.IterVisitor for Cwe770Visitor with fn visit_Expr_DotApply(
  env,
  self~,
  method_name~,
  args~,
  return_self~,
  loc~,
) {
  if is_server_start_method(method_name.name) {
    if !has_labelled_arg(args, "max_request_body_bytes") &&
      !has_labelled_arg(args, "max_body_size") &&
      !has_labelled_arg(args, "body_limit") {
      env.findings.push(
        make_finding(
          { cwe: 770, variant: "no-body-limit" },
          env.severity,
          Medium,
          "Server started without request body size limit. Large payloads can exhaust memory. Set a max body size.",
          env.file,
          env.source,
          loc,
        ),
      )
    }
  }
  @syntax.IterVisitor::base(env).visit_Expr_DotApply(
    self~,
    method_name~,
    args~,
    return_self~,
    loc~,
  )
}