///|
/// Strip CRLF, ANSI CSI escapes, and other control characters from
/// server-controlled strings before they reach WebDriver client logs.
/// Defends against log injection / log forging from malicious response
/// headers that propagate via HttpError reason strings.
fn sanitize_for_log(s : String) -> String {
  let buf = StringBuilder::new()
  let chars = s.to_array()
  let mut i = 0
  while i < chars.length() {
    let c = chars[i]
    let code = c.to_int()
    if code == 0x1b {
      // ESC — skip the entire CSI sequence (ESC [ ... terminating-letter)
      i = i + 1
      if i < chars.length() && chars[i] == '[' {
        i = i + 1
        while i < chars.length() {
          let cc = chars[i]
          let ccode = cc.to_int()
          i = i + 1
          // CSI terminator is any byte in 0x40-0x7e
          if ccode >= 0x40 && ccode <= 0x7e {
            break
          }
        }
      }
    } else if code == 0x09 {
      // Allow tab
      buf.write_char(c)
      i = i + 1
    } else if code < 0x20 {
      // Drop all other control characters (CR, LF, BEL, BS, …)
      i = i + 1
    } else {
      buf.write_char(c)
      i = i + 1
    }
  }
  buf.to_string()
}

///|
/// Map an `@http.HttpError` into the BiDi `errorText` string that
/// `network.fetchError` / `network.responseCompleted` payloads expose to the
/// driver.
///
/// The two CORS variants get distinguishing prefixes so a Playwright /
/// WebDriver client can tell a preflight rejection apart from the actual
/// request being blocked:
///
/// - `CorsBlocked(reason)`     -> `"CORS: "`
/// - `PreflightFailed(reason)` -> `"CORS preflight: "`
///
/// Existing variants retain their `Show` form so legacy callers see the same
/// errorText as before.
///
/// Reason strings from `CorsBlocked`, `PreflightFailed`, and `CorsError` are
/// sanitized via `sanitize_for_log` before interpolation to prevent log
/// injection from malicious server-controlled values.
pub fn error_text_of(e : @http.HttpError) -> String {
  match e {
    @http.HttpError::CorsBlocked(reason) => "CORS: " + sanitize_for_log(reason)
    @http.HttpError::PreflightFailed(reason) =>
      "CORS preflight: " + sanitize_for_log(reason)
    @http.HttpError::NetworkError(msg) => "NetworkError: " + msg
    @http.HttpError::InvalidUrl(url) => "InvalidUrl: " + url
    @http.HttpError::TimeoutError => "TimeoutError"
    @http.HttpError::CorsError(msg) => "CorsError: " + sanitize_for_log(msg)
    @http.HttpError::SandboxError(msg) => "SandboxError: " + msg
  }
}