From 790d75cf6edf1f17601bad711ee281c89167ae9e Mon Sep 17 00:00:00 2001 From: "Eason(G Ray)" <30045503+Eason0729@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:31:17 +0800 Subject: [PATCH] Fix unhandled negative offset --- .../src/m20231207_000001_create_table.rs | 8 ++++ backend/src/entity/util/helper.rs | 20 +++++++++- backend/src/entity/util/paginator.rs | 39 ++++++++++++------- backend/src/server/db.rs | 4 +- backend/src/util/bound.rs | 23 ++++++++--- 5 files changed, 74 insertions(+), 20 deletions(-) diff --git a/backend/migration/src/m20231207_000001_create_table.rs b/backend/migration/src/m20231207_000001_create_table.rs index b7d2519..bd95c52 100644 --- a/backend/migration/src/m20231207_000001_create_table.rs +++ b/backend/migration/src/m20231207_000001_create_table.rs @@ -668,17 +668,25 @@ impl MigrationTrait for Migration { index!(manager, Problem, AcceptCount); index!(manager, Problem, Difficulty); index!(manager, Problem, Order); + index!(manager, Problem, Content); + index!(manager, Problem, Title); + index!(manager, Submit, Committed); index!(manager, Submit, Time); index!(manager, Submit, Memory); + index!(manager, Submit, UserId); + index!(manager, Contest, Hoster); index!(manager, Contest, Public); index!(manager, Contest, End); index!(manager, Contest, Begin); + index!(manager, User, Score); index!(manager, User, Username); + index!(manager, Token, Rand); index!(manager, Token, Expiry); + index!(manager, Chat, CreateAt); manager diff --git a/backend/src/entity/util/helper.rs b/backend/src/entity/util/helper.rs index b199553..5d69f9b 100644 --- a/backend/src/entity/util/helper.rs +++ b/backend/src/entity/util/helper.rs @@ -4,8 +4,8 @@ use std::ops::Deref; -use sea_orm::*; use sea_orm::sea_query::*; +use sea_orm::*; use tracing::instrument; use crate::util::error::Error; @@ -190,3 +190,21 @@ impl MaxCount { }) } } + +/// convert sized span to single-direction span +/// +/// Return None if span cannot be converted(cross boundary). +/// +/// It panics if overflow(u64 to i64). +/// +/// See `dev.md` for sized span and single-direction span. +pub(super) fn to_inner_size_offset(size: u64, offset: i64) -> Option<(i64, u64)> { + // cross boundary + if offset.is_negative() && size > offset.unsigned_abs() { + return None; + } + match offset.is_negative() { + true => Some((-(size as i64), offset.unsigned_abs() - size)), + false => Some((size as i64, offset as u64)), + } +} diff --git a/backend/src/entity/util/paginator.rs b/backend/src/entity/util/paginator.rs index ff47389..7af82bb 100644 --- a/backend/src/entity/util/paginator.rs +++ b/backend/src/entity/util/paginator.rs @@ -20,7 +20,7 @@ use std::marker::PhantomData; use crate::util::error::Error; -const PAGINATE_GUARANTEE: u64 = 96; +const PAGINATE_GUARANTEE: u64 = 256; #[async_trait] pub trait PaginateRaw @@ -362,6 +362,14 @@ impl, R: Reflect> PaginateRaw for ColumnPaginator { #[serde(skip)] @@ -390,9 +398,15 @@ impl<'de, P: PaginateRaw> Deserialize<'de> for UninitPaginator

{ } impl UninitPaginator

