//! http utilities use http::StatusCode; use pingora::prelude::*; /// closure that returns the given status with no cause /// /// use with [`Option::ok_or_else`] pub fn status_error( why: &'static str, src: ErrorSource, status: StatusCode, ) -> impl FnOnce() -> Box { move || { Error::create( ErrorType::HTTPStatus(status.into()), src, Some(why.into()), None, ) } } /// closure that returns `500 Internal Server Error`, marked as caused by the error returned to /// the given closure /// /// use with [`Result::map_err`] pub fn internal_error_from(why: &'static str) -> impl FnOnce(E) -> Box where E: Into>, { move |cause| { Error::create( ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()), ErrorSource::Internal, Some(why.into()), Some(cause.into()), ) } } /// closure that returns `500 Internal Server Error` with no cause /// /// use with [`Option::ok_or_else`] pub fn internal_error(why: &'static str) -> impl FnOnce() -> Box { move || { Error::create( ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()), ErrorSource::Internal, Some(why.into()), None, ) } } /// closure that returns the given status, marked as caused by the error returned to the given closure /// /// use with [`Result::map_err`] pub fn status_error_from( why: &'static str, src: ErrorSource, status: http::StatusCode, ) -> impl FnOnce(E) -> Box where E: Into>, { move |cause| { Error::create( ErrorType::HTTPStatus(status.into()), src, Some(why.into()), Some(cause.into()), ) } } /// redirect to the given location /// /// the given callback can be used to inject additional headers, like `set-cookie` pub async fn redirect_response( session: &mut Session, to: &str, bld_resp_header: impl FnOnce(&mut ResponseHeader, &Session) -> Result<()>, ) -> Result<()> { session.set_keepalive(None); session .write_response_header( Box::new({ // per , any redirect is fine save 307, but HTTP 302 seems to // be their example. let mut resp = ResponseHeader::build(StatusCode::FOUND, Some(0))?; resp.insert_header(http::header::LOCATION, to)?; bld_resp_header(&mut resp, session)?; resp }), true, ) .await?; session.finish_body().await?; Ok(()) } /// fetch the cookies for the current request pub fn cookie_jar(req: &'_ RequestHeader) -> Result>> { use cookie_rs::CookieJar; let Some(raw) = req.headers.get(http::header::COOKIE) else { return Ok(None); }; Ok(Some( raw.to_str() .map_err(Box::::from) .and_then(|c| Ok(CookieJar::parse(c)?)) .map_err(status_error_from( "bad cookie header", ErrorSource::Downstream, StatusCode::BAD_REQUEST, ))?, )) } /// serialize a claim value for use in a header pub fn serialize_claim( val: serde_json::Value, cfg: Option<&crate::config::format::claims::Serialization>, ) -> Option { let array_sep = cfg .and_then(|c| c.join_array_items_with.as_deref()) .unwrap_or(""); let key_and_value_sep = cfg .and_then(|c| c.join_keys_and_values_with.as_deref()) .unwrap_or(""); let map_item_sep = cfg .and_then(|c| c.join_key_value_pairs_with.as_deref()) .unwrap_or(""); Some(match val { serde_json::Value::Null => return None, serde_json::Value::Bool(b) => b.to_string(), serde_json::Value::Number(num) => num.to_string(), serde_json::Value::String(s) => s, serde_json::Value::Array(values) => values .into_iter() .filter_map(|val| serialize_claim(val, cfg)) // T_T no intersperse yet .collect::>() .join(array_sep), serde_json::Value::Object(map) => map .into_iter() .filter_map(|(key, val)| { Some(format!( "{key}{sep}{val}", sep = key_and_value_sep, val = serialize_claim(val, cfg)?, )) }) .collect::>() .join(map_item_sep), }) }