Skip to content

Commit

Permalink
Fix unhandled negative offset
Browse files Browse the repository at this point in the history
  • Loading branch information
Eason0729 committed Aug 18, 2024
1 parent 53abd90 commit 790d75c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 20 deletions.
8 changes: 8 additions & 0 deletions backend/migration/src/m20231207_000001_create_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion backend/src/entity/util/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -190,3 +190,21 @@ impl<E: EntityTrait> MaxCount<E> {
})
}
}

/// 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)),
}
}
39 changes: 26 additions & 13 deletions backend/src/entity/util/paginator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -362,6 +362,14 @@ impl<S: SortSource<R>, R: Reflect<S::Entity>> PaginateRaw for ColumnPaginator<S,
}
}

/// A paginator that may be initialized
///
/// Key-set Paginator requires information from last fetched column
/// (sometimes called trailing information) to be initialized.
///
/// **Can only be serialized or deserialized when initialized.**
///
/// **Calling fetch with size 0 doesn't initialize it!**
#[derive(Serialize, Default)]
pub enum UninitPaginator<P: PaginateRaw> {
#[serde(skip)]
Expand Down Expand Up @@ -390,9 +398,15 @@ impl<'de, P: PaginateRaw> Deserialize<'de> for UninitPaginator<P> {
}

impl<P: PaginateRaw> UninitPaginator<P> {
/// construct a paginator with its initial query argument.
pub fn new(data: <P::Source as PagerData>::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,
Expand All @@ -401,32 +415,31 @@ impl<P: PaginateRaw> UninitPaginator<P> {
db: &DatabaseConnection,
) -> Result<Vec<P::Reflect>, 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();
}
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<u64, Error>
where
P: Remain,
Expand Down
4 changes: 3 additions & 1 deletion backend/src/server/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
23 changes: 18 additions & 5 deletions backend/src/util/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ macro_rules! list_paginator_request {
paste::paste! {
impl BoundCheck for [<List $n Request>] {
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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 790d75c

Please sign in to comment.