diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 0c18e1f..b4bf484 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -31,6 +31,47 @@ impl Context{ ).await.map_err(Error::Response)? .json().await.map_err(Error::Reqwest) } + pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{ + let url_raw=format!("{}/scripts",self.0.base_url); + let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; + + { + let mut query_pairs=url.query_pairs_mut(); + query_pairs.append_pair("Page",config.Page.to_string().as_str()); + query_pairs.append_pair("Limit",config.Limit.to_string().as_str()); + if let Some(name)=config.Name{ + query_pairs.append_pair("Name",name); + } + if let Some(hash)=config.Hash{ + query_pairs.append_pair("Hash",hash); + } + if let Some(source)=config.Source{ + query_pairs.append_pair("Source",source); + } + if let Some(submission_id)=config.SubmissionID{ + query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str()); + } + } + + response_ok( + self.0.get(url).await.map_err(Error::Reqwest)? + ).await.map_err(Error::Response)? + .json().await.map_err(Error::Reqwest) + } + pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{ + let scripts=self.get_scripts(GetScriptsRequest{ + Page:1, + Limit:2, + Hash:Some(config.hash), + Name:None, + Source:None, + SubmissionID:None, + }).await.map_err(SingleItemError::Other)?; + if 1<scripts.len(){ + return Err(SingleItemError::DuplicateItems); + } + Ok(scripts.into_iter().next()) + } pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{ let url_raw=format!("{}/scripts",self.0.base_url); let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 1be3133..41e282e 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -60,9 +60,9 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R } } -#[derive(Clone,Copy,PartialEq,Eq,serde::Serialize,serde::Deserialize)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)] pub struct ScriptID(pub(crate)i64); -#[derive(Clone,Copy,serde::Serialize,serde::Deserialize)] +#[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] pub struct ScriptPolicyID(pub(crate)i64); #[allow(nonstandard_style)] @@ -70,7 +70,7 @@ pub struct GetScriptRequest{ pub ScriptID:ScriptID, } #[allow(nonstandard_style)] -#[derive(serde::Serialize)] +#[derive(Clone,Debug,serde::Serialize)] pub struct GetScriptsRequest<'a>{ pub Page:u32, pub Limit:u32, @@ -83,11 +83,12 @@ pub struct GetScriptsRequest<'a>{ #[serde(skip_serializing_if="Option::is_none")] pub SubmissionID:Option<i64>, } +#[derive(Clone,Copy,Debug)] pub struct HashRequest<'a>{ pub hash:&'a str, } #[allow(nonstandard_style)] -#[derive(serde::Deserialize)] +#[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptResponse{ pub ID:ScriptID, pub Name:String, @@ -96,7 +97,7 @@ pub struct ScriptResponse{ pub SubmissionID:i64, } #[allow(nonstandard_style)] -#[derive(serde::Serialize)] +#[derive(Clone,Debug,serde::Serialize)] pub struct CreateScriptRequest<'a>{ pub Name:&'a str, pub Source:&'a str, @@ -104,12 +105,12 @@ pub struct CreateScriptRequest<'a>{ pub SubmissionID:Option<i64>, } #[allow(nonstandard_style)] -#[derive(serde::Deserialize)] +#[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptIDResponse{ pub ID:ScriptID, } -#[derive(PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)] #[repr(i32)] pub enum Policy{ None=0, // not yet reviewed @@ -120,7 +121,7 @@ pub enum Policy{ } #[allow(nonstandard_style)] -#[derive(serde::Serialize)] +#[derive(Clone,Debug,serde::Serialize)] pub struct GetScriptPoliciesRequest<'a>{ pub Page:u32, pub Limit:u32, @@ -132,7 +133,7 @@ pub struct GetScriptPoliciesRequest<'a>{ pub Policy:Option<Policy>, } #[allow(nonstandard_style)] -#[derive(serde::Deserialize)] +#[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptPolicyResponse{ pub ID:ScriptPolicyID, pub FromScriptHash:String, @@ -140,20 +141,20 @@ pub struct ScriptPolicyResponse{ pub Policy:Policy } #[allow(nonstandard_style)] -#[derive(serde::Serialize)] +#[derive(Clone,Debug,serde::Serialize)] pub struct CreateScriptPolicyRequest{ pub FromScriptID:ScriptID, pub ToScriptID:ScriptID, pub Policy:Policy, } #[allow(nonstandard_style)] -#[derive(serde::Deserialize)] +#[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptPolicyIDResponse{ pub ID:ScriptPolicyID, } #[allow(nonstandard_style)] -#[derive(serde::Serialize)] +#[derive(Clone,Debug,serde::Serialize)] pub struct UpdateScriptPolicyRequest{ pub ID:ScriptPolicyID, #[serde(skip_serializing_if="Option::is_none")] @@ -165,6 +166,7 @@ pub struct UpdateScriptPolicyRequest{ } #[allow(nonstandard_style)] +#[derive(Clone,Debug)] pub struct UpdateSubmissionModelRequest{ pub SubmissionID:i64, pub ModelID:u64, @@ -172,15 +174,18 @@ pub struct UpdateSubmissionModelRequest{ } #[allow(nonstandard_style)] +#[derive(Clone,Debug)] pub struct ActionSubmissionUploadedRequest{ pub SubmissionID:i64, pub TargetAssetID:Option<u64>, } #[allow(nonstandard_style)] +#[derive(Clone,Debug)] pub struct ActionSubmissionAcceptedRequest{ pub SubmissionID:i64, pub StatusMessage:String, } +#[derive(Clone,Copy,Debug)] pub struct SubmissionID(pub i64); diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 7b4e437..dcbfd99 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -21,23 +21,33 @@ fn source_has_illegal_keywords(source:&str)->bool{ source.find("getfenv").is_some()||source.find("require").is_some() } +fn hash_source(source:&str)->String{ + let mut hasher=siphasher::sip::SipHasher::new(); + std::hash::Hasher::write(&mut hasher,source.as_bytes()); + let hash=std::hash::Hasher::finish(&hasher); + format!("{:016x}",hash) +} + #[allow(dead_code)] #[derive(Debug)] pub enum ValidateError{ - Flagged, - Blocked, - NotAllowed, - Get(rbx_asset::cookie::GetError), - ReadDom(ReadDomError), - ApiGetScriptPolicy(submissions_api::types::SingleItemError), + ScriptFlaggedIllegalKeyword, + ScriptBlocked(Option<submissions_api::types::ScriptID>), + ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>), + ModelFileDownload(rbx_asset::cookie::GetError), + ModelFileDecode(ReadDomError), + ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError), ApiGetScript(submissions_api::Error), ApiCreateScript(submissions_api::Error), ApiCreateScriptPolicy(submissions_api::Error), + ApiGetScriptFromHash(submissions_api::types::SingleItemError), ApiUpdateSubmissionModel(submissions_api::Error), ApiActionSubmissionValidate(submissions_api::Error), - WriteDom(rbx_binary::EncodeError), - Upload(rbx_asset::cookie::UploadError), - Create(rbx_asset::cookie::CreateError), + ModelFileRootMustHaveOneChild, + ModelFileChildRefIsNil, + ModelFileEncode(rbx_binary::EncodeError), + AssetUpload(rbx_asset::cookie::UploadError), + AssetCreate(rbx_asset::cookie::CreateError), } impl std::fmt::Display for ValidateError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -89,10 +99,10 @@ impl Validator{ let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:validate_info.ModelID, version:Some(validate_info.ModelVersion), - }).await.map_err(ValidateError::Get)?; + }).await.map_err(ValidateError::ModelFileDownload)?; // decode dom (slow!) - let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ReadDom)?; + let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ModelFileDecode)?; /* VALIDATE MAP */ @@ -105,12 +115,12 @@ impl Validator{ // check the source for illegal keywords if source_has_illegal_keywords(source){ // immediately abort - return Err(ValidateError::Flagged); + return Err(ValidateError::ScriptFlaggedIllegalKeyword); } // associate a name and policy with the source code // policy will be fetched from the database to replace the default policy script_map.insert(source.clone(),NamePolicy{ - name:script.name.clone(), + name:get_partial_path(&dom,script), policy:Policy::None, }); } @@ -121,14 +131,12 @@ impl Validator{ futures::stream::iter(script_map.iter_mut().map(Ok)) .try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,NamePolicy{policy,name})|async{ // get the hash - let mut hasher=siphasher::sip::SipHasher::new(); - std::hash::Hasher::write(&mut hasher,source.as_bytes()); - let hash=std::hash::Hasher::finish(&hasher); + let hash=hash_source(source.as_str()); // fetch the script policy let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{ - hash:format!("{:016x}",hash).as_str(), - }).await.map_err(ValidateError::ApiGetScriptPolicy)?; + hash:hash.as_str(), + }).await.map_err(ValidateError::ApiGetScriptPolicyFromHash)?; // write the policy to the script_map, fetching the replacement code if necessary if let Some(script_policy)=script_policy{ @@ -170,10 +178,22 @@ impl Validator{ if let Some(script)=dom.get_by_ref_mut(script_ref){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){ match script_map.get(source.as_str()).map(|p|&p.policy){ - Some(Policy::Blocked)=>return Err(ValidateError::Blocked), + Some(Policy::Blocked)=>{ + let hash=hash_source(source.as_str()); + let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{ + hash:hash.as_str(), + }).await.map_err(ValidateError::ApiGetScriptFromHash)?; + return Err(ValidateError::ScriptBlocked(script.map(|s|s.ID))); + }, None |Some(Policy::None) - =>return Err(ValidateError::NotAllowed), + =>{ + let hash=hash_source(source.as_str()); + let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{ + hash:hash.as_str(), + }).await.map_err(ValidateError::ApiGetScriptFromHash)?; + return Err(ValidateError::ScriptNotYetReviewed(script.map(|s|s.ID))); + }, Some(Policy::Allowed)=>(), Some(Policy::Delete)=>{ modified=true; @@ -195,7 +215,10 @@ impl Validator{ if modified{ // serialize model (slow!) let mut data=Vec::new(); - rbx_binary::to_writer(&mut data,&dom,dom.root().children()).map_err(ValidateError::WriteDom)?; + let &[map_ref]=dom.root().children()else{ + return Err(ValidateError::ModelFileRootMustHaveOneChild); + }; + rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(ValidateError::ModelFileEncode)?; // upload a model lol let model_id=if let Some(model_id)=validate_info.ValidatedModelID{ @@ -207,18 +230,22 @@ impl Validator{ ispublic:None, allowComments:None, groupId:None, - },data).await.map_err(ValidateError::Upload)?; + },data).await.map_err(ValidateError::AssetUpload)?; response.AssetId }else{ + // grab the map instance from the map re + let Some(map_instance)=dom.get_by_ref(map_ref)else{ + return Err(ValidateError::ModelFileChildRefIsNil); + }; // create new model let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ - name:dom.root().name.clone(), + name:map_instance.name.clone(), description:"".to_owned(), ispublic:true, allowComments:true, groupId:None, - },data).await.map_err(ValidateError::Create)?; + },data).await.map_err(ValidateError::AssetCreate)?; response.AssetId }; @@ -291,6 +318,25 @@ fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types:: } } +fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{ + struct ParentIter<'a>{ + dom:&'a rbx_dom_weak::WeakDom, + instance:Option<&'a rbx_dom_weak::Instance>, + } + impl<'a> Iterator for ParentIter<'a>{ + type Item=&'a rbx_dom_weak::Instance; + fn next(&mut self)->Option<Self::Item>{ + let parent=self.instance.map(|i|i.parent()).and_then(|p|self.dom.get_by_ref(p)); + core::mem::replace(&mut self.instance,parent) + } + } + + let mut tragic:Vec<_>=ParentIter{dom,instance:Some(instance)}.map(|i|i.name.as_str()).collect(); + tragic.pop(); + tragic.reverse(); + tragic.join(".") +} + fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{ let mut scripts=std::vec::Vec::new(); recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer");