Skip to content

Commit 3c93a65

Browse files
backups: Count grapheme clusters instead of characters in poll strings
1 parent dbf4d62 commit 3c93a65

File tree

10 files changed

+34
-20
lines changed

10 files changed

+34
-20
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ tonic = "0.13.1"
210210
tonic-build = "0.13.1"
211211
tower = "0.5.2"
212212
tungstenite = "0.27.0"
213+
unicode-segmentation = "1.12.0"
213214
url = "2.4.1"
214215
uuid = "1.5"
215216
visibility = "0.1.1"

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ v0.85.1
33
- Backups / SVRB - add support for multiple SVRB backends when new enclaves need to roll out.
44

55
- Typed APIs: `UnauthUsernamesService.lookUpUsernameLink` has been added.
6+
7+
- Backup validator: count grapheme clusters instead of characters in poll strings.

acknowledgments/acknowledgments-android-testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3601,7 +3601,7 @@ DEALINGS IN THE SOFTWARE.
36013601
36023602
```
36033603

3604-
## gimli 0.31.1, heck 0.5.0, unicode-xid 0.2.6
3604+
## gimli 0.31.1, heck 0.5.0, unicode-segmentation 1.12.0, unicode-xid 0.2.6
36053605

36063606
```
36073607
Copyright (c) 2015 The Rust Project Developers

acknowledgments/acknowledgments-android.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3601,7 +3601,7 @@ DEALINGS IN THE SOFTWARE.
36013601
36023602
```
36033603

3604-
## gimli 0.31.1, heck 0.5.0, unicode-xid 0.2.6
3604+
## gimli 0.31.1, heck 0.5.0, unicode-segmentation 1.12.0, unicode-xid 0.2.6
36053605

36063606
```
36073607
Copyright (c) 2015 The Rust Project Developers

acknowledgments/acknowledgments-desktop.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3716,7 +3716,7 @@ DEALINGS IN THE SOFTWARE.
37163716
37173717
```
37183718

3719-
## gimli 0.31.1, heck 0.5.0, unicode-xid 0.2.6
3719+
## gimli 0.31.1, heck 0.5.0, unicode-segmentation 1.12.0, unicode-xid 0.2.6
37203720

37213721
```
37223722
Copyright (c) 2015 The Rust Project Developers

acknowledgments/acknowledgments-ios.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3857,7 +3857,7 @@ DEALINGS IN THE SOFTWARE.
38573857
<key>License</key>
38583858
<string>MIT License</string>
38593859
<key>Title</key>
3860-
<string>gimli 0.31.1, heck 0.5.0, unicode-xid 0.2.6</string>
3860+
<string>gimli 0.31.1, heck 0.5.0, unicode-segmentation 1.12.0, unicode-xid 0.2.6</string>
38613861
<key>Type</key>
38623862
<string>PSGroupSpecifier</string>
38633863
</dict>

acknowledgments/acknowledgments.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ <h1>Third Party Licenses</h1>
4646

4747
<h2>Overview of licenses:</h2>
4848
<ul class="licenses-overview">
49-
<li><a href="#MIT">MIT License</a> (346)</li>
49+
<li><a href="#MIT">MIT License</a> (347)</li>
5050
<li><a href="#AGPL-3.0-only">GNU Affero General Public License v3.0 only</a> (34)</li>
5151
<li><a href="#Apache-2.0">Apache License 2.0</a> (28)</li>
5252
<li><a href="#BSD-3-Clause">BSD 3-Clause &quot;New&quot; or &quot;Revised&quot; License</a> (10)</li>
@@ -4292,6 +4292,7 @@ <h4>Used by:</h4>
42924292
<ul class="license-used-by">
42934293
<li><a href="https://github.com/gimli-rs/gimli">gimli 0.31.1</a></li>
42944294
<li><a href="https://github.com/withoutboats/heck">heck 0.5.0</a></li>
4295+
<li><a href="https://github.com/unicode-rs/unicode-segmentation">unicode-segmentation 1.12.0</a></li>
42954296
<li><a href="https://github.com/unicode-rs/unicode-xid">unicode-xid 0.2.6</a></li>
42964297
</ul>
42974298
<pre class="license-text">Copyright (c) 2015 The Rust Project Developers

rust/message-backup/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static_assertions = { workspace = true }
8686
strum = { workspace = true, features = ["derive"] }
8787
subtle = { workspace = true }
8888
thiserror = { workspace = true }
89+
unicode-segmentation = { workspace = true }
8990
uuid = { workspace = true, features = ["serde"] }
9091
visibility = { workspace = true }
9192

rust/message-backup/src/backup/chat/poll.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::ops::RangeInclusive;
1010
use derive_where::derive_where;
1111
use intmap::IntMap;
1212
use itertools::Itertools as _;
13+
use unicode_segmentation::UnicodeSegmentation as _;
1314

