diff --git a/Cargo.lock b/Cargo.lock index 8c9fb342..1669d2d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,7 +136,7 @@ dependencies = [ "pin-project-lite", "tokio-rustls", "tokio-util", - "webpki-roots", + "webpki-roots 0.22.6", ] [[package]] @@ -256,6 +256,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -497,9 +503,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -540,9 +546,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -904,20 +910,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash 0.8.3", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.13.2", + "hashbrown 0.14.0", ] [[package]] @@ -1151,9 +1158,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1304,9 +1311,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "markdown" -version = "1.0.0-alpha.9" +version = "1.0.0-alpha.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c45dae11dd8af468c41201ace44dbbb3b1148215ac593cb8f0967b8d8d4b66c" +checksum = "b1bd98c3b68451b0390a289c58c856adb4e2b50cc40507ce2a105d5b00eafc80" dependencies = [ "log", "unicode-id", @@ -1542,9 +1549,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "16833386b02953ca926d19f64af613b9bf742c48dcd5e09b32fbfc9740bf84e2" dependencies = [ "thiserror", "ucd-trie", @@ -1552,9 +1559,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +checksum = "7763190f9406839f99e5197afee8c9e759969f7dbfa40ad3b8dbee8757b745b5" dependencies = [ "pest", "pest_generator", @@ -1562,9 +1569,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +checksum = "249061b22e99973da1f5f5f1410284419e283bb60b79255bf5f42a94b66a2e00" dependencies = [ "pest", "pest_meta", @@ -1575,9 +1582,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +checksum = "457c310cfc9cf3f22bc58901cc7f0d3410ac5d6298e432a4f9a6138565cb6df6" dependencies = [ "once_cell", "pest", @@ -1835,6 +1842,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -1844,6 +1863,16 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.13" @@ -1894,9 +1923,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "indexmap", "itoa", @@ -1929,9 +1958,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -2113,7 +2142,7 @@ dependencies = [ "percent-encoding", "rand", "rsa", - "rustls", + "rustls 0.20.8", "rustls-pemfile", "serde", "serde_json", @@ -2126,7 +2155,7 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "webpki-roots", + "webpki-roots 0.22.6", "whoami", ] @@ -2316,7 +2345,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.8", "tokio", "webpki", ] @@ -2397,9 +2426,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "8803eee176538f94ae9a14b55b2804eb7e1441f8210b1c31290b3bccdccff73b" dependencies = [ "proc-macro2", "quote", @@ -2489,18 +2518,18 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.6.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "flate2", "log", "once_cell", - "rustls", + "rustls 0.21.2", + "rustls-webpki", "url", - "webpki", - "webpki-roots", + "webpki-roots 0.23.1", ] [[package]] @@ -2528,11 +2557,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -2550,9 +2578,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2560,9 +2588,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -2575,9 +2603,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2585,9 +2613,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -2598,15 +2626,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2631,6 +2659,15 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +dependencies = [ + "rustls-webpki", +] + [[package]] name = "whoami" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 14b9009d..804716e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ codegen-units = 2 [dependencies] sqlx = { version = "0.6", features = ["any", "runtime-actix-rustls", "sqlite", "postgres", "mysql", "chrono"] } chrono = "0.4.23" -actix-web = { version = "4", features = ["rustls"] } +actix-web = { version = "4", features = ["rustls", "cookies"] } percent-encoding = "2.2.0" handlebars = "5.0.0-beta.0" log = "0.4.17" diff --git a/src/webserver/database/mod.rs b/src/webserver/database/mod.rs index 99c2fc4f..efe43da1 100644 --- a/src/webserver/database/mod.rs +++ b/src/webserver/database/mod.rs @@ -151,6 +151,7 @@ fn bind_parameters<'a>( .post_variables .get(x) .or_else(|| request.get_variables.get(x)), + StmtParam::Cookie(x) => request.cookies.get(x), }; log::debug!("Binding value {:?} in statement {}", &argument, stmt); match argument { @@ -293,10 +294,12 @@ impl Display for PreparedStatement { } } +#[derive(Debug, PartialEq, Eq)] enum StmtParam { Get(String), Post(String), GetOrPost(String), + Cookie(String), } #[actix_web::test] diff --git a/src/webserver/database/sql.rs b/src/webserver/database/sql.rs index fa0761ef..8bf90058 100644 --- a/src/webserver/database/sql.rs +++ b/src/webserver/database/sql.rs @@ -3,7 +3,10 @@ use crate::file_cache::AsyncFromStrWithState; use crate::webserver::database::StmtParam; use crate::{AppState, Database}; use async_trait::async_trait; -use sqlparser::ast::{DataType, Expr, Value, VisitMut, VisitorMut}; +use sqlparser::ast::{ + DataType, Expr, Function, FunctionArg, FunctionArgExpr, Ident, ObjectName, Value, VisitMut, + VisitorMut, +}; use sqlparser::dialect::GenericDialect; use sqlparser::parser::{Parser, ParserError}; use sqlparser::tokenizer::Token::{SemiColon, EOF}; @@ -36,8 +39,7 @@ impl ParsedSqlFile { }; while parser.consume_token(&SemiColon) {} let db_kind = db.connection.any_kind(); - let param_names = ParameterExtractor::extract_parameters(&mut stmt, db_kind); - let parameters = map_params(param_names); + let parameters = ParameterExtractor::extract_parameters(&mut stmt, db_kind); let query = stmt.to_string(); let param_types = get_param_types(¶meters); let stmt_res = db.prepare_with(&query, ¶m_types).await; @@ -96,31 +98,28 @@ fn get_param_types(parameters: &[StmtParam]) -> Vec { .collect() } -fn map_params(names: Vec) -> Vec { - names - .into_iter() - .map(|name| { - let (prefix, name) = name.split_at(1); - let name = name.to_owned(); - match prefix { - "$" => StmtParam::GetOrPost(name), - ":" => StmtParam::Post(name), - _ => StmtParam::Get(name), - } - }) - .collect() +fn map_param(mut name: String) -> StmtParam { + if name.is_empty() { + return StmtParam::GetOrPost(name); + } + let prefix = name.remove(0); + match prefix { + '$' => StmtParam::GetOrPost(name), + ':' => StmtParam::Post(name), + _ => StmtParam::Get(name), + } } struct ParameterExtractor { db_kind: AnyKind, - parameters: Vec, + parameters: Vec, } impl ParameterExtractor { fn extract_parameters( sql_ast: &mut sqlparser::ast::Statement, db_kind: AnyKind, - ) -> Vec { + ) -> Vec { let mut this = Self { db_kind, parameters: vec![], @@ -142,6 +141,40 @@ impl ParameterExtractor { data_type, } } + + fn handle_builtin_function( + &mut self, + func_name: &str, + mut arguments: Vec, + ) -> Expr { + #[allow(clippy::single_match_else)] + match (func_name, arguments.as_mut_slice()) { + ( + "cookie", + [FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString(cookie_name), + )))], + ) => { + let placeholder = self.make_placeholder(); + self.parameters + .push(StmtParam::Cookie(std::mem::take(cookie_name))); + placeholder + } + _ => { + log::warn!( + "Unsupported SQLPage function: {func_name} with arguments {arguments:#?}" + ); + Expr::Function(Function { + name: ObjectName(vec![Ident::new("unsupported_sqlpage_function")]), + args: arguments, + special: false, + distinct: false, + over: None, + order_by: vec![], + }) + } + } + } } #[inline] @@ -156,11 +189,36 @@ pub fn make_placeholder(db_kind: AnyKind, arg_number: usize) -> String { impl VisitorMut for ParameterExtractor { type Break = (); fn post_visit_expr(&mut self, value: &mut Expr) -> ControlFlow { - if let Expr::Value(Value::Placeholder(param)) = value { - let new_expr = self.make_placeholder(); - let name = std::mem::take(param); - self.parameters.push(name); - *value = new_expr; + match value { + Expr::Value(Value::Placeholder(param)) => { + let new_expr = self.make_placeholder(); + let name = std::mem::take(param); + self.parameters.push(map_param(name)); + *value = new_expr; + } + Expr::Function(Function { + name: ObjectName(func_name_parts), + args, + special: false, + distinct: false, + over: None, + .. + }) => { + if let [Ident { + value: func_name_part_0, + .. + }, Ident { + value: func_name, .. + }] = &func_name_parts[..] + { + if func_name_part_0 == "sqlpage" { + log::debug!("Handling builtin function: {func_name}"); + let arguments = std::mem::take(args); + *value = self.handle_builtin_function(func_name, arguments); + } + } + } + _ => (), } ControlFlow::<()>::Continue(()) } @@ -168,12 +226,21 @@ impl VisitorMut for ParameterExtractor { #[test] fn test_statement_rewrite() { - let sql = "select $a from t where $x > $a OR $x = 0"; + let sql = "select $a from t where $x > $a OR $x = sqlpage.cookie('cookoo')"; let mut ast = Parser::parse_sql(&GenericDialect, sql).unwrap(); let parameters = ParameterExtractor::extract_parameters(&mut ast[0], AnyKind::Postgres); assert_eq!( ast[0].to_string(), - "SELECT CAST($1 AS TEXT) FROM t WHERE CAST($2 AS TEXT) > CAST($3 AS TEXT) OR CAST($4 AS TEXT) = 0" + "SELECT CAST($1 AS TEXT) FROM t WHERE CAST($2 AS TEXT) > CAST($3 AS TEXT) OR CAST($4 AS TEXT) = CAST($5 AS TEXT)" + ); + assert_eq!( + parameters, + [ + StmtParam::GetOrPost("a".to_string()), + StmtParam::GetOrPost("x".to_string()), + StmtParam::GetOrPost("a".to_string()), + StmtParam::GetOrPost("x".to_string()), + StmtParam::Cookie("cookoo".to_string()), + ] ); - assert_eq!(parameters, ["$a", "$x", "$a", "$x"]); } diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 5fbaec3d..9f860485 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -276,9 +276,10 @@ pub struct RequestInfo { pub post_variables: ParamMap, pub headers: ParamMap, pub client_ip: Option, + pub cookies: ParamMap, } -fn param_map(values: Vec<(String, String)>) -> ParamMap { +fn param_map>(values: PAIRS) -> ParamMap { values .into_iter() .fold(HashMap::new(), |mut map, (mut k, v)| { @@ -301,30 +302,35 @@ fn param_map(values: Vec<(String, String)>) -> ParamMap { } async fn extract_request_info(req: &mut ServiceRequest) -> RequestInfo { - let headers: Vec<(String, String)> = req - .headers() - .iter() - .map(|(name, value)| { - ( - name.to_string(), - String::from_utf8_lossy(value.as_bytes()).to_string(), - ) - }) - .collect(); - let get_variables = web::Query::>::from_query(req.query_string()) - .map(web::Query::into_inner) - .unwrap_or_default(); - let client_ip = req.peer_addr().map(|addr| addr.ip()); let (http_req, payload) = req.parts_mut(); let post_variables = Form::>::from_request(http_req, payload) .await .map(Form::into_inner) .unwrap_or_default(); + + let headers = req.headers().iter().map(|(name, value)| { + ( + name.to_string(), + String::from_utf8_lossy(value.as_bytes()).to_string(), + ) + }); + let get_variables = web::Query::>::from_query(req.query_string()) + .map(web::Query::into_inner) + .unwrap_or_default(); + let client_ip = req.peer_addr().map(|addr| addr.ip()); + + let raw_cookies = req.cookies(); + let cookies = raw_cookies + .iter() + .flat_map(|c| c.iter()) + .map(|cookie| (cookie.name().to_string(), cookie.value().to_string())); + RequestInfo { headers: param_map(headers), get_variables: param_map(get_variables), post_variables: param_map(post_variables), client_ip, + cookies: param_map(cookies), } }