fix: we clip the change span in from 0 to the buffer length, this avoids that

badly formatted changes will crash the worker.
Previously I was able to crash the worker by sending oob changes.
chore: improved text change docs a lil bit
This commit is contained in:
cschen 2024-09-02 11:31:36 +02:00
parent d0d57f6fd5
commit 0b1a542ed5
2 changed files with 12 additions and 6 deletions

View file

@ -40,22 +40,24 @@ impl TextChange {
#[cfg_attr(feature = "python", pyo3::pymethods)] #[cfg_attr(feature = "python", pyo3::pymethods)]
impl TextChange { impl TextChange {
/// returns true if this TextChange deletes existing text /// A change is a "deletion" if the change range span is bigger than zero.
/// This is not exclusive, a change can be both an insertion and a deletion.
pub fn is_delete(&self) -> bool { pub fn is_delete(&self) -> bool {
self.start < self.end self.start < self.end
} }
/// returns true if this TextChange adds new text /// A change is an "Insertion" if the content is not empty.
/// This is not exclusive, a change can be both an insertion and a deletion.
pub fn is_insert(&self) -> bool { pub fn is_insert(&self) -> bool {
!self.content.is_empty() !self.content.is_empty()
} }
/// returns true if this TextChange is effectively as no-op /// Returns true if this TextChange is effectively as no-op
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
!self.is_delete() && !self.is_insert() !self.is_delete() && !self.is_insert()
} }
/// applies this text change to given text, returning a new string /// Applies this text change to given text, returning a new string
pub fn apply(&self, txt: &str) -> String { pub fn apply(&self, txt: &str) -> String {
let pre_index = std::cmp::min(self.start as usize, txt.len()); let pre_index = std::cmp::min(self.start as usize, txt.len());
let pre = txt.get(..pre_index).unwrap_or("").to_string(); let pre = txt.get(..pre_index).unwrap_or("").to_string();

View file

@ -106,13 +106,17 @@ impl ControllerWorker<TextChange> for BufferWorker {
Some((change, ack)) => { Some((change, ack)) => {
let agent_id = oplog.get_or_create_agent_id(&self.user_id.to_string()); let agent_id = oplog.get_or_create_agent_id(&self.user_id.to_string());
let last_ver = oplog.local_version(); let last_ver = oplog.local_version();
// clip to buffer extents
let clip_end = std::cmp::min(branch.len(), change.end as usize);
let clip_start = std::cmp::max(0, change.start as usize);
// in case we have a "replace" span
if change.is_delete() { if change.is_delete() {
branch.delete_without_content(&mut oplog, agent_id, change.start as usize..change.end as usize); branch.delete_without_content(&mut oplog, agent_id, clip_start..clip_end);
} }
if change.is_insert() { if change.is_insert() {
branch.insert(&mut oplog, agent_id, change.start as usize, &change.content); branch.insert(&mut oplog, agent_id, clip_start, &change.content);
} }
if change.is_delete() || change.is_insert() { if change.is_delete() || change.is_insert() {