summaryrefslogtreecommitdiffstats
path: root/third_party/rust/embed-manifest/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/embed-manifest/src')
-rw-r--r--third_party/rust/embed-manifest/src/embed/coff.rs192
-rw-r--r--third_party/rust/embed-manifest/src/embed/error.rs57
-rw-r--r--third_party/rust/embed-manifest/src/embed/mod.rs139
-rw-r--r--third_party/rust/embed-manifest/src/embed/test.rs173
-rw-r--r--third_party/rust/embed-manifest/src/lib.rs134
-rw-r--r--third_party/rust/embed-manifest/src/manifest/mod.rs882
-rw-r--r--third_party/rust/embed-manifest/src/manifest/test.rs117
-rw-r--r--third_party/rust/embed-manifest/src/manifest/xml.rs140
8 files changed, 1834 insertions, 0 deletions
diff --git a/third_party/rust/embed-manifest/src/embed/coff.rs b/third_party/rust/embed-manifest/src/embed/coff.rs
new file mode 100644
index 0000000000..bf751c3bf5
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/coff.rs
@@ -0,0 +1,192 @@
+//! Just as much COFF object file support as is needed to write a resource data segment
+//! for GNU Windows targets. Inspired by the `write::Object` code from the `object` crate.
+//!
+//! Integers are converted from u64 to u32 and added without checking because the manifest
+//! data cannot get anywhere close to overflowing unless the supplied application name or
+//! number of dependencies was extremely long. If this code was used more generally or if
+//! the input was less trustworthy then more checked conversions and checked arithmetic
+//! would be needed.
+
+use std::io::{self, Seek, SeekFrom, Write};
+use std::time::SystemTime;
+
+#[derive(Debug, Clone, Copy)]
+pub enum MachineType {
+ I386,
+ X86_64,
+ Aarch64,
+}
+
+impl MachineType {
+ pub fn machine(&self) -> u16 {
+ match self {
+ Self::I386 => 0x014c,
+ Self::X86_64 => 0x8664,
+ Self::Aarch64 => 0xaa64,
+ }
+ }
+
+ pub fn relocation_type(&self) -> u16 {
+ match self {
+ Self::I386 => 7,
+ Self::X86_64 => 3,
+ Self::Aarch64 => 2,
+ }
+ }
+}
+
+pub struct CoffWriter<W> {
+ /// wrapped writer or buffer
+ writer: W,
+ /// machine type for file header
+ machine_type: MachineType,
+ // size in bytes of resource section data
+ size_of_raw_data: u32,
+ // number of relocations at the end of the section
+ number_of_relocations: u16,
+}
+
+impl<W: Write + Seek> CoffWriter<W> {
+ /// Create a new instance wrapping a writer.
+ pub fn new(mut writer: W, machine_type: MachineType) -> io::Result<Self> {
+ // Add space for file header and section table.
+ writer.write_all(&[0; 60])?;
+ Ok(Self {
+ writer,
+ machine_type,
+ size_of_raw_data: 0,
+ number_of_relocations: 0,
+ })
+ }
+
+ /// Add data to a section and return its offset within the section.
+ pub fn add_data(&mut self, data: &[u8]) -> io::Result<u32> {
+ let start = self.size_of_raw_data;
+ self.writer.write_all(data)?;
+ self.size_of_raw_data = start + data.len() as u32;
+ Ok(start)
+ }
+
+ // Pad the resource data to a multiple of `n` bytes.
+ pub fn align_to(&mut self, n: u32) -> io::Result<()> {
+ let offset = self.size_of_raw_data % n;
+ if offset != 0 {
+ let padding = n - offset;
+ for _ in 0..padding {
+ self.writer.write_all(&[0])?;
+ }
+ self.size_of_raw_data += padding;
+ }
+ Ok(())
+ }
+
+ /// Write a relocation for a symbol at the end of the section.
+ pub fn add_relocation(&mut self, address: u32) -> io::Result<()> {
+ self.number_of_relocations += 1;
+ self.writer.write_all(&address.to_le_bytes())?;
+ self.writer.write_all(&[0, 0, 0, 0])?;
+ self.writer.write_all(&self.machine_type.relocation_type().to_le_bytes())
+ }
+
+ /// Write the object and section headers and write the symbol table.
+ pub fn finish(mut self) -> io::Result<W> {
+ // Get the timestamp for the header. `as` is correct here, as the low 32 bits
+ // should be used.
+ let timestamp = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .map_or(0, |d| d.as_secs() as u32);
+
+ // Copy file location of the symbol table.
+ let pointer_to_symbol_table = self.writer.stream_position()? as u32;
+
+ // Write the symbols and auxiliary data for the section.
+ self.writer.write_all(b".rsrc\0\0\0")?; // name
+ self.writer.write_all(&[0, 0, 0, 0])?; // address
+ self.writer.write_all(&[1, 0])?; // section number (1-based)
+ self.writer.write_all(&[0, 0, 3, 1])?; // type = 0, class = static, aux symbols = 1
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 12])?;
+
+ // Write the empty string table.
+ self.writer.write_all(&[0; 4])?;
+
+ // Write the object header.
+ let end_of_file = self.writer.seek(SeekFrom::Start(0))?;
+ self.writer.write_all(&self.machine_type.machine().to_le_bytes())?;
+ self.writer.write_all(&[1, 0])?; // number of sections
+ self.writer.write_all(&timestamp.to_le_bytes())?;
+ self.writer.write_all(&pointer_to_symbol_table.to_le_bytes())?;
+ self.writer.write_all(&[2, 0, 0, 0])?; // number of symbol table entries
+ self.writer.write_all(&[0; 4])?; // optional header size = 0, characteristics = 0
+
+ // Write the section header.
+ self.writer.write_all(b".rsrc\0\0\0")?;
+ self.writer.write_all(&[0; 8])?; // virtual size = 0 and virtual address = 0
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&[60, 0, 0, 0])?; // pointer to raw data
+ self.writer.write_all(&(self.size_of_raw_data + 60).to_le_bytes())?; // pointer to relocations
+ self.writer.write_all(&[0; 4])?; // pointer to line numbers
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 2])?; // number of line numbers
+ self.writer.write_all(&[0x40, 0, 0x30, 0xc0])?; // characteristics
+
+ // Return the inner writer and dispose of this object.
+ self.writer.seek(SeekFrom::Start(end_of_file))?;
+ Ok(self.writer)
+ }
+}
+
+/// Returns the bytes for a resource directory table.
+///
+/// Most of the fields are set to zero, including the timestamp, to aid
+/// with making builds reproducible.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Characteristics,
+/// DWORD TimeDateStamp,
+/// WORD MajorVersion,
+/// WORD MinorVersion,
+/// WORD NumberOfNamedEntries,
+/// WORD NumberOfIdEntries
+/// } IMAGE_RESOURCE_DIRECTORY;
+/// ```
+pub fn resource_directory_table(number_of_id_entries: u16) -> [u8; 16] {
+ let mut table = [0; 16];
+ table[14..16].copy_from_slice(&number_of_id_entries.to_le_bytes());
+ table
+}
+
+/// Returns the bytes for a resource directory entry for an ID.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Name,
+/// DWORD OffsetToData
+/// } IMAGE_RESOURCE_DIRECTORY_ENTRY;
+/// ```
+pub fn resource_directory_id_entry(id: u32, offset: u32, subdirectory: bool) -> [u8; 8] {
+ let mut entry = [0; 8];
+ entry[0..4].copy_from_slice(&id.to_le_bytes());
+ let flag: u32 = if subdirectory { 0x80000000 } else { 0 };
+ entry[4..8].copy_from_slice(&((offset & 0x7fffffff) | flag).to_le_bytes());
+ entry
+}
+
+/// Returns the bytes for a resource data entry.
+///
+/// ```c
+/// typedef struct {
+/// DWORD OffsetToData,
+/// DWORD Size,
+/// DWORD CodePage,
+/// DWORD Reserved
+/// } IMAGE_RESOURCE_DATA_ENTRY;
+/// ```
+pub fn resource_data_entry(rva: u32, size: u32) -> [u8; 16] {
+ let mut entry = [0; 16];
+ entry[0..4].copy_from_slice(&rva.to_le_bytes());
+ entry[4..8].copy_from_slice(&size.to_le_bytes());
+ entry
+}
diff --git a/third_party/rust/embed-manifest/src/embed/error.rs b/third_party/rust/embed-manifest/src/embed/error.rs
new file mode 100644
index 0000000000..365d0525ed
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/error.rs
@@ -0,0 +1,57 @@
+//! Error handling for application manifest embedding.
+
+use std::fmt::{self, Display, Formatter};
+use std::io::{self, ErrorKind};
+
+/// The error type which is returned when application manifest embedding fails.
+#[derive(Debug)]
+pub struct Error {
+ repr: Repr,
+}
+
+#[derive(Debug)]
+enum Repr {
+ IoError(io::Error),
+ UnknownTarget,
+}
+
+impl Error {
+ pub(crate) fn unknown_target() -> Error {
+ Error {
+ repr: Repr::UnknownTarget,
+ }
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self.repr {
+ Repr::IoError(ref e) => write!(f, "I/O error: {}", e),
+ Repr::UnknownTarget => f.write_str("unknown target"),
+ }
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self.repr {
+ Repr::IoError(ref e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Self {
+ Error { repr: Repr::IoError(e) }
+ }
+}
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ match e.repr {
+ Repr::IoError(ioe) => ioe,
+ _ => io::Error::new(ErrorKind::Other, e),
+ }
+ }
+}
diff --git a/third_party/rust/embed-manifest/src/embed/mod.rs b/third_party/rust/embed-manifest/src/embed/mod.rs
new file mode 100644
index 0000000000..b054878ac6
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/mod.rs
@@ -0,0 +1,139 @@
+use std::env;
+use std::fs::{self, File};
+use std::io::{self, stdout, BufWriter, Cursor, Write};
+use std::path::{Path, PathBuf};
+
+use crate::manifest::ManifestBuilder;
+
+use self::coff::{resource_data_entry, resource_directory_id_entry, resource_directory_table, CoffWriter, MachineType};
+use self::error::Error;
+
+mod coff;
+pub mod error;
+
+#[cfg(test)]
+mod test;
+
+/// Embeds the manifest described by `manifest` by converting it to XML,
+/// then saving it to a file and passing the correct options to the linker
+/// on MSVC targets, or by building a static library and instructing Cargo
+/// to link the executable against it on GNU targets.
+pub fn embed_manifest(manifest: ManifestBuilder) -> Result<(), Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ let manifest_file = out_dir.join("manifest.xml");
+ write!(BufWriter::new(File::create(&manifest_file)?), "{}", manifest)?;
+ link_manifest_msvc(&manifest_file, &mut stdout().lock())
+ } else {
+ let manifest_data = manifest.to_string();
+ link_manifest_gnu(manifest_data.as_bytes(), &out_dir, target.arch, &mut stdout().lock())
+ }
+}
+
+/// Directly embeds the manifest in the provided `file` by passing the correct
+/// options to the linker on MSVC targets, or by building a static library
+/// and instructing Cargo to link the executable against it on GNU targets.
+pub fn embed_manifest_file<P: AsRef<Path>>(file: P) -> Result<(), io::Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ Ok(link_manifest_msvc(file.as_ref(), &mut stdout().lock())?)
+ } else {
+ let manifest = fs::read(file.as_ref())?;
+ Ok(link_manifest_gnu(&manifest, &out_dir, target.arch, &mut stdout().lock())?)
+ }
+}
+
+fn get_out_dir() -> Result<PathBuf, io::Error> {
+ match env::var_os("OUT_DIR") {
+ Some(dir) => Ok(PathBuf::from(dir)),
+ None => env::current_dir(),
+ }
+}
+
+enum TargetOs {
+ WindowsGnu,
+ WindowsMsvc,
+}
+
+struct Target {
+ arch: MachineType,
+ os: TargetOs,
+}
+
+fn get_target() -> Result<Target, Error> {
+ match env::var("TARGET") {
+ Ok(target) => parse_target(&target),
+ _ => Err(Error::unknown_target()),
+ }
+}
+
+fn parse_target(target: &str) -> Result<Target, Error> {
+ let mut iter = target.splitn(3, '-');
+ let arch = match iter.next() {
+ Some("i686") => MachineType::I386,
+ Some("aarch64") => MachineType::Aarch64,
+ Some("x86_64") => MachineType::X86_64,
+ _ => return Err(Error::unknown_target()),
+ };
+ if iter.next() != Some("pc") {
+ return Err(Error::unknown_target());
+ }
+ let os = match iter.next() {
+ Some("windows-gnu") => TargetOs::WindowsGnu,
+ Some("windows-gnullvm") => TargetOs::WindowsGnu,
+ Some("windows-msvc") => TargetOs::WindowsMsvc,
+ _ => return Err(Error::unknown_target()),
+ };
+ Ok(Target { arch, os })
+}
+
+fn link_manifest_msvc<W: Write>(manifest_path: &Path, out: &mut W) -> Result<(), Error> {
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFEST:EMBED")?;
+ writeln!(
+ out,
+ "cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}",
+ manifest_path.canonicalize()?.display()
+ )?;
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO")?;
+ Ok(())
+}
+
+fn link_manifest_gnu<W: Write>(manifest: &[u8], out_dir: &Path, arch: MachineType, out: &mut W) -> Result<(), Error> {
+ // Generate a COFF object file containing the manifest in a .rsrc section.
+ let object_data = create_object_file(manifest, arch)?;
+ let path = out_dir.join("embed-manifest.o");
+ fs::create_dir_all(out_dir)?;
+ fs::write(&path, object_data)?;
+
+ // Link the manifest with the executable.
+ writeln!(out, "cargo:rustc-link-arg-bins={}", path.display())?;
+ Ok(())
+}
+
+fn create_object_file(manifest: &[u8], arch: MachineType) -> Result<Vec<u8>, io::Error> {
+ // Start object file with .rsrc section.
+ let mut obj = CoffWriter::new(Cursor::new(Vec::with_capacity(4096)), arch)?;
+
+ // Create resource directories for type ID 24, name ID 1, language ID 1033.
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(24, 24, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1, 48, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1033, 72, false))?;
+
+ // Add resource data entry with relocated address.
+ let address = obj.add_data(&resource_data_entry(88, manifest.len() as u32))?;
+
+ // Add the manifest data.
+ obj.add_data(manifest)?;
+ obj.align_to(8)?;
+
+ // Write manifest data relocation at the end of the section.
+ obj.add_relocation(address)?;
+
+ // Finish writing file and return the populated object data.
+ Ok(obj.finish()?.into_inner())
+}
diff --git a/third_party/rust/embed-manifest/src/embed/test.rs b/third_party/rust/embed-manifest/src/embed/test.rs
new file mode 100644
index 0000000000..c464c6044c
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/test.rs
@@ -0,0 +1,173 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use object::coff::CoffFile;
+use object::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry, IMAGE_RESOURCE_DATA_IS_DIRECTORY};
+use object::{
+ pod, Architecture, LittleEndian, Object, ObjectSection, ObjectSymbol, RelocationEncoding, RelocationKind, SectionKind,
+};
+use tempfile::{tempdir, TempDir};
+
+use crate::embed::coff::MachineType;
+use crate::embed::{create_object_file, link_manifest_gnu, link_manifest_msvc, TargetOs};
+use crate::new_manifest;
+
+#[test]
+fn create_obj() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ let data = fs::read(&res.object_file()).unwrap();
+ let obj = CoffFile::parse(&data[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ let expected_manifest = fs::read(&sample_manifest_path()).unwrap();
+ check_object_file(obj, &expected_manifest);
+}
+
+#[test]
+fn link_lib_gnu() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ assert!(res.object_file().exists());
+ let object_option = format!("cargo:rustc-link-arg-bins={}", res.object_file().display());
+ assert_eq!(res.lines(), &[object_option.as_str()]);
+}
+
+#[test]
+fn link_file_msvc() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsMsvc);
+ assert!(!res.object_file().exists());
+ let mut input_option = String::from("cargo:rustc-link-arg-bins=/MANIFESTINPUT:");
+ input_option.push_str(res.manifest_path.canonicalize().unwrap().to_str().unwrap());
+ assert_eq!(
+ res.lines(),
+ &[
+ "cargo:rustc-link-arg-bins=/MANIFEST:EMBED",
+ input_option.as_str(),
+ "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO"
+ ]
+ );
+}
+
+struct EmbedResult {
+ manifest_path: PathBuf,
+ out_dir: TempDir,
+ output: String,
+}
+
+impl EmbedResult {
+ fn object_file(&self) -> PathBuf {
+ self.out_dir.path().join("embed-manifest.o")
+ }
+
+ fn lines(&self) -> Vec<&str> {
+ self.output.lines().collect()
+ }
+}
+
+fn sample_manifest_path() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/sample.exe.manifest")
+}
+
+fn do_embed_file(arch: MachineType, os: TargetOs) -> EmbedResult {
+ let manifest_path = sample_manifest_path();
+ let out_dir = tempdir().unwrap();
+ let mut buf: Vec<u8> = Vec::new();
+ if matches!(os, TargetOs::WindowsMsvc) {
+ link_manifest_msvc(&manifest_path, &mut buf).unwrap();
+ } else {
+ link_manifest_gnu(&fs::read(&manifest_path).unwrap(), out_dir.path(), arch, &mut buf).unwrap();
+ }
+ EmbedResult {
+ manifest_path,
+ out_dir,
+ output: String::from_utf8(buf).unwrap(),
+ }
+}
+
+#[test]
+fn object_file_x86() {
+ let manifest = new_manifest("Test.X86").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::I386).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::I386);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_x86_64() {
+ let manifest = new_manifest("Test.X86_64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::X86_64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_aarch64() {
+ let manifest = new_manifest("Test.AARCH64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::Aarch64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::Aarch64);
+ check_object_file(obj, &manifest);
+}
+
+fn check_object_file(obj: CoffFile, expected_manifest: &[u8]) {
+ // There should be one sections `.rsrc`.
+ assert_eq!(
+ obj.sections().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // There should be one section symbol.
+ assert_eq!(
+ obj.symbols().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // The resource section must be a data section.
+ let rsrc = obj.section_by_name(".rsrc").unwrap();
+ assert_eq!(rsrc.address(), 0);
+ assert_eq!(rsrc.kind(), SectionKind::Data);
+
+ // The data RVA in the resource data entry must be relocatable.
+ let (addr, reloc) = rsrc.relocations().next().unwrap();
+ assert_eq!(reloc.kind(), RelocationKind::ImageOffset);
+ assert_eq!(reloc.encoding(), RelocationEncoding::Generic);
+ assert_eq!(addr, 0x48); // size of the directory table, three directories, and no strings
+ assert_eq!(reloc.addend(), 0);
+
+ // The resource directory contains one manifest resource type subdirectory.
+ let data = rsrc.data().unwrap();
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(data).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(24, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The manifest subdirectory contains one image (not DLL) manifest subdirectory.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(1, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The image manifest subdirectory contains one US English manifest data entry.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(0x0409, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(0, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = offset as usize;
+
+ // The manifest data matches what was added.
+ let (entry, resource_data) = pod::from_bytes::<ImageResourceDataEntry>(&data[offset..]).unwrap();
+ let end = entry.size.get(LittleEndian) as usize;
+ let manifest = &resource_data[..end];
+ assert_eq!(manifest, expected_manifest);
+}
diff --git a/third_party/rust/embed-manifest/src/lib.rs b/third_party/rust/embed-manifest/src/lib.rs
new file mode 100644
index 0000000000..6c60cdf541
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/lib.rs
@@ -0,0 +1,134 @@
+//! The `embed-manifest` crate provides a straightforward way to embed
+//! a Windows manifest in an executable, whatever the build environment
+//! and even when cross-compiling, without dependencies on external
+//! tools from LLVM or MinGW.
+//!
+//! This should be called from a [build script][1], as shown below.
+//!
+//! [1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
+//!
+//! On MSVC targets, the manifest file is embedded in the executable by
+//! instructing Cargo to pass `/MANIFEST` options to `LINK.EXE`. This
+//! requires Cargo from Rust 1.56.
+//!
+//! On GNU targets, the manifest file is added as a resource in a COFF
+//! object file, and Cargo is instructed to link this file into the
+//! executable, also using functionality from Rust 1.56.
+//!
+//! # Usage
+//!
+//! This crate should be added to the `[build-dependencies]` section in
+//! your executable’s `Cargo.toml`:
+//!
+//! ```toml
+//! [build-dependencies]
+//! embed-manifest = "1.3.1"
+//! ```
+//!
+//! In the same directory, create a `build.rs` file to call this crate’s
+//! code when building for Windows, and to only run once:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
+//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+//! embed_manifest(new_manifest("Contoso.Sample")).expect("unable to embed manifest file");
+//! }
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+//!
+//! To customise the application manifest, use the methods on it to change things like
+//! enabling the segment heap:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest, manifest::HeapType};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
+//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+//! embed_manifest(new_manifest("Contoso.Sample").heap_type(HeapType::SegmentHeap))
+//! .expect("unable to embed manifest file");
+//! }
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+//!
+//! or making it always use legacy single-byte API encoding and only declaring compatibility
+//! up to Windows 8.1, without checking whether this is a Windows build:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest};
+//! use embed_manifest::manifest::{ActiveCodePage::Legacy, SupportedOS::*};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! let manifest = new_manifest("Contoso.Sample")
+//! .active_code_page(Legacy)
+//! .supported_os(Windows7..=Windows81);
+//! embed_manifest(manifest).expect("unable to embed manifest file");
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+
+#![allow(clippy::needless_doctest_main)]
+
+pub use embed::error::Error;
+pub use embed::{embed_manifest, embed_manifest_file};
+
+use crate::manifest::ManifestBuilder;
+
+mod embed;
+pub mod manifest;
+
+/// Creates a new [`ManifestBuilder`] with sensible defaults, allowing customisation
+/// before the Windows application manifest XML is generated.
+///
+/// The initial values used by the manifest are:
+/// - Version number from the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and
+/// `CARGO_PKG_VERSION_PATCH` environment variables. This can then be changed with
+/// [`version()`][ManifestBuilder::version].
+/// - A dependency on version 6 of the Common Controls so that message boxes and dialogs
+/// will use the latest design, and have the best available support for high DPI displays.
+/// This can be removed with
+/// [`remove_dependency`][ManifestBuilder::remove_dependency].
+/// - [Compatible with Windows from 7 to 11][ManifestBuilder::supported_os],
+/// matching the Rust compiler [tier 1 targets][tier1].
+/// - A “[maximum version tested][ManifestBuilder::max_version_tested]” of Windows 10
+/// version 1903.
+/// - An [active code page][ManifestBuilder::active_code_page] of UTF-8, so that
+/// single-byte Windows APIs will generally interpret Rust strings correctly, starting
+/// from Windows 10 version 1903.
+/// - [Version 2 of per-monitor high DPI awareness][manifest::DpiAwareness::PerMonitorV2Only],
+/// so that user interface elements can be scaled correctly by the application and
+/// Common Controls from Windows 10 version 1703.
+/// - [Long path awareness][ManifestBuilder::long_path_aware] from Windows 10 version
+/// 1607 [when separately enabled][longpaths].
+/// - [Printer driver isolation][ManifestBuilder::printer_driver_isolation] enabled
+/// to improve reliability and security.
+/// - An [execution level][ManifestBuilder::requested_execution_level] of “as invoker”
+/// so that the UAC elevation dialog will never be displayed, regardless of the name
+/// of the program, and [UAC Virtualisation][uac] is disabled in 32-bit programs.
+///
+/// [tier1]: https://doc.rust-lang.org/nightly/rustc/platform-support.html
+/// [longpaths]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later
+/// [uac]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#virtualization
+pub fn new_manifest(name: &str) -> ManifestBuilder {
+ ManifestBuilder::new(name)
+}
+
+/// Creates a new [`ManifestBuilder`] without any settings, allowing creation of
+/// a manifest with only desired content.
+pub fn empty_manifest() -> ManifestBuilder {
+ ManifestBuilder::empty()
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/mod.rs b/third_party/rust/embed-manifest/src/manifest/mod.rs
new file mode 100644
index 0000000000..716a3559f8
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/mod.rs
@@ -0,0 +1,882 @@
+//! A builder for Windows application manifest XML files.
+//!
+//! This module allows the construction of application manifests from code with the
+//! [`ManifestBuilder`], for use from a Cargo build script. Once configured, the builder
+//! should be passed to [`embed_manifest()`][crate::embed_manifest] to generate the
+//! correct instructions for Cargo. For any other use, the builder will output the XML
+//! when formatted for [`Display`], or with [`to_string()`][ToString]. For more
+//! information about the different elements of an application manifest, see
+//! [Application Manifests][1] in the Microsoft Windows App Development documentation.
+//!
+//! [1]: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
+//!
+//! To generate the manifest XML separately, the XML can be output with `write!` or
+//! copied to a string with [`to_string()`][ToString]. To produce the manifest XML with
+//! extra whitespace for formatting, format it with the ‘alternate’ flag:
+//!
+//! ```
+//! # use embed_manifest::new_manifest;
+//! let builder = new_manifest("Company.OrgUnit.Program");
+//! assert_eq!(format!("{:#}", builder), r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+//! <assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+//! <assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.4.0.0"/>
+//! <dependency>
+//! <dependentAssembly>
+//! <assemblyIdentity language="*" name="Microsoft.Windows.Common-Controls" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" type="win32" version="6.0.0.0"/>
+//! </dependentAssembly>
+//! </dependency>
+//! <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+//! <application>
+//! <maxversiontested Id="10.0.18362.1"/>
+//! <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+//! <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+//! <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+//! <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+//! </application>
+//! </compatibility>
+//! <asmv3:application>
+//! <asmv3:windowsSettings>
+//! <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
+//! <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
+//! <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+//! <printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
+//! </asmv3:windowsSettings>
+//! </asmv3:application>
+//! <asmv3:trustInfo>
+//! <asmv3:security>
+//! <asmv3:requestedPrivileges>
+//! <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+//! </asmv3:requestedPrivileges>
+//! </asmv3:security>
+//! </asmv3:trustInfo>
+//! </assembly>"#.replace("\n", "\r\n"))
+//! ```
+
+use std::fmt::{Display, Formatter};
+use std::ops::RangeBounds;
+use std::{env, fmt};
+
+use crate::manifest::xml::XmlFormatter;
+
+mod xml;
+
+#[cfg(test)]
+mod test;
+
+/// An opaque container to describe the Windows application manifest for the
+/// executable. A new instance with reasonable defaults is created with
+/// [`new_manifest()`][crate::new_manifest].
+#[derive(Debug)]
+pub struct ManifestBuilder {
+ identity: Option<AssemblyIdentity>,
+ dependent_assemblies: Vec<AssemblyIdentity>,
+ compatibility: ApplicationCompatibility,
+ windows_settings: WindowsSettings,
+ requested_execution_level: Option<RequestedExecutionLevel>,
+}
+
+impl ManifestBuilder {
+ pub(crate) fn new(name: &str) -> Self {
+ ManifestBuilder {
+ identity: Some(AssemblyIdentity::application(name)),
+ dependent_assemblies: vec![AssemblyIdentity::new(
+ "Microsoft.Windows.Common-Controls",
+ [6, 0, 0, 0],
+ 0x6595b64144ccf1df,
+ )],
+ compatibility: ApplicationCompatibility {
+ max_version_tested: Some(MaxVersionTested::Windows10Version1903),
+ supported_os: vec![
+ SupportedOS::Windows7,
+ SupportedOS::Windows8,
+ SupportedOS::Windows81,
+ SupportedOS::Windows10,
+ ],
+ },
+ windows_settings: WindowsSettings::new(),
+ requested_execution_level: Some(RequestedExecutionLevel {
+ level: ExecutionLevel::AsInvoker,
+ ui_access: false,
+ }),
+ }
+ }
+
+ pub(crate) fn empty() -> Self {
+ ManifestBuilder {
+ identity: None,
+ dependent_assemblies: Vec::new(),
+ compatibility: ApplicationCompatibility {
+ max_version_tested: None,
+ supported_os: Vec::new(),
+ },
+ windows_settings: WindowsSettings::empty(),
+ requested_execution_level: None,
+ }
+ }
+
+ // Set the dot-separated [application name][identity] in the manifest.
+ //
+ // [identity]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#assemblyIdentity
+ pub fn name(mut self, name: &str) -> Self {
+ match self.identity {
+ Some(ref mut identity) => identity.name = name.to_string(),
+ None => self.identity = Some(AssemblyIdentity::application_version(name, 0, 0, 0, 0)),
+ }
+ self
+ }
+
+ /// Set the four-part application version number in the manifest.
+ pub fn version(mut self, major: u16, minor: u16, build: u16, revision: u16) -> Self {
+ match self.identity {
+ Some(ref mut identity) => identity.version = Version(major, minor, build, revision),
+ None => {
+ self.identity = Some(AssemblyIdentity::application_version("", major, minor, build, revision));
+ }
+ }
+ self
+ }
+
+ /// Add a dependency on a specific version of a side-by-side assembly
+ /// to the application manifest. For more information on side-by-side
+ /// assemblies, see [Using Side-by-side Assemblies][sxs].
+ ///
+ /// [sxs]: https://docs.microsoft.com/en-us/windows/win32/sbscs/using-side-by-side-assemblies
+ pub fn dependency(mut self, identity: AssemblyIdentity) -> Self {
+ self.dependent_assemblies.push(identity);
+ self
+ }
+
+ /// Remove a dependency on a side-by-side assembly. This can be used to
+ /// remove the default dependency on Common Controls version 6:
+ ///
+ /// ```
+ /// # use embed_manifest::new_manifest;
+ /// new_manifest("Company.OrgUnit.Program")
+ /// .remove_dependency("Microsoft.Windows.Common-Controls")
+ /// # ;
+ /// ```
+ pub fn remove_dependency(mut self, name: &str) -> Self {
+ self.dependent_assemblies.retain(|d| d.name != name);
+ self
+ }
+
+ /// Set the “maximum version tested” based on a Windows SDK version.
+ /// This compatibility setting enables the use of XAML Islands, as described in
+ /// [Host a standard WinRT XAML control in a C++ desktop (Win32) app][xaml].
+ ///
+ /// [xaml]: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/host-standard-control-with-xaml-islands-cpp
+ pub fn max_version_tested(mut self, version: MaxVersionTested) -> Self {
+ self.compatibility.max_version_tested = Some(version);
+ self
+ }
+
+ /// Remove the “maximum version tested” from the application compatibility.
+ pub fn remove_max_version_tested(mut self) -> Self {
+ self.compatibility.max_version_tested = None;
+ self
+ }
+
+ /// Set the range of supported versions of Windows for application compatibility.
+ /// The default value declares compatibility with every version from
+ /// [Windows 7][SupportedOS::Windows7] to [Windows 10 and 11][SupportedOS::Windows10].
+ pub fn supported_os<R: RangeBounds<SupportedOS>>(mut self, os_range: R) -> Self {
+ use SupportedOS::*;
+
+ self.compatibility.supported_os.clear();
+ for os in [WindowsVista, Windows7, Windows8, Windows81, Windows10] {
+ if os_range.contains(&os) {
+ self.compatibility.supported_os.push(os);
+ }
+ }
+ self
+ }
+
+ /// Set the code page used for single-byte Windows API, starting from Windows 10
+ /// version 1903. The default setting of [UTF-8][`ActiveCodePage::Utf8`] makes Rust
+ /// strings work directly with APIs like `MessageBoxA`.
+ pub fn active_code_page(mut self, code_page: ActiveCodePage) -> Self {
+ self.windows_settings.active_code_page = code_page;
+ self
+ }
+
+ /// Configures how Windows should display this program on monitors where the
+ /// graphics need scaling, whether by the application drawing its user
+ /// interface at different sizes or by the Windows system rendering the graphics
+ /// to a bitmap then resizing that for display.
+ pub fn dpi_awareness(mut self, setting: DpiAwareness) -> Self {
+ self.windows_settings.dpi_awareness = setting;
+ self
+ }
+
+ /// Attempts to scale GDI primitives by the per-monitor scaling values,
+ /// from Windows 10 version 1703. It seems to be best to leave this disabled.
+ pub fn gdi_scaling(mut self, setting: Setting) -> Self {
+ self.windows_settings.gdi_scaling = setting.enabled();
+ self
+ }
+
+ /// Select the memory allocator use by the standard heap allocation APIs,
+ /// including the default Rust allocator. Selecting a different algorithm
+ /// may make performance and memory use better or worse, so any changes
+ /// should be carefully tested.
+ pub fn heap_type(mut self, setting: HeapType) -> Self {
+ self.windows_settings.heap_type = setting;
+ self
+ }
+
+ /// Enable paths longer than 260 characters with some wide-character Win32 APIs,
+ /// when also enabled in the Windows registry. For more details, see
+ /// [Maximum Path Length Limitation][1] in the Windows App Development
+ /// documentation.
+ ///
+ /// As of Rust 1.58, the [Rust standard library bypasses this limitation][2] itself
+ /// by using Unicode paths beginning with `\\?\`.
+ ///
+ /// [1]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
+ /// [2]: https://github.com/rust-lang/rust/pull/89174
+ pub fn long_path_aware(mut self, setting: Setting) -> Self {
+ self.windows_settings.long_path_aware = setting.enabled();
+ self
+ }
+
+ /// Enable printer driver isolation for the application, improving security and
+ /// stability when printing by loading the printer driver in a separate
+ /// application. This is poorly documented, but is described in a blog post,
+ /// “[Application-level Printer Driver Isolation][post]”.
+ ///
+ /// [post]: https://peteronprogramming.wordpress.com/2018/01/22/application-level-printer-driver-isolation/
+ pub fn printer_driver_isolation(mut self, setting: Setting) -> Self {
+ self.windows_settings.printer_driver_isolation = setting.enabled();
+ self
+ }
+
+ /// Configure whether the application should receive mouse wheel scroll events
+ /// with a minimum delta of 1, 40 or 120, as described in
+ /// [Windows precision touchpad devices][touchpad].
+ ///
+ /// [touchpad]: https://docs.microsoft.com/en-us/windows/win32/w8cookbook/windows-precision-touchpad-devices
+ pub fn scrolling_awareness(mut self, setting: ScrollingAwareness) -> Self {
+ self.windows_settings.scrolling_awareness = setting;
+ self
+ }
+
+ /// Allows the application to disable the filtering that normally
+ /// removes UWP windows from the results of the `EnumWindows` API.
+ pub fn window_filtering(mut self, setting: Setting) -> Self {
+ self.windows_settings.disable_window_filtering = setting.disabled();
+ self
+ }
+
+ /// Selects the authorities to execute the program with, rather than
+ /// [guessing based on a filename][installer] like `setup.exe`,
+ /// `update.exe` or `patch.exe`.
+ ///
+ /// [installer]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#installer-detection-technology
+ pub fn requested_execution_level(mut self, level: ExecutionLevel) -> Self {
+ match self.requested_execution_level {
+ Some(ref mut requested_execution_level) => requested_execution_level.level = level,
+ None => self.requested_execution_level = Some(RequestedExecutionLevel { level, ui_access: false }),
+ }
+ self
+ }
+
+ /// Allows the application to access the user interface of applications
+ /// running with elevated permissions when this program does not, typically
+ /// for accessibility. The program must additionally be correctly signed
+ /// and installed in a trusted location like the Program Files directory.
+ pub fn ui_access(mut self, access: bool) -> Self {
+ match self.requested_execution_level {
+ Some(ref mut requested_execution_level) => requested_execution_level.ui_access = access,
+ None => {
+ self.requested_execution_level = Some(RequestedExecutionLevel {
+ level: ExecutionLevel::AsInvoker,
+ ui_access: access,
+ })
+ }
+ }
+ self
+ }
+}
+
+impl Display for ManifestBuilder {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let mut w = XmlFormatter::new(f);
+ w.start_document()?;
+ let mut attrs = vec![("xmlns", "urn:schemas-microsoft-com:asm.v1")];
+ if !self.windows_settings.is_empty() || self.requested_execution_level.is_some() {
+ attrs.push(("xmlns:asmv3", "urn:schemas-microsoft-com:asm.v3"));
+ }
+ attrs.push(("manifestVersion", "1.0"));
+ w.start_element("assembly", &attrs)?;
+ if let Some(ref identity) = self.identity {
+ identity.xml_to(&mut w)?;
+ }
+ if !self.dependent_assemblies.is_empty() {
+ w.element("dependency", &[], |w| {
+ for d in self.dependent_assemblies.as_slice() {
+ w.element("dependentAssembly", &[], |w| d.xml_to(w))?;
+ }
+ Ok(())
+ })?;
+ }
+ if !self.compatibility.is_empty() {
+ self.compatibility.xml_to(&mut w)?;
+ }
+ if !self.windows_settings.is_empty() {
+ self.windows_settings.xml_to(&mut w)?;
+ }
+ if let Some(ref requested_execution_level) = self.requested_execution_level {
+ requested_execution_level.xml_to(&mut w)?;
+ }
+ w.end_element("assembly")
+ }
+}
+
+/// Identity of a side-by-side assembly dependency for the application.
+#[derive(Debug)]
+pub struct AssemblyIdentity {
+ r#type: AssemblyType,
+ name: String,
+ language: Option<String>,
+ processor_architecture: Option<AssemblyProcessorArchitecture>,
+ version: Version,
+ public_key_token: Option<PublicKeyToken>,
+}
+
+impl AssemblyIdentity {
+ fn application(name: &str) -> AssemblyIdentity {
+ let major = env::var("CARGO_PKG_VERSION_MAJOR").map_or(0, |s| s.parse().unwrap_or(0));
+ let minor = env::var("CARGO_PKG_VERSION_MINOR").map_or(0, |s| s.parse().unwrap_or(0));
+ let patch = env::var("CARGO_PKG_VERSION_PATCH").map_or(0, |s| s.parse().unwrap_or(0));
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: None,
+ processor_architecture: None,
+ version: Version(major, minor, patch, 0),
+ public_key_token: None,
+ }
+ }
+
+ fn application_version(name: &str, major: u16, minor: u16, build: u16, revision: u16) -> AssemblyIdentity {
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: None,
+ processor_architecture: None,
+ version: Version(major, minor, build, revision),
+ public_key_token: None,
+ }
+ }
+
+ /// Creates a new value for a [manifest dependency][ManifestBuilder::dependency],
+ /// with the `version` as an array of numbers like `[1, 0, 0, 0]` for 1.0.0.0,
+ /// and the public key token as a 64-bit number like `0x6595b64144ccf1df`.
+ pub fn new(name: &str, version: [u16; 4], public_key_token: u64) -> AssemblyIdentity {
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: Some("*".to_string()),
+ processor_architecture: Some(AssemblyProcessorArchitecture::All),
+ version: Version(version[0], version[1], version[2], version[3]),
+ public_key_token: Some(PublicKeyToken(public_key_token)),
+ }
+ }
+
+ /// Change the language from `"*"` to the language code in `language`.
+ pub fn language(mut self, language: &str) -> Self {
+ self.language = Some(language.to_string());
+ self
+ }
+
+ /// Change the processor architecture from `"*"` to the architecture in `arch`.
+ pub fn processor_architecture(mut self, arch: AssemblyProcessorArchitecture) -> Self {
+ self.processor_architecture = Some(arch);
+ self
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ let version = self.version.to_string();
+ let public_key_token = self.public_key_token.as_ref().map(|token| token.to_string());
+
+ let mut attrs: Vec<(&str, &str)> = Vec::with_capacity(6);
+ if let Some(ref language) = self.language {
+ attrs.push(("language", language));
+ }
+ attrs.push(("name", &self.name));
+ if let Some(ref arch) = self.processor_architecture {
+ attrs.push(("processorArchitecture", arch.as_str()))
+ }
+ if let Some(ref token) = public_key_token {
+ attrs.push(("publicKeyToken", token));
+ }
+ attrs.push(("type", self.r#type.as_str()));
+ attrs.push(("version", &version));
+ w.empty_element("assemblyIdentity", &attrs)
+ }
+}
+
+#[derive(Debug)]
+struct Version(u16, u16, u16, u16);
+
+impl fmt::Display for Version {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
+ }
+}
+
+#[derive(Debug)]
+struct PublicKeyToken(u64);
+
+impl fmt::Display for PublicKeyToken {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:016x}", self.0)
+ }
+}
+
+/// Processor architecture for an assembly identity.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum AssemblyProcessorArchitecture {
+ /// Any processor architecture, as `"*"`.
+ All,
+ /// 32-bit x86 processors, as `"x86"`.
+ X86,
+ /// 64-bit x64 processors, as `"x64"`.
+ Amd64,
+ /// 32-bit ARM processors, as `"arm"`.
+ Arm,
+ /// 64-bit ARM processors, as `"arm64"`.
+ Arm64,
+}
+
+impl AssemblyProcessorArchitecture {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::All => "*",
+ Self::X86 => "x86",
+ Self::Amd64 => "amd64",
+ Self::Arm => "arm",
+ Self::Arm64 => "arm64",
+ }
+ }
+}
+
+impl fmt::Display for AssemblyProcessorArchitecture {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+enum AssemblyType {
+ Win32,
+}
+
+impl AssemblyType {
+ fn as_str(&self) -> &'static str {
+ "win32"
+ }
+}
+
+#[derive(Debug)]
+struct ApplicationCompatibility {
+ max_version_tested: Option<MaxVersionTested>,
+ supported_os: Vec<SupportedOS>,
+}
+
+impl ApplicationCompatibility {
+ fn is_empty(&self) -> bool {
+ self.supported_os.is_empty()
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element(
+ "compatibility",
+ &[("xmlns", "urn:schemas-microsoft-com:compatibility.v1")],
+ |w| {
+ w.element("application", &[], |w| {
+ if self.supported_os.contains(&SupportedOS::Windows10) {
+ if let Some(ref version) = self.max_version_tested {
+ w.empty_element("maxversiontested", &[("Id", version.as_str())])?;
+ }
+ }
+ for os in self.supported_os.iter() {
+ w.empty_element("supportedOS", &[("Id", os.as_str())])?
+ }
+ Ok(())
+ })
+ },
+ )
+ }
+}
+
+/// Windows build versions for [`max_version_tested()`][ManifestBuilder::max_version_tested]
+/// from the [Windows SDK archive](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/).
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum MaxVersionTested {
+ /// Windows 10 version 1903, with build version 10.0.18362.0.
+ Windows10Version1903,
+ /// Windows 10 version 2004, with build version 10.0.19041.0.
+ Windows10Version2004,
+ /// Windows 10 version 2104, with build version 10.0.20348.0.
+ Windows10Version2104,
+ /// Windows 11, with build version 10.0.22000.194.
+ Windows11,
+ /// Windows 11 version 22H2, with build version 10.0.22621.1.
+ Windows11Version22H2,
+}
+
+impl MaxVersionTested {
+ /// Return the Windows version as a string.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Windows10Version1903 => "10.0.18362.1",
+ Self::Windows10Version2004 => "10.0.19041.0",
+ Self::Windows10Version2104 => "10.0.20348.0",
+ Self::Windows11 => "10.0.22000.194",
+ Self::Windows11Version22H2 => "10.0.22621.1",
+ }
+ }
+}
+
+impl Display for MaxVersionTested {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+/// Operating system versions for Windows compatibility.
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum SupportedOS {
+ /// Windows Vista and Windows Server 2008.
+ WindowsVista,
+ /// Windows 7 and Windows Server 2008 R2.
+ Windows7,
+ /// Windows 8 and Windows Server 2012.
+ Windows8,
+ /// Windows 8.1 and Windows Server 2012 R2.
+ Windows81,
+ /// Windows 10 and 11, and Windows Server 2016, 2019 and 2022.
+ Windows10,
+}
+
+impl SupportedOS {
+ /// Returns the GUID string for the Windows version.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::WindowsVista => "{e2011457-1546-43c5-a5fe-008deee3d3f0}",
+ Self::Windows7 => "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
+ Self::Windows8 => "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
+ Self::Windows81 => "{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
+ Self::Windows10 => "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}",
+ }
+ }
+}
+
+impl Display for SupportedOS {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+static WS2005: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
+static WS2011: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2011/WindowsSettings");
+static WS2013: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2013/WindowsSettings");
+static WS2016: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
+static WS2017: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2017/WindowsSettings");
+static WS2019: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2019/WindowsSettings");
+static WS2020: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2020/WindowsSettings");
+
+#[derive(Debug)]
+struct WindowsSettings {
+ active_code_page: ActiveCodePage,
+ disable_window_filtering: bool,
+ dpi_awareness: DpiAwareness,
+ gdi_scaling: bool,
+ heap_type: HeapType,
+ long_path_aware: bool,
+ printer_driver_isolation: bool,
+ scrolling_awareness: ScrollingAwareness,
+}
+
+impl WindowsSettings {
+ fn new() -> Self {
+ Self {
+ active_code_page: ActiveCodePage::Utf8,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::PerMonitorV2Only,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: true,
+ printer_driver_isolation: true,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ }
+
+ fn empty() -> Self {
+ Self {
+ active_code_page: ActiveCodePage::System,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::UnawareByDefault,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: false,
+ printer_driver_isolation: false,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ }
+
+ fn is_empty(&self) -> bool {
+ matches!(
+ self,
+ Self {
+ active_code_page: ActiveCodePage::System,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::UnawareByDefault,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: false,
+ printer_driver_isolation: false,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ )
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element("asmv3:application", &[], |w| {
+ w.element("asmv3:windowsSettings", &[], |w| {
+ self.active_code_page.xml_to(w)?;
+ if self.disable_window_filtering {
+ w.element("disableWindowFiltering", &[WS2011], |w| w.text("true"))?;
+ }
+ self.dpi_awareness.xml_to(w)?;
+ if self.gdi_scaling {
+ w.element("gdiScaling", &[WS2017], |w| w.text("true"))?;
+ }
+ if matches!(self.heap_type, HeapType::SegmentHeap) {
+ w.element("heapType", &[WS2020], |w| w.text("SegmentHeap"))?;
+ }
+ if self.long_path_aware {
+ w.element("longPathAware", &[WS2016], |w| w.text("true"))?;
+ }
+ if self.printer_driver_isolation {
+ w.element("printerDriverIsolation", &[WS2011], |w| w.text("true"))?;
+ }
+ self.scrolling_awareness.xml_to(w)
+ })
+ })
+ }
+}
+
+/// Configure whether a Windows setting is enabled or disabled, avoiding confusion
+/// over which of these options `true` and `false` represent.
+#[derive(Debug)]
+pub enum Setting {
+ Disabled = 0,
+ Enabled = 1,
+}
+
+impl Setting {
+ /// Returns `true` if the setting should be disabled.
+ fn disabled(&self) -> bool {
+ matches!(self, Setting::Disabled)
+ }
+
+ /// Returns `true` if the setting should be enabled.
+ fn enabled(&self) -> bool {
+ matches!(self, Setting::Enabled)
+ }
+}
+
+/// The code page used by single-byte APIs in the program.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ActiveCodePage {
+ /// Use the code page from the configured system locale, or UTF-8 if “Use Unicode UTF-8
+ /// for worldwide language support” is configured.
+ System,
+ /// Use UTF-8 from Windows 10 version 1903 and on Windows 11.
+ Utf8,
+ /// Use the code page from the configured system locale, even when “Use Unicode UTF-8
+ /// for worldwide language support” is configured.
+ Legacy,
+ /// Use the code page from the configured system locale on Windows 10, or from this
+ /// locale on Windows 11.
+ Locale(String),
+}
+
+impl ActiveCodePage {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Self::System => "",
+ Self::Utf8 => "UTF-8",
+ Self::Legacy => "Legacy",
+ Self::Locale(s) => s,
+ }
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ match self {
+ Self::System => Ok(()),
+ _ => w.element("activeCodePage", &[WS2019], |w| w.text(self.as_str())),
+ }
+ }
+}
+
+impl Display for ActiveCodePage {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+/// Options for how Windows will handle drawing on monitors when the graphics
+/// need scaling to display at the correct size.
+///
+/// See [High DPI Desktop Application Development on Windows][dpi] for more details
+/// about the impact of these options.
+///
+/// [dpi]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum DpiAwareness {
+ /// DPI awareness is not configured, so Windows will scale the application unless
+ /// changed via the `SetProcessDpiAware` or `SetProcessDpiAwareness` API.
+ UnawareByDefault,
+ /// DPI awareness is not configured, with Windows 8.1, 10 and 11 not able
+ /// to change the setting via API.
+ Unaware,
+ /// The program uses the system DPI, or the DPI of the monitor they start on if
+ /// “Fix scaling for apps” is enabled. If the DPI does not match the current
+ /// monitor then Windows will scale the application.
+ System,
+ /// The program uses the system DPI on Windows Vista, 7 and 8, and version 1 of
+ /// per-monitor DPI awareness on Windows 8.1, 10 and 11. Using version 1 of the
+ /// API is not recommended.
+ PerMonitor,
+ /// The program uses the system DPI on Windows Vista, 7 and 8, version 1 of
+ /// per-monitor DPI awareness on Windows 8.1 and Windows 10 version 1507 and 1511,
+ /// and version 2 of per-monitor DPI awareness from Windows 10 version 1607.
+ PerMonitorV2,
+ /// The program uses the system DPI on Windows Vista, 7, 8, 8.1 and Windows 10
+ /// version 1507 and 1511, and version 2 of per-monitor DPI awareness from
+ /// Windows 10 version 1607.
+ PerMonitorV2Only,
+}
+
+impl DpiAwareness {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ let settings = match self {
+ Self::UnawareByDefault => (None, None),
+ Self::Unaware => (Some("false"), None),
+ Self::System => (Some("true"), None),
+ Self::PerMonitor => (Some("true/pm"), None),
+ Self::PerMonitorV2 => (Some("true/pm"), Some("permonitorv2,permonitor")),
+ Self::PerMonitorV2Only => (None, Some("permonitorv2")),
+ };
+ if let Some(dpi_aware) = settings.0 {
+ w.element("dpiAware", &[WS2005], |w| w.text(dpi_aware))?;
+ }
+ if let Some(dpi_awareness) = settings.1 {
+ w.element("dpiAwareness", &[WS2016], |w| w.text(dpi_awareness))?;
+ }
+ Ok(())
+ }
+}
+
+/// The heap type for the default memory allocator.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum HeapType {
+ /// The default heap type since Windows Vista.
+ LowFragmentationHeap,
+ /// The modern segment heap, which may reduce total memory allocation in some programs.
+ /// This is supported since Windows 10 version 2004. See
+ /// [Improving Memory Usage in Microsoft Edge][edge].
+ ///
+ /// [edge]: https://blogs.windows.com/msedgedev/2020/06/17/improving-memory-usage/
+ SegmentHeap,
+}
+
+/// Whether the application supports scroll wheel events with a minimum delta
+/// of 1, 40 or 120.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ScrollingAwareness {
+ /// The application can only handle scroll wheel events with the original delta of 120.
+ LowResolution,
+ /// The application can handle high precision scroll wheel events with a delta of 40.
+ HighResolution,
+ /// The application can handle ultra high precision scroll wheel events with a delta as low as 1.
+ UltraHighResolution,
+}
+
+impl ScrollingAwareness {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ match self {
+ Self::LowResolution => w.element("ultraHighResolutionScrollingAware", &[WS2013], |w| w.text("false")),
+ Self::HighResolution => w.element("highResolutionScrollingAware", &[WS2013], |w| w.text("true")),
+ Self::UltraHighResolution => Ok(()),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct RequestedExecutionLevel {
+ level: ExecutionLevel,
+ ui_access: bool,
+}
+
+impl RequestedExecutionLevel {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element("asmv3:trustInfo", &[], |w| {
+ w.element("asmv3:security", &[], |w| {
+ w.element("asmv3:requestedPrivileges", &[], |w| {
+ w.empty_element(
+ "asmv3:requestedExecutionLevel",
+ &[
+ ("level", self.level.as_str()),
+ ("uiAccess", if self.ui_access { "true" } else { "false" }),
+ ],
+ )
+ })
+ })
+ })
+ }
+}
+
+/// The requested execution level for the application when started.
+///
+/// The behaviour of each option is described in
+/// [Designing UAC Applications for Windows Vista Step 6: Create and Embed an Application Manifest][step6].
+///
+/// [step6]: https://msdn.microsoft.com/en-us/library/bb756929.aspx
+#[derive(Debug)]
+pub enum ExecutionLevel {
+ /// The application will always run with the same authorities as the program invoking it.
+ AsInvoker,
+ /// The program will run without special authorities for a standard user, but will try to
+ /// run with administrator authority if the user is an administrator. This is rarely used.
+ HighestAvailable,
+ /// The application will run as an administrator, prompting for elevation if necessary.
+ RequireAdministrator,
+}
+
+impl ExecutionLevel {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::AsInvoker => "asInvoker",
+ Self::HighestAvailable => "highestAvailable",
+ Self::RequireAdministrator => "requireAdministrator",
+ }
+ }
+}
+
+impl Display for ExecutionLevel {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/test.rs b/third_party/rust/embed-manifest/src/manifest/test.rs
new file mode 100644
index 0000000000..9290f7661d
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/test.rs
@@ -0,0 +1,117 @@
+use super::{ExecutionLevel, SupportedOS};
+use crate::{empty_manifest, new_manifest};
+
+fn enc(s: &str) -> String {
+ let mut buf = String::with_capacity(1024);
+ buf.push('\u{feff}');
+ for l in s.lines() {
+ buf.push_str(l);
+ buf.push_str("\r\n");
+ }
+ buf.truncate(buf.len() - 2);
+ buf
+}
+
+fn encp(s: &str) -> String {
+ s.replace("\n", "\r\n")
+}
+
+#[test]
+fn empty_manifest_canonical() {
+ let builder = empty_manifest();
+ assert_eq!(
+ format!("{}", builder),
+ enc(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"></assembly>"#)
+ );
+}
+
+#[test]
+fn empty_manifest_pretty() {
+ let builder = empty_manifest();
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"/>"#
+ )
+ );
+}
+
+#[test]
+fn only_execution_level() {
+ let builder = empty_manifest().requested_execution_level(ExecutionLevel::AsInvoker);
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+ <asmv3:trustInfo>
+ <asmv3:security>
+ <asmv3:requestedPrivileges>
+ <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </asmv3:requestedPrivileges>
+ </asmv3:security>
+ </asmv3:trustInfo>
+</assembly>"#
+ )
+ );
+}
+
+#[test]
+fn only_windows10() {
+ let builder = empty_manifest().supported_os(SupportedOS::Windows10..);
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+</assembly>"#
+ )
+ );
+}
+
+#[test]
+fn no_comctl32_6() {
+ let builder = new_manifest("Company.OrgUnit.Program")
+ .version(1, 0, 0, 2)
+ .remove_dependency("Microsoft.Windows.Common-Controls");
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+ <assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.0.0.2"/>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <maxversiontested Id="10.0.18362.1"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+ <asmv3:application>
+ <asmv3:windowsSettings>
+ <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ <printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+ <asmv3:trustInfo>
+ <asmv3:security>
+ <asmv3:requestedPrivileges>
+ <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </asmv3:requestedPrivileges>
+ </asmv3:security>
+ </asmv3:trustInfo>
+</assembly>"#
+ )
+ );
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/xml.rs b/third_party/rust/embed-manifest/src/manifest/xml.rs
new file mode 100644
index 0000000000..a247f42a61
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/xml.rs
@@ -0,0 +1,140 @@
+use std::fmt::{self, Display, Formatter, Write};
+
+/// Simple but still over-engineered XML generator for a [`Formatter`], for generating
+/// Windows Manifest XML. This can easily generate invalid XML.
+///
+/// When used correctly, this should generate the same output as MT’s `-canonicalize`
+/// option.
+pub struct XmlFormatter<'a, 'f> {
+ f: &'f mut Formatter<'a>,
+ state: State,
+ depth: usize,
+}
+
+#[derive(Eq, PartialEq)]
+enum State {
+ Init,
+ StartTag,
+ Text,
+}
+
+impl<'a, 'f> XmlFormatter<'a, 'f> {
+ pub fn new(f: &'f mut Formatter<'a>) -> Self {
+ Self {
+ f,
+ state: State::Init,
+ depth: 0,
+ }
+ }
+
+ fn pretty(&mut self) -> fmt::Result {
+ if self.f.alternate() {
+ self.f.write_str("\r\n")?;
+ for _ in 0..self.depth {
+ self.f.write_str(" ")?;
+ }
+ }
+ Ok(())
+ }
+
+ pub fn start_document(&mut self) -> fmt::Result {
+ if !self.f.alternate() {
+ self.f.write_char('\u{FEFF}')?;
+ }
+ self.f
+ .write_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n")
+ }
+
+ pub fn element<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, name: &str, attrs: &[(&str, &str)], f: F) -> fmt::Result {
+ self.start_element(name, attrs)?;
+ f(self)?;
+ self.end_element(name)
+ }
+
+ pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
+ self.start_element(name, attrs)?;
+ self.end_element(name)
+ }
+
+ pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
+ if self.state == State::StartTag {
+ self.f.write_char('>')?;
+ }
+ if self.depth != 0 {
+ self.pretty()?;
+ }
+ write!(self.f, "<{}", name)?;
+ for (name, value) in attrs {
+ write!(self.f, " {}=\"{}\"", name, Xml(value))?;
+ }
+ self.depth += 1;
+ self.state = State::StartTag;
+ Ok(())
+ }
+
+ pub fn end_element(&mut self, name: &str) -> fmt::Result {
+ self.depth -= 1;
+ match self.state {
+ State::Init => {
+ self.pretty()?;
+ write!(self.f, "</{}>", name)
+ }
+ State::Text => {
+ self.state = State::Init;
+ write!(self.f, "</{}>", name)
+ }
+ State::StartTag => {
+ self.state = State::Init;
+ if self.f.alternate() {
+ self.f.write_str("/>")
+ } else {
+ write!(self.f, "></{}>", name)
+ }
+ }
+ }
+ }
+
+ pub fn text(&mut self, s: &str) -> fmt::Result {
+ if self.state == State::StartTag {
+ self.state = State::Text;
+ self.f.write_char('>')?;
+ }
+ Xml(s).fmt(self.f)
+ }
+}
+
+/// Temporary wrapper for outputting a string with XML attribute encoding.
+/// This does not do anything with the control characters which are not
+/// valid in XML, encoded or not.
+struct Xml<'a>(&'a str);
+
+impl<'a> Display for Xml<'a> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ // Process the string in blocks separated by special characters, so that the parts that
+ // don't need encoding can be written all at once, not character by character, and with
+ // no checks for whether string slices are aligned on character boundaries.
+ for s in self.0.split_inclusive(&['<', '&', '>', '"', '\r'][..]) {
+ // Check whether the last character in the substring needs encoding. This will be
+ // `None` at the end of the input string.
+ let mut iter = s.chars();
+ let ch = match iter.next_back() {
+ Some('<') => Some("&lt;"),
+ Some('&') => Some("&amp;"),
+ Some('>') => Some("&gt;"),
+ Some('"') => Some("&quot;"),
+ Some('\r') => Some("&#13;"),
+ _ => None,
+ };
+ // Write the substring except the last character, then the encoded character;
+ // or the entire substring if it is not terminated by a special character.
+ match ch {
+ Some(enc) => {
+ f.write_str(iter.as_str())?;
+ f.write_str(enc)?;
+ }
+ None => f.write_str(s)?,
+ }
+ }
+ Ok(())
+ }
+}