This commit is contained in:
Benoît CORTIER 2025-04-26 01:14:15 +09:00
parent 194ed07630
commit cd3973e0de
No known key found for this signature in database
GPG key ID: FE943BF70ABEA672
11 changed files with 394 additions and 0 deletions

18
Cargo.lock generated
View file

@ -2429,6 +2429,13 @@ dependencies = [
"tracing",
]
[[package]]
name = "ironrdp-cfg"
version = "0.1.0"
dependencies = [
"ironrdp-propertyset",
]
[[package]]
name = "ironrdp-client"
version = "0.1.0"
@ -2628,6 +2635,10 @@ dependencies = [
name = "ironrdp-pdu-generators"
version = "0.0.0"
[[package]]
name = "ironrdp-propertyset"
version = "0.1.0"
[[package]]
name = "ironrdp-rdcleanpath"
version = "0.1.3"
@ -2659,6 +2670,13 @@ dependencies = [
"tracing",
]
[[package]]
name = "ironrdp-rdpfile"
version = "0.1.0"
dependencies = [
"ironrdp-propertyset",
]
[[package]]
name = "ironrdp-rdpsnd"
version = "0.4.0"

View file

@ -0,0 +1,23 @@
[package]
name = "ironrdp-cfg"
version = "0.1.0"
readme = "README.md"
description = "IronRDP utilities for ironrdp-cfgstore"
publish = false # Temporary.
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public
[lints]
workspace = true

View file

@ -0,0 +1,7 @@
# IronRDP Configuration
IronRDP-related utilities for ironrdp-propertyset.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,38 @@
// QUESTION: consider auto-generating this file based on a reference file?
// https://gist.github.com/awakecoding/838c7fe2ed3a6208e3ca5d8af25363f6
use ironrdp_propertyset::{ExtractFrom, PropertySet, Value};
pub trait PropertySetExt {
fn read<'a, V: ExtractFrom<&'a Value>>(&'a self, key: &str) -> Option<V>;
fn full_address(&self) -> Option<&str> {
self.read::<&str>("full address")
}
fn alternate_full_address(&self) -> Option<&str> {
self.read::<&str>("alternate full address")
}
fn gateway_hostname(&self) -> Option<&str> {
self.read::<&str>("gatewayhostname")
}
fn remote_application_name(&self) -> Option<&str> {
self.read::<&str>("remoteapplicationname")
}
fn remote_application_program(&self) -> Option<&str> {
self.read::<&str>("remoteapplicationprogram")
}
fn kdc_proxy_url(&self) -> Option<&str> {
self.read::<&str>("kdcproxyurl")
}
}
impl PropertySetExt for PropertySet {
fn read<'a, V: ExtractFrom<&'a Value>>(&'a self, key: &str) -> Option<V> {
todo!("should we log directly from ironrdp-propertyset?");
}
}

View file

@ -4,6 +4,7 @@
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::fmt;

View file

@ -0,0 +1,20 @@
[package]
name = "ironrdp-propertyset"
version = "0.1.0"
readme = "README.md"
description = "A key-value store for configuration options"
publish = false # Temporary.
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[lints]
workspace = true

View file

@ -0,0 +1,7 @@
# IronRDP PropertySet
The main type is `PropertySet`, a key-value store for configuration options.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,129 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
#![no_std]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::collections::BTreeMap;
use alloc::string::String;
pub type Key = Cow<'static, str>;
/// Key-value store for configuration keys.
#[derive(Clone, Default, PartialEq, Eq)]
pub struct PropertySet {
inner: BTreeMap<Key, Value>,
}
impl PropertySet {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: impl Into<Key>, value: impl Into<Value>) -> Option<Value> {
self.inner.insert(key.into(), value.into())
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
self.inner.remove(key)
}
pub fn get<'a, V: ExtractFrom<&'a Value>>(&'a self, key: &str) -> Option<V> {
self.inner.get(key).and_then(|val| V::extract_from(val, private::Token))
}
pub fn iter(&self) -> impl Iterator<Item = (&Key, &Value)> {
self.inner.iter()
}
pub fn into_iter(self) -> impl Iterator<Item = (Key, Value)> {
self.inner.into_iter()
}
}
macro_rules! impl_from {
($from:ty => $enum:ident :: $variant:ident) => {
impl From<$from> for $enum {
fn from(value: $from) -> Self {
Self::$variant(value.into())
}
}
};
}
macro_rules! impl_extract_from {
(ref $enum:ident :: as_int => $to:ty) => {
impl ExtractFrom<&$enum> for $to {
fn extract_from(value: &$enum, _token: private::Token) -> Option<Self> {
value.as_int().and_then(|v| v.try_into().ok())
}
}
};
}
pub trait ExtractFrom<Value>: Sized {
fn extract_from(value: Value, _token: private::Token) -> Option<Self>;
}
/// Represents a value of any type of the .RDP file format.
#[derive(Clone, PartialEq, Eq)]
pub enum Value {
/// Numerical value.
Int(i64),
/// String value.
Str(String),
}
impl Value {
pub fn as_str(&self) -> Option<&str> {
if let Self::Str(value) = self {
Some(value.as_str())
} else {
None
}
}
pub fn as_int(&self) -> Option<i64> {
if let Self::Int(value) = self {
Some(*value)
} else {
None
}
}
}
impl_from!(String => Value::Str);
impl_from!(&str => Value::Str);
impl_from!(u8 => Value::Int);
impl_from!(u16 => Value::Int);
impl_from!(u32 => Value::Int);
impl_from!(i8 => Value::Int);
impl_from!(i16 => Value::Int);
impl_from!(i32 => Value::Int);
impl_from!(i64 => Value::Int);
impl_from!(bool => Value::Int);
impl_extract_from!(ref Value::as_int => u8);
impl_extract_from!(ref Value::as_int => u16);
impl_extract_from!(ref Value::as_int => u32);
impl_extract_from!(ref Value::as_int => i8);
impl_extract_from!(ref Value::as_int => i16);
impl_extract_from!(ref Value::as_int => i32);
impl_extract_from!(ref Value::as_int => i64);
impl<'a> ExtractFrom<&'a Value> for &'a str {
fn extract_from(value: &'a Value, _token: private::Token) -> Option<Self> {
value.as_str()
}
}
impl ExtractFrom<&Value> for bool {
fn extract_from(value: &Value, _token: private::Token) -> Option<Self> {
value.as_int().map(|value| value != 0)
}
}
mod private {
pub struct Token;
}

