mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-07 08:27:46 +00:00
printf: fix escape octal parsing
Co-authored-by: aryal <141743392+aryalaadi@users.noreply.github.com>
This commit is contained in:
parent
cd1e764581
commit
bbca2ffdbf
4 changed files with 47 additions and 21 deletions
|
@ -9,7 +9,7 @@ use std::env;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::io::{self, StdoutLock, Write};
|
use std::io::{self, StdoutLock, Write};
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::format::{parse_escape_only, EscapedChar, FormatChar};
|
use uucore::format::{parse_escape_only, EscapedChar, FormatChar, OctalParsing};
|
||||||
use uucore::{format_usage, help_about, help_section, help_usage};
|
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("echo.md");
|
const ABOUT: &str = help_about!("echo.md");
|
||||||
|
@ -135,7 +135,7 @@ fn execute(
|
||||||
}
|
}
|
||||||
|
|
||||||
if escaped {
|
if escaped {
|
||||||
for item in parse_escape_only(bytes) {
|
for item in parse_escape_only(bytes, OctalParsing::ThreeDigits) {
|
||||||
match item {
|
match item {
|
||||||
EscapedChar::End => return Ok(()),
|
EscapedChar::End => return Ok(()),
|
||||||
c => c.write(&mut *stdout_lock)?,
|
c => c.write(&mut *stdout_lock)?,
|
||||||
|
|
|
@ -17,24 +17,37 @@ pub enum EscapedChar {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub enum OctalParsing {
|
||||||
|
#[default]
|
||||||
|
TwoDigits = 2,
|
||||||
|
ThreeDigits = 3,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Base {
|
enum Base {
|
||||||
Oct = 8,
|
Oct(OctalParsing),
|
||||||
Hex = 16,
|
Hex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Base {
|
impl Base {
|
||||||
|
fn as_base(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Base::Oct(_) => 8,
|
||||||
|
Base::Hex => 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn max_digits(&self) -> u8 {
|
fn max_digits(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Self::Oct => 3,
|
Self::Oct(parsing) => *parsing as u8,
|
||||||
Self::Hex => 2,
|
Self::Hex => 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_digit(&self, c: u8) -> Option<u8> {
|
fn convert_digit(&self, c: u8) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Oct => {
|
Self::Oct(_) => {
|
||||||
if matches!(c, b'0'..=b'7') {
|
if matches!(c, b'0'..=b'7') {
|
||||||
Some(c - b'0')
|
Some(c - b'0')
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,7 +81,7 @@ fn parse_code(input: &mut &[u8], base: Base) -> Option<u8> {
|
||||||
let Some(n) = base.convert_digit(*c) else {
|
let Some(n) = base.convert_digit(*c) else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
ret = ret.wrapping_mul(base as u8).wrapping_add(n);
|
ret = ret.wrapping_mul(base.as_base()).wrapping_add(n);
|
||||||
*input = rest;
|
*input = rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +100,9 @@ fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
|
||||||
for _ in 1..digits {
|
for _ in 1..digits {
|
||||||
let (c, rest) = input.split_first()?;
|
let (c, rest) = input.split_first()?;
|
||||||
let n = Base::Hex.convert_digit(*c)?;
|
let n = Base::Hex.convert_digit(*c)?;
|
||||||
ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32);
|
ret = ret
|
||||||
|
.wrapping_mul(Base::Hex.as_base() as u32)
|
||||||
|
.wrapping_add(n as u32);
|
||||||
*input = rest;
|
*input = rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,13 +114,16 @@ fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
|
||||||
pub struct EscapeError {}
|
pub struct EscapeError {}
|
||||||
|
|
||||||
/// Parse an escape sequence, like `\n` or `\xff`, etc.
|
/// Parse an escape sequence, like `\n` or `\xff`, etc.
|
||||||
pub fn parse_escape_code(rest: &mut &[u8]) -> Result<EscapedChar, EscapeError> {
|
pub fn parse_escape_code(
|
||||||
|
rest: &mut &[u8],
|
||||||
|
zero_octal_parsing: OctalParsing,
|
||||||
|
) -> Result<EscapedChar, EscapeError> {
|
||||||
if let [c, new_rest @ ..] = rest {
|
if let [c, new_rest @ ..] = rest {
|
||||||
// This is for the \NNN syntax for octal sequences.
|
// This is for the \NNN syntax for octal sequences.
|
||||||
// Note that '0' is intentionally omitted because that
|
// Note that '0' is intentionally omitted because that
|
||||||
// would be the \0NNN syntax.
|
// would be the \0NNN syntax.
|
||||||
if let b'1'..=b'7' = c {
|
if let b'1'..=b'7' = c {
|
||||||
if let Some(parsed) = parse_code(rest, Base::Oct) {
|
if let Some(parsed) = parse_code(rest, Base::Oct(OctalParsing::ThreeDigits)) {
|
||||||
return Ok(EscapedChar::Byte(parsed));
|
return Ok(EscapedChar::Byte(parsed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +149,7 @@ pub fn parse_escape_code(rest: &mut &[u8]) -> Result<EscapedChar, EscapeError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b'0' => Ok(EscapedChar::Byte(
|
b'0' => Ok(EscapedChar::Byte(
|
||||||
parse_code(rest, Base::Oct).unwrap_or(b'\0'),
|
parse_code(rest, Base::Oct(zero_octal_parsing)).unwrap_or(b'\0'),
|
||||||
)),
|
)),
|
||||||
b'u' => Ok(EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0'))),
|
b'u' => Ok(EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0'))),
|
||||||
b'U' => Ok(EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0'))),
|
b'U' => Ok(EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0'))),
|
||||||
|
|
|
@ -51,7 +51,7 @@ use os_display::Quotable;
|
||||||
use crate::error::UError;
|
use crate::error::UError;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
escape::{parse_escape_code, EscapedChar},
|
escape::{parse_escape_code, EscapedChar, OctalParsing},
|
||||||
num_format::Formatter,
|
num_format::Formatter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -184,10 +184,12 @@ pub fn parse_spec_and_escape(
|
||||||
}
|
}
|
||||||
[b'\\', rest @ ..] => {
|
[b'\\', rest @ ..] => {
|
||||||
current = rest;
|
current = rest;
|
||||||
Some(match parse_escape_code(&mut current) {
|
Some(
|
||||||
Ok(c) => Ok(FormatItem::Char(c)),
|
match parse_escape_code(&mut current, OctalParsing::default()) {
|
||||||
Err(_) => Err(FormatError::MissingHex),
|
Ok(c) => Ok(FormatItem::Char(c)),
|
||||||
})
|
Err(_) => Err(FormatError::MissingHex),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
[c, rest @ ..] => {
|
[c, rest @ ..] => {
|
||||||
current = rest;
|
current = rest;
|
||||||
|
@ -224,13 +226,19 @@ pub fn parse_spec_only(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a format string containing escape sequences
|
/// Parse a format string containing escape sequences
|
||||||
pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator<Item = EscapedChar> + '_ {
|
pub fn parse_escape_only(
|
||||||
|
fmt: &[u8],
|
||||||
|
zero_octal_parsing: OctalParsing,
|
||||||
|
) -> impl Iterator<Item = EscapedChar> + '_ {
|
||||||
let mut current = fmt;
|
let mut current = fmt;
|
||||||
std::iter::from_fn(move || match current {
|
std::iter::from_fn(move || match current {
|
||||||
[] => None,
|
[] => None,
|
||||||
[b'\\', rest @ ..] => {
|
[b'\\', rest @ ..] => {
|
||||||
current = rest;
|
current = rest;
|
||||||
Some(parse_escape_code(&mut current).unwrap_or(EscapedChar::Backslash(b'x')))
|
Some(
|
||||||
|
parse_escape_code(&mut current, zero_octal_parsing)
|
||||||
|
.unwrap_or(EscapedChar::Backslash(b'x')),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
[c, rest @ ..] => {
|
[c, rest @ ..] => {
|
||||||
current = rest;
|
current = rest;
|
||||||
|
|
|
@ -12,7 +12,7 @@ use super::{
|
||||||
self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix,
|
self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix,
|
||||||
UnsignedIntVariant,
|
UnsignedIntVariant,
|
||||||
},
|
},
|
||||||
parse_escape_only, ArgumentIter, FormatChar, FormatError,
|
parse_escape_only, ArgumentIter, FormatChar, FormatError, OctalParsing,
|
||||||
};
|
};
|
||||||
use std::{io::Write, ops::ControlFlow};
|
use std::{io::Write, ops::ControlFlow};
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ impl Spec {
|
||||||
Self::EscapedString => {
|
Self::EscapedString => {
|
||||||
let s = args.get_str();
|
let s = args.get_str();
|
||||||
let mut parsed = Vec::new();
|
let mut parsed = Vec::new();
|
||||||
for c in parse_escape_only(s.as_bytes()) {
|
for c in parse_escape_only(s.as_bytes(), OctalParsing::default()) {
|
||||||
match c.write(&mut parsed)? {
|
match c.write(&mut parsed)? {
|
||||||
ControlFlow::Continue(()) => {}
|
ControlFlow::Continue(()) => {}
|
||||||
ControlFlow::Break(()) => {
|
ControlFlow::Break(()) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue