Skip to content

Commit 9c6d1fc

Browse files
committed
printf: fix escape octal parsing
1 parent cd1e764 commit 9c6d1fc

File tree

4 files changed

+44
-17
lines changed

4 files changed

+44
-17
lines changed

src/uu/echo/src/echo.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::env;
99
use std::ffi::{OsStr, OsString};
1010
use std::io::{self, StdoutLock, Write};
1111
use uucore::error::{UResult, USimpleError};
12-
use uucore::format::{parse_escape_only, EscapedChar, FormatChar};
12+
use uucore::format::{parse_escape_only, EscapedChar, FormatChar, OctalParsing};
1313
use uucore::{format_usage, help_about, help_section, help_usage};
1414

1515
const ABOUT: &str = help_about!("echo.md");
@@ -135,7 +135,7 @@ fn execute(
135135
}
136136

137137
if escaped {
138-
for item in parse_escape_only(bytes) {
138+
for item in parse_escape_only(bytes, Some(OctalParsing::ThreeDigits)) {
139139
match item {
140140
EscapedChar::End => return Ok(()),
141141
c => c.write(&mut *stdout_lock)?,

src/uucore/src/lib/features/format/escape.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,36 @@ pub enum EscapedChar {
1717
End,
1818
}
1919

20-
#[repr(u8)]
20+
#[derive(Clone, Copy)]
21+
pub enum OctalParsing {
22+
TwoDigits = 2,
23+
ThreeDigits = 3,
24+
}
25+
2126
#[derive(Clone, Copy)]
2227
enum Base {
23-
Oct = 8,
24-
Hex = 16,
28+
Oct(OctalParsing),
29+
Hex,
2530
}
2631

2732
impl Base {
33+
fn as_base(&self) -> u8 {
34+
match self {
35+
Base::Oct(_) => 8,
36+
Base::Hex => 16,
37+
}
38+
}
39+
2840
fn max_digits(&self) -> u8 {
2941
match self {
30-
Self::Oct => 3,
42+
Self::Oct(parsing) => *parsing as u8,
3143
Self::Hex => 2,
3244
}
3345
}
3446

3547
fn convert_digit(&self, c: u8) -> Option<u8> {
3648
match self {
37-
Self::Oct => {
49+
Self::Oct(_) => {
3850
if matches!(c, b'0'..=b'7') {
3951
Some(c - b'0')
4052
} else {
@@ -68,7 +80,7 @@ fn parse_code(input: &mut &[u8], base: Base) -> Option<u8> {
6880
let Some(n) = base.convert_digit(*c) else {
6981
break;
7082
};
71-
ret = ret.wrapping_mul(base as u8).wrapping_add(n);
83+
ret = ret.wrapping_mul(base.as_base()).wrapping_add(n);
7284
*input = rest;
7385
}
7486

@@ -87,7 +99,9 @@ fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
8799
for _ in 1..digits {
88100
let (c, rest) = input.split_first()?;
89101
let n = Base::Hex.convert_digit(*c)?;
90-
ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32);
102+
ret = ret
103+
.wrapping_mul(Base::Hex.as_base() as u32)
104+
.wrapping_add(n as u32);
91105
*input = rest;
92106
}
93107

@@ -99,13 +113,16 @@ fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
99113
pub struct EscapeError {}
100114

101115
/// Parse an escape sequence, like `\n` or `\xff`, etc.
102-
pub fn parse_escape_code(rest: &mut &[u8]) -> Result<EscapedChar, EscapeError> {
116+
pub fn parse_escape_code(
117+
rest: &mut &[u8],
118+
zero_octal_parsing: Option<OctalParsing>,
119+
) -> Result<EscapedChar, EscapeError> {
103120
if let [c, new_rest @ ..] = rest {
104121
// This is for the \NNN syntax for octal sequences.
105122
// Note that '0' is intentionally omitted because that
106123
// would be the \0NNN syntax.
107124
if let b'1'..=b'7' = c {
108-
if let Some(parsed) = parse_code(rest, Base::Oct) {
125+
if let Some(parsed) = parse_code(rest, Base::Oct(OctalParsing::ThreeDigits)) {
109126
return Ok(EscapedChar::Byte(parsed));
110127
}
111128
}
@@ -131,7 +148,11 @@ pub fn parse_escape_code(rest: &mut &[u8]) -> Result<EscapedChar, EscapeError> {
131148
}
132149
}
133150
b'0' => Ok(EscapedChar::Byte(
134-
parse_code(rest, Base::Oct).unwrap_or(b'\0'),
151+
parse_code(
152+
rest,
153+
Base::Oct(zero_octal_parsing.unwrap_or(OctalParsing::TwoDigits)),
154+
)
155+
.unwrap_or(b'\0'),
135156
)),
136157
b'u' => Ok(EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0'))),
137158
b'U' => Ok(EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0'))),

src/uucore/src/lib/features/format/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use os_display::Quotable;
5151
use crate::error::UError;
5252

5353
pub use self::{
54-
escape::{parse_escape_code, EscapedChar},
54+
escape::{parse_escape_code, EscapedChar, OctalParsing},
5555
num_format::Formatter,
5656
};
5757

@@ -184,7 +184,7 @@ pub fn parse_spec_and_escape(
184184
}
185185
[b'\\', rest @ ..] => {
186186
current = rest;
187-
Some(match parse_escape_code(&mut current) {
187+
Some(match parse_escape_code(&mut current, None) {
188188
Ok(c) => Ok(FormatItem::Char(c)),
189189
Err(_) => Err(FormatError::MissingHex),
190190
})
@@ -224,13 +224,19 @@ pub fn parse_spec_only(
224224
}
225225

226226
/// Parse a format string containing escape sequences
227-
pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator<Item = EscapedChar> + '_ {
227+
pub fn parse_escape_only(
228+
fmt: &[u8],
229+
zero_octal_parsing: Option<OctalParsing>,
230+
) -> impl Iterator<Item = EscapedChar> + '_ {
228231
let mut current = fmt;
229232
std::iter::from_fn(move || match current {
230233
[] => None,
231234
[b'\\', rest @ ..] => {
232235
current = rest;
233-
Some(parse_escape_code(&mut current).unwrap_or(EscapedChar::Backslash(b'x')))
236+
Some(
237+
parse_escape_code(&mut current, zero_octal_parsing)
238+
.unwrap_or(EscapedChar::Backslash(b'x')),
239+
)
234240
}
235241
[c, rest @ ..] => {
236242
current = rest;

src/uucore/src/lib/features/format/spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ impl Spec {
348348
Self::EscapedString => {
349349
let s = args.get_str();
350350
let mut parsed = Vec::new();
351-
for c in parse_escape_only(s.as_bytes()) {
351+
for c in parse_escape_only(s.as_bytes(), None) {
352352
match c.write(&mut parsed)? {
353353
ControlFlow::Continue(()) => {}
354354
ControlFlow::Break(()) => {

0 commit comments

Comments
 (0)