1415
use crate::backup::TryIntoWith;
1516
use crate::backup::chat::reactions::{ReactionError, ReactionSet};
@@ -69,8 +70,8 @@ pub enum PollError {
6970
UnknownVoterId,
7071
/// voter id is not self nor contact
7172
InvalidVoterId,
72-
/// poll option size ({0}) is out of bounds
73-
InvalidPollStringSize(usize),
73+
/// {0} size ({1}) is out of bounds
74+
InvalidPollStringSize(&'static str, usize),
7475
/// {0} option(s) is too few for a poll
7576
TooFewOptions(usize),
7677
/// {0}
@@ -123,7 +124,7 @@ impl<R: Clone, C: LookupPair<RecipientId, MinimalRecipientData, R> + ReportUnusu
123124
votes,
124125
special_fields: _,
125126
} = self;
126-
validate_poll_string_len(&option)?;
127+
validate_poll_string_len(&option, "poll option")?;
127128
validate_unique_voters(&votes)?;
128129
let votes = votes
129130
.into_iter()
@@ -148,7 +149,7 @@ impl<R: Clone, C: LookupPair<RecipientId, MinimalRecipientData, R> + ReportUnusu
148149
reactions,
149150
special_fields: _,
150151
} = self;
151-
validate_poll_string_len(&question)?;
152+
validate_poll_string_len(&question, "poll question")?;
152153
if options.len() < MIN_POLL_OPTIONS {
153154
return Err(Self::Error::TooFewOptions(options.len()));
154155
}
@@ -177,7 +178,7 @@ impl<C: ReportUnusualTimestamp> TryIntoWith<PollTerminate, C> for PollTerminateP
177178
question,
178179
special_fields: _,
179180
} = self;
180-
validate_poll_string_len(&question)?;
181+
validate_poll_string_len(&question, "poll question")?;
181182
let target_sent_timestamp = Timestamp::from_millis(
182183
targetSentTimestamp,
183184
"PollTerminateUpdate.targetSentTimestamp",
@@ -190,10 +191,10 @@ impl<C: ReportUnusualTimestamp> TryIntoWith<PollTerminate, C> for PollTerminateP
190191
}
191192
}
192193

193-
fn validate_poll_string_len(s: &str) -> Result<(), PollError> {
194-
let len = s.len();
194+
fn validate_poll_string_len(s: &str, description: &'static str) -> Result<(), PollError> {
195+
let len = s.graphemes(true).count();
195196
if !POLL_STRING_LENGTH_RANGE.contains(&len) {
196-
return Err(PollError::InvalidPollStringSize(len));
197+
return Err(PollError::InvalidPollStringSize(description, len));
197198
}
198199
Ok(())
199200
}
@@ -270,10 +271,11 @@ mod test {
270271

271272
#[test_case("a" => Ok(()); "lower bound")]
272273
#[test_case(&format!("{:0100}", 0) => Ok(()); "upper bound")]
273-
#[test_case("" => Err(PollError::InvalidPollStringSize(0)); "too short")]
274-
#[test_case(&format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize(101)); "too long")]
274+
#[test_case("" => Err(PollError::InvalidPollStringSize("test string", 0)); "too short")]
275+
#[test_case(&format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize("test string", 101)); "too long")]
276+
#[test_case("🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒" => Ok(()); "grapheme clusters")]
275277
fn length_check(s: &str) -> Result<(), PollError> {
276-
validate_poll_string_len(s)
278+
validate_poll_string_len(s, "test string")
277279
}
278280

279281
fn poll_option_proto(option: &str) -> PollOptionProto {
@@ -361,10 +363,10 @@ mod test {
361363
#[test_case(|x| x.options = vec![] => Err(PollError::TooFewOptions(0)); "not an option")]
362364
#[test_case(|x| x.options.truncate(1) => Err(PollError::TooFewOptions(1)); "but one option")]
363365
#[test_case(|x| x.options.truncate(2) => Ok(()); "barely enough choice")]
364-
#[test_case(|x| x.question = "".to_string() => Err(PollError::InvalidPollStringSize(0)); "empty question")]
366+
#[test_case(|x| x.question = "".to_string() => Err(PollError::InvalidPollStringSize("poll question", 0)); "empty question")]
365367
#[test_case(|x| x.question = "a".to_string() => Ok(()); "question len lower bound")]
366368
#[test_case(|x| x.question = format!("{:0100}", 0) => Ok(()); "question len upper bound")]
367-
#[test_case(|x| x.question = format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize(101)); "question too long")]
369+
#[test_case(|x| x.question = format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize("poll question", 101)); "question too long")]
368370
#[test_case(|x| x.reactions.clear() => Ok(()); "no reactions")]
369371
#[test_case(|x| x.reactions.push(ReactionProto::default()) => Err(PollError::Reaction(ReactionError::EmptyEmoji)); "invalid reaction")]
370372
fn poll(modify: fn(&mut PollProto)) -> Result<(), PollError> {
@@ -401,10 +403,10 @@ mod test {
401403
#[test_case(|_| {} => Ok(()); "happy path")]
402404
#[test_case(|x| x.targetSentTimestamp = Timestamp::MAX_SAFE_TIMESTAMP_MS + 1 =>
403405
Err(PollError::InvalidTimestamp(TimestampError("PollTerminateUpdate.targetSentTimestamp", Timestamp::MAX_SAFE_TIMESTAMP_MS + 1))); "bad timestamp")]
404-
#[test_case(|x| x.question = "".to_string() => Err(PollError::InvalidPollStringSize(0)); "empty question")]
406+
#[test_case(|x| x.question = "".to_string() => Err(PollError::InvalidPollStringSize("poll question", 0)); "empty question")]
405407
#[test_case(|x| x.question = "a".to_string() => Ok(()); "question len lower bound")]
406408
#[test_case(|x| x.question = format!("{:0100}", 0) => Ok(()); "question len upper bound")]
407-
#[test_case(|x| x.question = format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize(101)); "question too long")]
409+
#[test_case(|x| x.question = format!("{:0101}", 0) => Err(PollError::InvalidPollStringSize("poll question", 101)); "question too long")]
408410
fn poll_terminate(modify: fn(&mut PollTerminateProto)) -> Result<(), PollError> {
409411
let mut terminate = poll_terminate_proto();
410412
modify(&mut terminate);

0 commit comments

Comments
 (0)