{ + /// construct a paginator with its initial query argument. pub fn new(data: ::Data, start_from_end: bool) -> Self { Self::Uninit(data, start_from_end) } + /// fetch some entries + /// + /// Return `Error::BadArgument` when + /// 1. offset is when paginator has yet to be initialized and offset is negative + /// 2. span crosses the boundary, pub async fn fetch( &mut self, size: u64, @@ -401,17 +415,8 @@ impl UninitPaginator

{ db: &DatabaseConnection, ) -> Result, Error> { if let UninitPaginator::Init(x) = self { - let size = size.min((i64::MAX - 1) as u64) as i64; - let offset = offset.max(i64::MIN + 1); - let (size, offset) = match offset < 0 { - true => ( - -size, - (-offset) - .checked_sub(size) - .ok_or(Error::BadArgument("size"))? as u64, - ), - false => (size, offset as u64), - }; + let (size, offset) = + to_inner_size_offset(size, offset).ok_or(Error::BadArgument("size"))?; x.fetch(auth, size, offset, db).await.map(|mut x| { if size.is_negative() { x.reverse(); @@ -419,14 +424,22 @@ impl UninitPaginator

{ x }) } else if let UninitPaginator::Uninit(x, start_from_end) = std::mem::take(self) { + if offset.is_negative() { + return Err(Error::BadArgument("offset")); + } let (paginator, list) = - P::new_fetch(x, auth, size, offset.max(0) as u64, start_from_end, db).await?; + P::new_fetch(x, auth, size, offset as u64, start_from_end, db).await?; *self = UninitPaginator::Init(paginator); Ok(list) } else { unreachable!("Cannot in middle state") } } + /// get how many column are remaining the **absolute forward** direction of paginator + /// + /// If more than [`PAGINATE_GUARANTEE`], it returns [`PAGINATE_GUARANTEE`]. + /// + /// It passes [`sea_orm::DbErr`] to the callee. pub async fn remain(&self, auth: &Auth, db: &DatabaseConnection) -> Result where P: Remain, diff --git a/backend/src/server/db.rs b/backend/src/server/db.rs index e4b426c..4d1c20b 100644 --- a/backend/src/server/db.rs +++ b/backend/src/server/db.rs @@ -48,7 +48,9 @@ pub async fn init( #[cfg(feature = "standalone")] /// Run migration async fn migrate(db: &DatabaseConnection) -> super::Result<()> { - <::migration::Migrator as migration::MigratorTrait>::up(db,None).await.map_err(InitError::AutoMigrate) + <::migration::Migrator as migration::MigratorTrait>::up(db, None) + .await + .map_err(InitError::AutoMigrate) } #[instrument(skip_all, name = "construct_admin")] diff --git a/backend/src/util/bound.rs b/backend/src/util/bound.rs index 845a0fb..2c46a87 100644 --- a/backend/src/util/bound.rs +++ b/backend/src/util/bound.rs @@ -53,7 +53,7 @@ macro_rules! list_paginator_request { paste::paste! { impl BoundCheck for [] { fn check(&self) -> bool { - if self.size==0{ + if self.size == 0 || self.size >= i32::MAX as u64 || self.offset.unsigned_abs() >= i32::MAX as u64{ true }else if let Some(x) = &self.request { (match x { @@ -82,7 +82,12 @@ list_paginator_request!(User); impl BoundCheck for ListTokenRequest { fn check(&self) -> bool { - if let Some(list_token_request::Request::Paginator(x)) = &self.request { + if self.size == 0 + || self.size >= i32::MAX as u64 + || self.offset.unsigned_abs() >= i32::MAX as u64 + { + true + } else if let Some(list_token_request::Request::Paginator(x)) = &self.request { x.len() > 512 } else { false @@ -92,12 +97,15 @@ impl BoundCheck for ListTokenRequest { impl BoundCheck for ListChatRequest { fn check(&self) -> bool { - self.offset == 0 || self.offset > 4096 || self.size > 128 + self.offset == 0 || self.offset.unsigned_abs() > 4096 || self.size > 128 } } impl BoundCheck for ListSubmitRequest { fn check(&self) -> bool { - if self.offset == 0 { + if self.size == 0 + || self.size >= i32::MAX as u64 + || self.offset.unsigned_abs() >= i32::MAX as u64 + { true } else if let Some(list_submit_request::Request::Paginator(x)) = &self.request { x.len() > 512 @@ -108,7 +116,12 @@ impl BoundCheck for ListSubmitRequest { } impl BoundCheck for ListTestcaseRequest { fn check(&self) -> bool { - if let Some(list_testcase_request::Request::Paginator(x)) = &self.request { + if self.size == 0 + || self.size >= i32::MAX as u64 + || self.offset.unsigned_abs() >= i32::MAX as u64 + { + true + } else if let Some(list_testcase_request::Request::Paginator(x)) = &self.request { x.len() > 512 } else { false