swf: SwfStr no longer contains encoding

SwfStr is now an unsized slice analogous to `str` or `bstr`.
The desired encoding must be supplied when converting to String.
This commit is contained in:
Mike Welsh 2021-01-20 12:46:22 -08:00
parent 700c3c1767
commit 66256dd3be
18 changed files with 254 additions and 182 deletions

View File

@ -559,7 +559,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Action::Return => self.action_return(),
Action::SetMember => self.action_set_member(),
Action::SetProperty => self.action_set_property(),
Action::SetTarget(target) => self.action_set_target(&target.to_str_lossy()),
Action::SetTarget(target) => {
self.action_set_target(&target.to_str_lossy(self.encoding()))
}
Action::SetTarget2 => self.action_set_target2(),
Action::SetVariable => self.action_set_variable(),
Action::StackSwap => self.action_stack_swap(),
@ -889,13 +891,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_constant_pool(
&mut self,
constant_pool: &[SwfStr<'_>],
constant_pool: &[&'_ SwfStr],
) -> Result<FrameControl<'gc>, Error<'gc>> {
self.context.avm1.constant_pool = GcCell::allocate(
self.context.gc_context,
constant_pool
.iter()
.map(|s| (*s).to_string_lossy())
.map(|s| (*s).to_string_lossy(self.encoding()))
.collect(),
);
self.set_constant_pool(self.context.avm1.constant_pool);
@ -911,11 +913,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_define_function(
&mut self,
name: SwfStr<'_>,
params: &[SwfStr<'_>],
name: &'_ SwfStr,
params: &[&'_ SwfStr],
actions: SwfSlice,
) -> Result<FrameControl<'gc>, Error<'gc>> {
let name = name.to_str_lossy();
let name = name.to_str_lossy(self.encoding());
let name = name.as_ref();
let swf_version = self.swf_version();
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
@ -982,7 +984,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if action_func.name.is_empty() {
self.context.avm1.push(func_obj);
} else {
self.define(&action_func.name.to_str_lossy(), func_obj);
self.define(&action_func.name.to_str_lossy(self.encoding()), func_obj);
}
Ok(FrameControl::Continue)
@ -1220,12 +1222,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_get_url(
&mut self,
url: SwfStr<'_>,
target: SwfStr<'_>,
url: &'_ SwfStr,
target: &'_ SwfStr,
) -> Result<FrameControl<'gc>, Error<'gc>> {
let target = target.to_str_lossy();
let target = target.to_str_lossy(self.encoding());
let target = target.as_ref();
let url = url.to_string_lossy();
let url = url.to_string_lossy(self.encoding());
if target.starts_with("_level") && target.len() > 6 {
match target[6..].parse::<u32>() {
Ok(level_id) => {
@ -1425,13 +1427,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue)
}
fn action_goto_label(&mut self, label: SwfStr<'_>) -> Result<FrameControl<'gc>, Error<'gc>> {
fn action_goto_label(&mut self, label: &'_ SwfStr) -> Result<FrameControl<'gc>, Error<'gc>> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
if let Some(frame) = clip.frame_label_to_number(&label.to_str_lossy()) {
if let Some(frame) =
clip.frame_label_to_number(&label.to_str_lossy(self.encoding()))
{
clip.goto_frame(&mut self.context, frame, true);
} else {
avm_warn!(self, "GoToLabel: Frame label '{}' not found", label);
avm_warn!(self, "GoToLabel: Frame label '{:?}' not found", label);
}
} else {
avm_warn!(self, "GoToLabel: Target is not a MovieClip");
@ -1776,7 +1780,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
SwfValue::Float(v) => f64::from(*v).into(),
SwfValue::Double(v) => (*v).into(),
SwfValue::Str(v) => {
AvmString::new(self.context.gc_context, v.to_string_lossy()).into()
AvmString::new(self.context.gc_context, v.to_string_lossy(self.encoding()))
.into()
}
SwfValue::Register(v) => self.current_register(*v),
SwfValue::ConstantPool(i) => {
@ -2279,9 +2284,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
);
match catch_vars {
CatchVar::Var(name) => {
activation.set_variable(&name.to_str_lossy(), value.to_owned())?
}
CatchVar::Var(name) => activation.set_variable(
&name.to_str_lossy(activation.encoding()),
value.to_owned(),
)?,
CatchVar::Register(id) => {
activation.set_current_register(*id, value.to_owned())
}
@ -2874,6 +2880,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.scope_cell().read().is_defined(self, name)
}
/// Returns the suggested string encoding for actions.
/// For SWF version 6 and higher, this is always UTF-8.
/// For SWF version 5 and lower, this is locale-dependent,
/// and we default to WINDOWS-1252.
pub fn encoding(&self) -> &'static swf::Encoding {
swf::SwfStr::encoding_for_version(self.swf_version)
}
/// Returns the SWF version of the action or function being executed.
pub fn swf_version(&self) -> u8 {
self.swf_version

View File

@ -94,7 +94,7 @@ impl<'gc> Avm1Function<'gc> {
swf_version: u8,
actions: SwfSlice,
name: &str,
params: &[SwfStr<'_>],
params: &[&'_ SwfStr],
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<String>>,
base_clip: DisplayObject<'gc>,
@ -121,7 +121,12 @@ impl<'gc> Avm1Function<'gc> {
preload_global: false,
params: params
.iter()
.map(|&s| (None, s.to_string_lossy()))
.map(|&s| {
(
None,
s.to_string_lossy(SwfStr::encoding_for_version(swf_version)),
)
})
.collect(),
scope,
constant_pool,
@ -141,7 +146,11 @@ impl<'gc> Avm1Function<'gc> {
let name = if swf_function.name.is_empty() {
None
} else {
Some(swf_function.name.to_string_lossy())
Some(
swf_function
.name
.to_string_lossy(SwfStr::encoding_for_version(swf_version)),
)
};
let mut owned_params = Vec::new();
@ -150,7 +159,10 @@ impl<'gc> Avm1Function<'gc> {
register_index: r,
} in &swf_function.params
{
owned_params.push((*r, (*s).to_string_lossy()))
owned_params.push((
*r,
(*s).to_string_lossy(SwfStr::encoding_for_version(swf_version)),
))
}
Avm1Function {

View File

@ -841,7 +841,9 @@ pub trait TDisplayObject<'gc>:
self.set_color_transform(gc_context, &color_transform.clone().into());
}
if let Some(name) = &place_object.name {
self.set_name(gc_context, &name.to_str_lossy());
let encoding = swf::SwfStr::encoding_for_version(self.swf_version());
let name = name.to_str_lossy(encoding);
self.set_name(gc_context, &name);
}
if let Some(clip_depth) = place_object.clip_depth {
self.set_clip_depth(gc_context, clip_depth.into());

View File

@ -152,11 +152,12 @@ impl<'gc> EditText<'gc> {
let document = XMLDocument::new(context.gc_context);
let text = swf_tag.initial_text.clone().unwrap_or_default();
let default_format = TextFormat::from_swf_tag(swf_tag.clone(), swf_movie.clone(), context);
let encoding = swf_movie.encoding();
let mut text_spans = FormatSpans::new();
text_spans.set_default_format(default_format.clone());
let text = text.to_str_lossy();
let text = text.to_str_lossy(encoding);
if is_html {
let _ = document
.as_node()
@ -193,7 +194,7 @@ impl<'gc> EditText<'gc> {
base.matrix_mut(context.gc_context).ty = bounds.y_min;
let variable = if !swf_tag.variable_name.is_empty() {
Some(swf_tag.variable_name.clone())
Some(swf_tag.variable_name)
} else {
None
};
@ -212,13 +213,15 @@ impl<'gc> EditText<'gc> {
id: swf_tag.id,
bounds: swf_tag.bounds,
font_id: swf_tag.font_id,
font_class_name: swf_tag.font_class_name.map(|s| s.to_string_lossy()),
font_class_name: swf_tag
.font_class_name
.map(|s| s.to_string_lossy(encoding)),
height: swf_tag.height,
color: swf_tag.color.clone(),
max_length: swf_tag.max_length,
layout: swf_tag.layout.clone(),
variable_name: swf_tag.variable_name.to_string_lossy(),
initial_text: swf_tag.initial_text.map(|s| s.to_string_lossy()),
variable_name: swf_tag.variable_name.to_string_lossy(encoding),
initial_text: swf_tag.initial_text.map(|s| s.to_string_lossy(encoding)),
is_word_wrap: swf_tag.is_word_wrap,
is_multiline: swf_tag.is_multiline,
is_password: swf_tag.is_password,
@ -247,7 +250,7 @@ impl<'gc> EditText<'gc> {
intrinsic_bounds,
bounds,
autosize: AutoSizeMode::None,
variable: variable.map(|s| s.to_string_lossy()),
variable: variable.map(|s| s.to_string_lossy(encoding)),
bound_stage_object: None,
firing_variable_binding: false,
selection: None,
@ -620,7 +623,7 @@ impl<'gc> EditText<'gc> {
.initial_text
.clone()
.unwrap_or_default();
let _ = self.set_text(text.to_string(), &mut activation.context);
let _ = self.set_text(text, &mut activation.context);
self.0.write(activation.context.gc_context).variable = variable;
self.try_bind_text_field_variable(activation, true);

View File

@ -459,7 +459,7 @@ impl<'gc> MovieClip<'gc> {
// giving us a `SwfSlice` for later parsing, so we have to replcate the
// *entire* parsing code here. This sucks.
let flags = reader.read_u32()?;
let name = reader.read_string()?.to_string_lossy();
let name = reader.read_string()?.to_string_lossy(reader.encoding());
let is_lazy_initialize = flags & 1 != 0;
let domain = library.avm2_domain();
@ -499,7 +499,7 @@ impl<'gc> MovieClip<'gc> {
for _ in 0..num_symbols {
let id = reader.read_u16()?;
let class_name = reader.read_string()?.to_string_lossy();
let class_name = reader.read_string()?.to_string_lossy(reader.encoding());
if let Some(name) =
Avm2QName::from_symbol_class(&class_name, activation.context.gc_context)
@ -565,10 +565,11 @@ impl<'gc> MovieClip<'gc> {
.map(|fld| fld.frame_num + 1)
.unwrap_or_else(|| static_data.total_frames as u32 + 1);
let label = label.to_string_lossy(reader.encoding());
static_data.scene_labels.insert(
label.to_string(),
label.clone(),
Scene {
name: label.to_string(),
name: label,
start,
length: end as u16 - start as u16,
},
@ -576,9 +577,10 @@ impl<'gc> MovieClip<'gc> {
}
for FrameLabelData { frame_num, label } in sfl_data.frame_labels {
static_data
.frame_labels
.insert(label.to_string(), frame_num as u16 + 1);
static_data.frame_labels.insert(
label.to_string_lossy(reader.encoding()),
frame_num as u16 + 1,
);
}
Ok(())
@ -2468,7 +2470,13 @@ impl<'gc, 'a> MovieClipData<'gc> {
is_bold: false,
is_italic: false,
};
let font_object = Font::from_swf_tag(context.gc_context, context.renderer, &font).unwrap();
let font_object = Font::from_swf_tag(
context.gc_context,
context.renderer,
&font,
reader.encoding(),
)
.unwrap();
context
.library
.library_for_movie_mut(self.movie())
@ -2483,7 +2491,13 @@ impl<'gc, 'a> MovieClipData<'gc> {
reader: &mut SwfStream<'a>,
) -> DecodeResult {
let font = reader.read_define_font_2(2)?;
let font_object = Font::from_swf_tag(context.gc_context, context.renderer, &font).unwrap();
let font_object = Font::from_swf_tag(
context.gc_context,
context.renderer,
&font,
reader.encoding(),
)
.unwrap();
context
.library
.library_for_movie_mut(self.movie())
@ -2498,7 +2512,13 @@ impl<'gc, 'a> MovieClipData<'gc> {
reader: &mut SwfStream<'a>,
) -> DecodeResult {
let font = reader.read_define_font_2(3)?;
let font_object = Font::from_swf_tag(context.gc_context, context.renderer, &font).unwrap();
let font_object = Font::from_swf_tag(
context.gc_context,
context.renderer,
&font,
reader.encoding(),
)
.unwrap();
context
.library
.library_for_movie_mut(self.movie())
@ -2606,15 +2626,16 @@ impl<'gc, 'a> MovieClipData<'gc> {
) -> DecodeResult {
let exports = reader.read_export_assets()?;
for export in exports {
let name = export.name.to_str_lossy(reader.encoding());
let character = context
.library
.library_for_movie_mut(self.movie())
.register_export(export.id, &export.name.to_str_lossy());
.register_export(export.id, &name);
// TODO: do other types of Character need to know their exported name?
if let Some(Character::MovieClip(movie_clip)) = character {
*movie_clip.0.read().static_data.exported_name.borrow_mut() =
Some(export.name.to_string());
Some(name.to_string());
}
}
Ok(())
@ -2631,7 +2652,10 @@ impl<'gc, 'a> MovieClipData<'gc> {
) -> DecodeResult {
let frame_label = reader.read_frame_label(tag_len)?;
// Frame labels are case insensitive (ASCII).
let label = frame_label.label.to_str_lossy().to_ascii_lowercase();
let label = frame_label
.label
.to_str_lossy(reader.encoding())
.to_ascii_lowercase();
if let std::collections::hash_map::Entry::Vacant(v) = static_data.frame_labels.entry(label)
{
v.insert(cur_frame);

View File

@ -101,6 +101,7 @@ impl<'gc> Font<'gc> {
gc_context: MutationContext<'gc, '_>,
renderer: &mut dyn RenderBackend,
tag: &swf::Font,
encoding: &'static swf::Encoding,
) -> Result<Font<'gc>, Error> {
let mut glyphs = vec![];
let mut code_point_to_glyph = fnv::FnvHashMap::default();
@ -124,7 +125,7 @@ impl<'gc> Font<'gc> {
fnv::FnvHashMap::default()
};
let descriptor = FontDescriptor::from_swf_tag(tag);
let descriptor = FontDescriptor::from_swf_tag(tag, encoding);
let (ascent, descent, leading) = if let Some(layout) = &tag.layout {
(layout.ascent, layout.descent, layout.leading)
} else {
@ -392,12 +393,8 @@ pub struct FontDescriptor {
impl FontDescriptor {
/// Obtain a font descriptor from a SWF font tag.
pub fn from_swf_tag(val: &swf::Font) -> Self {
let mut name = val.name.to_string();
if let Some(first_null) = name.find('\0') {
name.truncate(first_null);
};
pub fn from_swf_tag(val: &swf::Font, encoding: &'static swf::Encoding) -> Self {
let name = val.name.to_string_lossy(encoding);
Self {
name,

View File

@ -193,12 +193,12 @@ impl TextFormat {
swf_movie: Arc<SwfMovie>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Self {
let encoding = swf_movie.encoding();
let movie_library = context.library.library_for_movie_mut(swf_movie);
let font = et.font_id.and_then(|fid| movie_library.get_font(fid));
let font_class = et
.font_class_name
.map(|s| s.to_string_lossy())
.map(|s| s.to_string_lossy(encoding))
.or_else(|| font.map(|font| font.descriptor().class().to_string()))
.unwrap_or_else(|| "Times New Roman".to_string());
let align = et.layout.clone().map(|l| l.align);
@ -211,7 +211,7 @@ impl TextFormat {
// Times New Roman non-bold, non-italic. This will need to be revised
// when we start supporting device fonts.
Self {
font: Some(font_class.to_string()),
font: Some(font_class),
size: et.height.map(|h| h.to_pixels()),
color: et.color,
align,

View File

@ -1281,8 +1281,12 @@ impl Player {
renderer: &mut dyn RenderBackend,
) -> Result<crate::font::Font<'gc>, Error> {
let mut reader = swf::read::Reader::new(data, 8);
let device_font =
crate::font::Font::from_swf_tag(gc_context, renderer, &reader.read_define_font_2(3)?)?;
let device_font = crate::font::Font::from_swf_tag(
gc_context,
renderer,
&reader.read_define_font_2(3)?,
reader.encoding(),
)?;
Ok(device_font)
}

View File

@ -25,6 +25,9 @@ pub struct SwfMovie {
/// Any parameters provided when loading this movie (also known as 'flashvars')
parameters: PropertyMap<String>,
/// The suggest encoding for this SWF.
encoding: &'static swf::Encoding,
}
impl SwfMovie {
@ -42,6 +45,7 @@ impl SwfMovie {
data: vec![],
url: None,
parameters: PropertyMap::new(),
encoding: swf::UTF_8,
}
}
@ -56,6 +60,7 @@ impl SwfMovie {
data,
url: source.url.clone(),
parameters: source.parameters.clone(),
encoding: source.encoding,
}
}
@ -74,11 +79,13 @@ impl SwfMovie {
/// Construct a movie based on the contents of the SWF datastream.
pub fn from_data(swf_data: &[u8], url: Option<String>) -> Result<Self, Error> {
let swf_buf = swf::read::decompress_swf(&swf_data[..])?;
let encoding = swf::SwfStr::encoding_for_version(swf_buf.header.version);
Ok(Self {
header: swf_buf.header,
data: swf_buf.data,
url,
parameters: PropertyMap::new(),
encoding,
})
}
@ -95,6 +102,14 @@ impl SwfMovie {
&self.data
}
/// Returns the suggested string encoding for the given SWF version.
/// For SWF version 6 and higher, this is always UTF-8.
/// For SWF version 5 and lower, this is locale-dependent,
/// and we default to WINDOWS-1252.
pub fn encoding(&self) -> &'static swf::Encoding {
self.encoding
}
pub fn width(&self) -> u32 {
(self.header.stage_size.x_max - self.header.stage_size.x_min).to_pixels() as u32
}

View File

@ -3,7 +3,7 @@
use crate::avm1::{opcode::OpCode, types::*};
use crate::error::{Error, Result};
use crate::read::SwfReadExt;
use crate::string::SwfStr;
use crate::string::{Encoding, SwfStr, UTF_8, WINDOWS_1252};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{self, Read};
@ -11,27 +11,35 @@ use std::io::{self, Read};
pub struct Reader<'a> {
input: &'a [u8],
version: u8,
encoding: &'static encoding_rs::Encoding,
encoding: &'static Encoding,
}
impl<'a> Reader<'a> {
#[inline]
pub fn new(input: &'a [u8], version: u8) -> Self {
Self {
input,
version,
encoding: if version > 5 {
encoding_rs::UTF_8
UTF_8
} else {
// TODO: Allow configurable encoding
encoding_rs::WINDOWS_1252
WINDOWS_1252
},
}
}
#[inline]
pub fn encoding(&self) -> &'static Encoding {
SwfStr::encoding_for_version(self.version)
}
#[inline]
pub fn get_ref(&self) -> &'a [u8] {
self.input
}
#[inline]
pub fn get_mut(&mut self) -> &mut &'a [u8] {
&mut self.input
}
@ -49,7 +57,7 @@ impl<'a> Reader<'a> {
}
}
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
pub fn read_string(&mut self) -> io::Result<&'a SwfStr> {
let mut pos = 0;
loop {
let byte = *self.input.get(pos).ok_or_else(|| {
@ -63,7 +71,7 @@ impl<'a> Reader<'a> {
let s = unsafe {
let slice = self.input.get_unchecked(..pos);
SwfStr::from_bytes_unchecked(slice, self.encoding)
SwfStr::from_bytes(slice)
};
self.input = &self.input[pos + 1..];
Ok(s)
@ -483,7 +491,6 @@ pub mod tests {
#[test]
fn read_define_function() {
use encoding_rs::WINDOWS_1252;
// Ensure we read a function properly along with the function data.
let action_bytes = vec![
0x9b, 0x08, 0x00, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x96, 0x06, 0x00,

View File

@ -18,11 +18,11 @@ pub enum Action<'a> {
CastOp,
CharToAscii,
CloneSprite,
ConstantPool(Vec<SwfStr<'a>>),
ConstantPool(Vec<&'a SwfStr>),
Decrement,
DefineFunction {
name: SwfStr<'a>,
params: Vec<SwfStr<'a>>,
name: &'a SwfStr,
params: Vec<&'a SwfStr>,
actions: &'a [u8],
},
DefineFunction2(Function<'a>),
@ -41,8 +41,8 @@ pub enum Action<'a> {
GetProperty,
GetTime,
GetUrl {
url: SwfStr<'a>,
target: SwfStr<'a>,
url: &'a SwfStr,
target: &'a SwfStr,
},
GetUrl2 {
send_vars_method: SendVarsMethod,
@ -55,7 +55,7 @@ pub enum Action<'a> {
set_playing: bool,
scene_offset: u16,
},
GotoLabel(SwfStr<'a>),
GotoLabel(&'a SwfStr),
Greater,
If {
offset: i16,
@ -91,7 +91,7 @@ pub enum Action<'a> {
Return,
SetMember,
SetProperty,
SetTarget(SwfStr<'a>),
SetTarget(&'a SwfStr),
SetTarget2,
SetVariable,
StackSwap,
@ -140,7 +140,7 @@ pub enum Value<'a> {
Int(i32),
Float(f32),
Double(f64),
Str(SwfStr<'a>),
Str(&'a SwfStr),
Register(u8),
ConstantPool(u16),
}
@ -154,7 +154,7 @@ pub enum SendVarsMethod {
#[derive(Clone, Debug, PartialEq)]
pub struct Function<'a> {
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub register_count: u8,
pub params: Vec<FunctionParam<'a>>,
pub preload_parent: bool,
@ -171,7 +171,7 @@ pub struct Function<'a> {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionParam<'a> {
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub register_index: Option<u8>,
}
@ -184,6 +184,6 @@ pub struct TryBlock<'a> {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CatchVar<'a> {
Var(SwfStr<'a>),
Var(&'a SwfStr),
Register(u8),
}

View File

@ -60,7 +60,7 @@ impl<W: Write> SwfWriteExt for Writer<W> {
}
#[inline]
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
fn write_string(&mut self, s: &'_ SwfStr) -> io::Result<()> {
self.output.write_all(s.as_bytes())?;
self.write_u8(0)
}

View File

@ -56,7 +56,7 @@ impl<W: Write> SwfWriteExt for Writer<W> {
}
#[inline]
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
fn write_string(&mut self, s: &'_ SwfStr) -> io::Result<()> {
self.output.write_all(s.as_bytes())?;
self.write_u8(0)
}

View File

@ -8,7 +8,7 @@
use crate::{
error::{Error, Result},
string::SwfStr,
string::{Encoding, SwfStr},
types::*,
};
use bitstream_io::BitRead;
@ -248,28 +248,30 @@ impl<'a, 'b> BitReader<'a, 'b> {
pub struct Reader<'a> {
input: &'a [u8],
version: u8,
encoding: &'static encoding_rs::Encoding,
}
impl<'a> Reader<'a> {
#[inline]
pub fn new(input: &'a [u8], version: u8) -> Reader<'a> {
Reader {
input,
version,
encoding: if version > 5 {
encoding_rs::UTF_8
} else {
// TODO: Allow configurable encoding
encoding_rs::WINDOWS_1252
},
}
Reader { input, version }
}
/// Returns the suggested string encoding for this SWF.
/// For SWF version 6 and higher, this is always UTF-8.
/// For SWF version 5 and lower, this is locale-dependent,
/// and we default to WINDOWS-1252.
#[inline]
pub fn encoding(&self) -> &'static Encoding {
SwfStr::encoding_for_version(self.version)
}
#[inline]
pub fn version(&self) -> u8 {
self.version
}
/// Returns a reference to the underlying `Reader`.
#[inline]
pub fn get_ref(&self) -> &'a [u8] {
self.input
}
@ -277,6 +279,7 @@ impl<'a> Reader<'a> {
/// Returns a mutable reference to the underlying `Reader`.
///
/// Reading from this reference is not recommended.
#[inline]
pub fn get_mut(&mut self) -> &mut &'a [u8] {
&mut self.input
}
@ -317,7 +320,7 @@ impl<'a> Reader<'a> {
slice
}
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
pub fn read_string(&mut self) -> io::Result<&'a SwfStr> {
let mut pos = 0;
loop {
let byte = *self.input.get(pos).ok_or_else(|| {
@ -329,16 +332,14 @@ impl<'a> Reader<'a> {
pos += 1;
}
let s = unsafe {
let slice = self.input.get_unchecked(..pos);
SwfStr::from_bytes_unchecked(slice, self.encoding)
};
let slice = unsafe { self.input.get_unchecked(..pos) };
let s = SwfStr::from_bytes(slice);
self.input = &self.input[pos + 1..];
Ok(s)
}
fn read_string_with_len(&mut self, len: usize) -> io::Result<SwfStr<'a>> {
Ok(SwfStr::from_bytes(&self.read_slice(len)?, self.encoding))
fn read_string_with_len(&mut self, len: usize) -> io::Result<&'a SwfStr> {
Ok(SwfStr::from_bytes_null_terminated(&self.read_slice(len)?))
}
/// Reads the next SWF tag from the stream.

View File

@ -1,58 +1,49 @@
//! String typed used by SWF files.
//!
//! Allows for locale-dependent encoding for SWF version <6.
//! String type used by SWF files.
use encoding_rs::{Encoding, UTF_8};
pub use encoding_rs::{Encoding, SHIFT_JIS, UTF_8, WINDOWS_1252};
use std::{borrow::Cow, fmt};
/// `SwfStr` is returned by SWF and AVM1 parsing functions.
/// `SwfStr` is analogous to `&str`, with some additional allowances:
/// * An encoding is specified along with the string data.
/// * The string contains no null bytes.
/// `SwfStr` is the string type returned by SWF parsing functions.
/// `SwfStr` is a bstr-like type analogous to `str`:
/// * The encoding depends on the SWF version (UTF-8 for SWF6 and higher).
/// Use `Reader::encoding` or `SwfStr::encoding_for_version` to get the
/// proper encoding.
/// * Invalid data for any particular encoding is allowed;
/// any conversions to std::String will be lossy for invalid data.
/// This handles the locale dependent encoding of early SWF files and
/// mimics C-style null-terminated string behavior.
/// To convert this to a standard Rust string, use `SwfStr::to_str_lossy`.
/// `SwfStr`s are equal if both their encoding and data matches.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct SwfStr<'a> {
#[derive(Eq, PartialEq)]
#[repr(transparent)]
pub struct SwfStr {
/// The string bytes.
string: &'a [u8],
/// The encoding of the string data.
encoding: &'static Encoding,
string: [u8],
}
impl<'a> SwfStr<'a> {
impl SwfStr {
/// Create a new `SwfStr` from a byte slice with a given encoding.
/// The string will be truncated if a null byte is encountered.
/// The data is not required to be valid for the given encoding.
#[inline]
pub fn from_bytes(string: &'a [u8], encoding: &'static Encoding) -> Self {
let i = string.iter().position(|&c| c == 0).unwrap_or(string.len());
Self {
string: &string[..i],
encoding,
}
pub fn from_bytes(string: &[u8]) -> &Self {
unsafe { &*(string as *const [u8] as *const Self) }
}
/// Create a new `SwfStr` from a byte slice with a given encoding.
/// The data is not required to be valid for the given encoding.
///
/// # Safety
///
/// The string should contain no null bytes.
#[inline]
pub unsafe fn from_bytes_unchecked(string: &'a [u8], encoding: &'static Encoding) -> Self {
Self { string, encoding }
pub fn from_bytes_null_terminated(string: &[u8]) -> &Self {
let i = string.iter().position(|&c| c == 0).unwrap_or(string.len());
Self::from_bytes(&string[..i])
}
/// Create a new UTF-8 `SwfStr` from a Rust `str`.
#[inline]
pub fn from_utf8_str(string: &str) -> &Self {
Self::from_bytes(string.as_bytes())
}
/// Create a new UTF-8 `SwfStr` from a Rust `str`.
/// The string will be truncated if a null byte is encountered.
#[inline]
pub fn from_utf8_str(string: &'a str) -> Self {
Self::from_bytes(string.as_bytes(), UTF_8)
pub fn from_utf8_str_null_terminated(string: &str) -> &Self {
Self::from_bytes_null_terminated(string.as_bytes())
}
/// Create a new `SwfStr` with the given encoding from a Rust `str`.
@ -60,24 +51,34 @@ impl<'a> SwfStr<'a> {
/// The string will be truncated if a null byte is encountered.
/// `None` is returned if the encoding is not lossless.
/// Intended for tests.
pub fn from_str_with_encoding(string: &'a str, encoding: &'static Encoding) -> Option<Self> {
pub fn from_str_with_encoding<'a>(
string: &'a str,
encoding: &'static Encoding,
) -> Option<&'a Self> {
if let (Cow::Borrowed(s), _, false) = encoding.encode(&string) {
Some(Self::from_bytes(s, encoding))
Some(Self::from_bytes(s))
} else {
None
}
}
/// Returns the byte slice of this string.
/// Returns the suggested string encoding for the given SWF version.
/// For SWF version 6 and higher, this is always UTF-8.
/// For SWF version 5 and lower, this is locale-dependent,
/// and we default to WINDOWS-1252.
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.string
pub fn encoding_for_version(swf_version: u8) -> &'static Encoding {
if swf_version >= 6 {
UTF_8
} else {
WINDOWS_1252
}
}
/// Returns the encoding used by this string.
/// Returns the byte slice of this string.
#[inline]
pub fn encoding(&self) -> &'static Encoding {
self.encoding
pub fn as_bytes(&self) -> &[u8] {
&self.string
}
/// Returns `true` if the string has a length of zero, and `false` otherwise.
@ -95,47 +96,40 @@ impl<'a> SwfStr<'a> {
/// Decodes the string into a Rust UTF-8 `str`.
/// The UTF-8 replacement character will be uses for any invalid data.
#[inline]
pub fn to_str_lossy(&self) -> Cow<'a, str> {
self.encoding.decode_without_bom_handling(self.string).0
pub fn to_str_lossy(&self, encoding: &'static Encoding) -> Cow<'_, str> {
encoding.decode_without_bom_handling(&self.string).0
}
/// Decodes the string into a Rust UTF-8 `String`.
/// The UTF-8 replacement character will be uses for any invalid data.
#[inline]
pub fn to_string_lossy(&self) -> String {
self.to_str_lossy().into_owned()
pub fn to_string_lossy(&self, encoding: &'static Encoding) -> String {
self.to_str_lossy(encoding).into_owned()
}
}
impl<'a> Default for SwfStr<'a> {
fn default() -> Self {
Self {
string: &[],
encoding: UTF_8,
}
impl<'a> Default for &'a SwfStr {
fn default() -> &'a SwfStr {
SwfStr::from_bytes(&[])
}
}
impl<'a> From<&'a str> for SwfStr<'a> {
fn from(s: &'a str) -> Self {
impl<'a> From<&'a str> for &'a SwfStr {
fn from(s: &'a str) -> &'a SwfStr {
SwfStr::from_utf8_str(s)
}
}
impl<'a, T: AsRef<str>> PartialEq<T> for SwfStr<'a> {
impl<'a, T: ?Sized + AsRef<str>> PartialEq<T> for SwfStr {
fn eq(&self, other: &T) -> bool {
self.string == other.as_ref().as_bytes()
&self.string == other.as_ref().as_bytes()
}
}
impl<'a> fmt::Display for SwfStr<'a> {
impl fmt::Debug for SwfStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_str_lossy())
}
}
impl<'a> fmt::Debug for SwfStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_str_lossy())
// Note that this assumes UTF-8 encoding;
// other encodings like Shift-JIS will output gibberish.
f.write_str(&self.to_str_lossy(UTF_8))
}
}

View File

@ -5,11 +5,10 @@ use crate::avm2::read::tests::read_abc_from_file;
use crate::avm2::types::*;
use crate::read::tests::{read_tag_bytes_from_file, read_tag_bytes_from_file_with_index};
use crate::read::{decompress_swf, parse_swf};
use crate::string::SwfStr;
use crate::string::{SwfStr, WINDOWS_1252};
use crate::tag_code::TagCode;
use crate::types::*;
use crate::write::write_swf;
use encoding_rs::WINDOWS_1252;
use std::fs::File;
use std::vec::Vec;

View File

@ -247,7 +247,7 @@ pub struct FileAttributes {
#[derive(Debug, PartialEq)]
pub struct FrameLabel<'a> {
pub label: SwfStr<'a>,
pub label: &'a SwfStr,
pub is_anchor: bool,
}
@ -260,7 +260,7 @@ pub struct DefineSceneAndFrameLabelData<'a> {
#[derive(Debug, PartialEq)]
pub struct FrameLabelData<'a> {
pub frame_num: u32,
pub label: SwfStr<'a>,
pub label: &'a SwfStr,
}
pub type Depth = u16;
@ -274,9 +274,9 @@ pub struct PlaceObject<'a> {
pub matrix: Option<Matrix>,
pub color_transform: Option<ColorTransform>,
pub ratio: Option<u16>,
pub name: Option<SwfStr<'a>>,
pub name: Option<&'a SwfStr>,
pub clip_depth: Option<Depth>,
pub class_name: Option<SwfStr<'a>>,
pub class_name: Option<&'a SwfStr>,
pub filters: Option<Vec<Filter>>,
pub background_color: Option<Color>,
pub blend_mode: Option<BlendMode>,
@ -472,7 +472,7 @@ pub enum Tag<'a> {
},
ShowFrame,
Protect(Option<SwfStr<'a>>),
Protect(Option<&'a SwfStr>),
CsmTextSettings(CsmTextSettings),
DebugId(DebugId),
DefineBinaryData {
@ -505,8 +505,8 @@ pub enum Tag<'a> {
DefineFontInfo(Box<FontInfo<'a>>),
DefineFontName {
id: CharacterId,
name: SwfStr<'a>,
copyright_info: SwfStr<'a>,
name: &'a SwfStr,
copyright_info: &'a SwfStr,
},
DefineMorphShape(Box<DefineMorphShape>),
DefineScalingGrid {
@ -524,14 +524,14 @@ pub enum Tag<'a> {
id: CharacterId,
action_data: &'a [u8],
},
EnableDebugger(SwfStr<'a>),
EnableDebugger(&'a SwfStr),
EnableTelemetry {
password_hash: &'a [u8],
},
End,
Metadata(SwfStr<'a>),
Metadata(&'a SwfStr),
ImportAssets {
url: SwfStr<'a>,
url: &'a SwfStr,
imports: Vec<ExportedAsset<'a>>,
},
JpegTables(JpegTables<'a>),
@ -545,7 +545,7 @@ pub enum Tag<'a> {
SoundStreamHead2(Box<SoundStreamHead>),
StartSound(StartSound),
StartSound2 {
class_name: SwfStr<'a>,
class_name: &'a SwfStr,
sound_info: Box<SoundInfo>,
},
SymbolClass(Vec<SymbolClassLink<'a>>),
@ -570,7 +570,7 @@ pub type ExportAssets<'a> = Vec<ExportedAsset<'a>>;
#[derive(Debug, PartialEq, Clone)]
pub struct ExportedAsset<'a> {
pub id: CharacterId,
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
}
#[derive(Debug, PartialEq, Clone)]
@ -584,7 +584,7 @@ pub type SetBackgroundColor = Color;
#[derive(Debug, PartialEq, Clone)]
pub struct SymbolClassLink<'a> {
pub id: CharacterId,
pub class_name: SwfStr<'a>,
pub class_name: &'a SwfStr,
}
#[derive(Debug, PartialEq, Clone)]
@ -897,7 +897,7 @@ pub struct FontV1 {
pub struct Font<'a> {
pub version: u8,
pub id: CharacterId,
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub language: Language,
pub layout: Option<FontLayout>,
pub glyphs: Vec<Glyph>,
@ -913,7 +913,7 @@ pub struct Font4<'a> {
pub id: CharacterId,
pub is_italic: bool,
pub is_bold: bool,
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub data: Option<&'a [u8]>,
}
@ -944,7 +944,7 @@ pub struct KerningRecord {
pub struct FontInfo<'a> {
pub id: CharacterId,
pub version: u8,
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub is_small_text: bool,
pub is_shift_jis: bool,
pub is_ansi: bool,
@ -983,13 +983,13 @@ pub struct EditText<'a> {
pub id: CharacterId,
pub bounds: Rectangle,
pub font_id: Option<CharacterId>, // TODO(Herschel): Combine with height
pub font_class_name: Option<SwfStr<'a>>,
pub font_class_name: Option<&'a SwfStr>,
pub height: Option<Twips>,
pub color: Option<Color>,
pub max_length: Option<u16>,
pub layout: Option<TextLayout>,
pub variable_name: SwfStr<'a>,
pub initial_text: Option<SwfStr<'a>>,
pub variable_name: &'a SwfStr,
pub initial_text: Option<&'a SwfStr>,
pub is_word_wrap: bool,
pub is_multiline: bool,
pub is_password: bool,
@ -1116,7 +1116,7 @@ pub struct DefineBitsJpeg3<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct DoAbc<'a> {
pub name: SwfStr<'a>,
pub name: &'a SwfStr,
pub is_lazy_initialize: bool,
pub data: &'a [u8],
}

View File

@ -144,7 +144,7 @@ pub trait SwfWriteExt {
fn write_i32(&mut self, n: i32) -> io::Result<()>;
fn write_f32(&mut self, n: f32) -> io::Result<()>;
fn write_f64(&mut self, n: f64) -> io::Result<()>;
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()>;
fn write_string(&mut self, s: &'_ SwfStr) -> io::Result<()>;
}
pub struct BitWriter<W: Write> {
@ -257,7 +257,7 @@ impl<W: Write> SwfWriteExt for Writer<W> {
}
#[inline]
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
fn write_string(&mut self, s: &'_ SwfStr) -> io::Result<()> {
self.output.write_all(s.as_bytes())?;
self.write_u8(0)
}