View file

@ -0,0 +1,23 @@
[package]
name = "ironrdp-rdpfile"
version = "0.1.0"
readme = "README.md"
description = "Parser and writer for .RDP file format"
publish = false # Temporary.
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[lib]
doctest = false
test = false
[dependencies]
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public
[lints]
workspace = true

View file

@ -0,0 +1,7 @@
# IronRDP .RDP file
Loader and writer for the .RDPfile format.
This crate is part of the [IronRDP] project.
[IronRDP]: https://github.com/Devolutions/IronRDP

View file

@ -0,0 +1,121 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
#![no_std]
extern crate alloc;
use core::fmt;
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use ironrdp_propertyset::{PropertySet, Value};
#[derive(Debug, Clone)]
pub enum ErrorKind {
UnknownType { ty: String },
InvalidValue { ty: String, value: String },
MalformedLine { line: String },
DuplicatedKey { key: String },
}
#[derive(Debug, Clone)]
pub struct Error {
pub kind: ErrorKind,
pub line: usize,
}
impl core::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let line_number = self.line;
match &self.kind {
ErrorKind::UnknownType { ty } => write!(f, "unknown type at line {line_number} ({ty})"),
ErrorKind::InvalidValue { ty, value } => {
write!(f, "invalid value at line {line_number} for type {ty} ({value})")
}
ErrorKind::MalformedLine { line } => write!(f, "malformed line at line {line_number} ({line})"),
ErrorKind::DuplicatedKey { key } => write!(f, "duplicated key at line {line_number} ({key})"),
}
}
}
pub struct ParseResult {
pub store: PropertySet,
pub errors: Vec<Error>,
}
pub fn parse(input: &str) -> ParseResult {
let mut store = PropertySet::new();
let mut errors = Vec::new();
for (idx, line) in input.lines().enumerate() {
let mut split = line.splitn(2, ':');
if let (Some(key), Some(ty), Some(value)) = (split.next(), split.next(), split.next()) {
let is_duplicated = match ty {
"i" => {
if let Ok(value) = value.parse::<i64>() {
store.insert(key.to_owned(), value).is_some()
} else {
errors.push(Error {
kind: ErrorKind::InvalidValue {
ty: ty.to_owned(),
value: value.to_owned(),
},
line: idx,
});
continue;
}
}
"s" => store.insert(key.to_owned(), value).is_some(),
_ => {
errors.push(Error {
kind: ErrorKind::UnknownType { ty: ty.to_owned() },
line: idx,
});
continue;
}
};
if is_duplicated {
errors.push(Error {
kind: ErrorKind::DuplicatedKey { key: key.to_owned() },
line: idx,
});
}
} else {
errors.push(Error {
kind: ErrorKind::MalformedLine { line: line.to_owned() },
line: idx,
})
}
}
ParseResult { store, errors }
}
pub fn write(store: &PropertySet) -> String {
let mut buf = String::new();
for (key, value) in store.iter() {
buf.push_str(key);
match value {
Value::Int(value) => {
buf.push_str(":i:");
buf.push_str(&value.to_string());
}
Value::Str(value) => {
buf.push_str(":s:");
buf.push_str(value);
}
}
buf.push('\n');
}
buf
}