From 9fa0a4fbb4bd113dd83e68a9d75a69c8f404314e Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Tue, 17 Jan 2023 10:28:54 +0000 Subject: [PATCH] Ensure SQL `rand()` functions do not hang indefinitely --- lib/src/fnc/rand.rs | 91 ++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/lib/src/fnc/rand.rs b/lib/src/fnc/rand.rs index 76dc6624..ddbcb340 100644 --- a/lib/src/fnc/rand.rs +++ b/lib/src/fnc/rand.rs @@ -1,8 +1,8 @@ use crate::cnf::ID_CHARS; use crate::err::Error; -use crate::sql::datetime::Datetime; use crate::sql::uuid::Uuid; use crate::sql::value::Value; +use chrono::{TimeZone, Utc}; use nanoid::nanoid; use rand::distributions::{Alphanumeric, DistString}; use rand::prelude::IteratorRandom; @@ -41,22 +41,39 @@ pub fn float((range,): (Option<(f64, f64)>,)) -> Result { .into()) } -pub fn guid((len,): (Option,)) -> Result { - // Only need 53 to uniquely identify all atoms in observable universe. - const LIMIT: usize = 64; - - let len = match len { - Some(len) if len <= LIMIT => len, - None => 20, - _ => { +pub fn guid((arg1, arg2): (Option, Option)) -> Result { + // Set a reasonable maximum length + const LIMIT: i64 = 64; + // Check the function input arguments + let val = if let Some((min, max)) = arg1.zip(arg2) { + match min { + min if (1..=LIMIT).contains(&min) => match max { + max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize), + max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize), + _ => return Err(Error::InvalidArguments { + name: String::from("rand::guid"), + message: format!("To generate a guid of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), + }), + }, + _ => return Err(Error::InvalidArguments { + name: String::from("rand::guid"), + message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), + }), + } + } else if let Some(len) = arg1 { + if (1..=LIMIT).contains(&len) { + len as usize + } else { return Err(Error::InvalidArguments { name: String::from("rand::guid"), - message: format!("The maximum length of a GUID is {}.", LIMIT), - }) + message: format!("To generate a string of X characters in length, the argument must be a positive number and no higher than {}.", LIMIT), + }); } + } else { + 20 }; - - Ok(nanoid!(len, &ID_CHARS).into()) + // Generate the random guid + Ok(nanoid!(val, &ID_CHARS).into()) } pub fn int((range,): (Option<(i64, i64)>,)) -> Result { @@ -73,14 +90,14 @@ pub fn int((range,): (Option<(i64, i64)>,)) -> Result { } pub fn string((arg1, arg2): (Option, Option)) -> Result { - // Limit how much time and bandwidth is spent. - const LIMIT: i64 = 2i64.pow(16); - - let len = if let Some((min, max)) = arg1.zip(arg2) { + // Set a reasonable maximum length + const LIMIT: i64 = 65536; + // Check the function input arguments + let val = if let Some((min, max)) = arg1.zip(arg2) { match min { - min if (0..=LIMIT).contains(&min) => match max { + min if (1..=LIMIT).contains(&min) => match max { max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize), - max if max >= 0 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize), + max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize), _ => return Err(Error::InvalidArguments { name: String::from("rand::string"), message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), @@ -92,7 +109,7 @@ pub fn string((arg1, arg2): (Option, Option)) -> Result }), } } else if let Some(len) = arg1 { - if (0..=LIMIT).contains(&len) { + if (1..=LIMIT).contains(&len) { len as usize } else { return Err(Error::InvalidArguments { @@ -103,22 +120,34 @@ pub fn string((arg1, arg2): (Option, Option)) -> Result } else { 32 }; - - Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), len).into()) + // Generate the random string + Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), val).into()) } pub fn time((range,): (Option<(i64, i64)>,)) -> Result { - let i = if let Some((min, max)) = range { - let range = if max < min { - max..=min - } else { - min..=max - }; - rand::thread_rng().gen_range(range) + // Set the maximum valid seconds + const LIMIT: i64 = 8210298412799; + // Check the function input arguments + let val = if let Some((min, max)) = range { + match min { + min if (1..=LIMIT).contains(&min) => match max { + max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min..=max), + max if max >= 1 && max <= min => rand::thread_rng().gen_range(max..=min), + _ => return Err(Error::InvalidArguments { + name: String::from("rand::time"), + message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), + }), + }, + _ => return Err(Error::InvalidArguments { + name: String::from("rand::time"), + message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), + }), + } } else { - rand::random::() as i64 + rand::thread_rng().gen_range(0..=LIMIT) }; - Ok(Datetime::from(i).into()) + // Generate the random time + Ok(Utc.timestamp_opt(val, 0).earliest().unwrap().into()) } pub fn ulid(_: ()) -> Result {