Refactored the "helpers" so that they use a common Codec trait; this makes them composable as well.
This commit is contained in:
parent
e36b8d4fb9
commit
18cb6f6e2d
19 changed files with 285 additions and 222 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::hashes::Sha1HexAttribute;
|
use crate::hashes::Sha1HexAttribute;
|
||||||
use crate::pubsub::PubSubPayload;
|
use crate::pubsub::PubSubPayload;
|
||||||
use crate::util::helpers::WhitespaceAwareBase64;
|
use crate::util::text_node_codecs::{Codec, WhitespaceAwareBase64};
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Communicates information about an avatar.
|
/// Communicates information about an avatar.
|
||||||
|
|
@ -48,7 +48,7 @@ generate_element!(
|
||||||
Data, "data", AVATAR_DATA,
|
Data, "data", AVATAR_DATA,
|
||||||
text: (
|
text: (
|
||||||
/// Vector of bytes representing the avatar’s image.
|
/// Vector of bytes representing the avatar’s image.
|
||||||
data: WhitespaceAwareBase64<Vec<u8>>
|
data: WhitespaceAwareBase64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::hashes::{Algo, Hash};
|
use crate::hashes::{Algo, Hash};
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
use minidom::IntoAttributeValue;
|
use minidom::IntoAttributeValue;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// The actual data.
|
/// The actual data.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
|
|
||||||
generate_elem_id!(
|
generate_elem_id!(
|
||||||
/// The name of a certificate.
|
/// The name of a certificate.
|
||||||
|
|
@ -19,7 +19,7 @@ generate_element!(
|
||||||
Cert, "x509cert", SASL_CERT,
|
Cert, "x509cert", SASL_CERT,
|
||||||
text: (
|
text: (
|
||||||
/// The BER X.509 data.
|
/// The BER X.509 data.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::helpers::PlainText;
|
use crate::util::text_node_codecs::{Codec, OptionalCodec, Text};
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ generate_element!(
|
||||||
///
|
///
|
||||||
/// If None, it is the successful reply from the server, the stream is now
|
/// If None, it is the successful reply from the server, the stream is now
|
||||||
/// fully established and both sides can now exchange stanzas.
|
/// fully established and both sides can now exchange stanzas.
|
||||||
data: PlainText<Option<String>>
|
data: OptionalCodec<Text>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::presence::PresencePayload;
|
use crate::presence::PresencePayload;
|
||||||
use crate::util::helpers::PlainText;
|
use crate::util::text_node_codecs::{Codec, OptionalCodec, Text};
|
||||||
use jid::Jid;
|
use jid::Jid;
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -22,7 +22,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// The optional reason this message got delayed.
|
/// The optional reason this message got delayed.
|
||||||
data: PlainText<Option<String>>
|
data: OptionalCodec<Text>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
|
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
|
||||||
use minidom::IntoAttributeValue;
|
use minidom::IntoAttributeValue;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
|
|
@ -105,7 +105,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// The hash value, as a vector of bytes.
|
/// The hash value, as a vector of bytes.
|
||||||
hash: Base64<Vec<u8>>
|
hash: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::iq::IqSetPayload;
|
use crate::iq::IqSetPayload;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
|
|
||||||
generate_id!(
|
generate_id!(
|
||||||
/// An identifier matching a stream.
|
/// An identifier matching a stream.
|
||||||
|
|
@ -53,7 +53,7 @@ Data, "data", IBB,
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// Vector of bytes to be exchanged.
|
/// Vector of bytes to be exchanged.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,14 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::iq::{IqGetPayload, IqResultPayload};
|
use crate::iq::{IqGetPayload, IqResultPayload};
|
||||||
use crate::util::helpers::{JidCodec, Text};
|
use crate::util::text_node_codecs::{Codec, JidCodec, Text};
|
||||||
use jid::Jid;
|
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Request from a client to stringprep/PRECIS a string into a JID.
|
/// Request from a client to stringprep/PRECIS a string into a JID.
|
||||||
JidPrepQuery, "jid", JID_PREP,
|
JidPrepQuery, "jid", JID_PREP,
|
||||||
text: (
|
text: (
|
||||||
/// The potential JID.
|
/// The potential JID.
|
||||||
data: Text<String>
|
data: Text
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -31,7 +30,7 @@ generate_element!(
|
||||||
JidPrepResponse, "jid", JID_PREP,
|
JidPrepResponse, "jid", JID_PREP,
|
||||||
text: (
|
text: (
|
||||||
/// The JID.
|
/// The JID.
|
||||||
jid: JidCodec<Jid>
|
jid: JidCodec
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::hashes::{Algo, Hash};
|
use crate::hashes::{Algo, Hash};
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::ColonSeparatedHex;
|
use crate::util::text_node_codecs::{Codec, ColonSeparatedHex};
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// Indicates which of the end points should initiate the TCP connection establishment.
|
/// Indicates which of the end points should initiate the TCP connection establishment.
|
||||||
|
|
@ -43,7 +43,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// Hash value of this fingerprint.
|
/// Hash value of this fingerprint.
|
||||||
value: ColonSeparatedHex<Vec<u8>>
|
value: ColonSeparatedHex
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::pubsub::PubSubPayload;
|
use crate::pubsub::PubSubPayload;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Element of the device list
|
/// Element of the device list
|
||||||
|
|
@ -39,7 +39,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// Serialized PublicKey
|
/// Serialized PublicKey
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ generate_element!(
|
||||||
SignedPreKeySignature, "signedPreKeySignature", LEGACY_OMEMO,
|
SignedPreKeySignature, "signedPreKeySignature", LEGACY_OMEMO,
|
||||||
text: (
|
text: (
|
||||||
/// Signature bytes
|
/// Signature bytes
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ generate_element!(
|
||||||
IdentityKey, "identityKey", LEGACY_OMEMO,
|
IdentityKey, "identityKey", LEGACY_OMEMO,
|
||||||
text: (
|
text: (
|
||||||
/// Serialized PublicKey
|
/// Serialized PublicKey
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// Serialized PublicKey
|
/// Serialized PublicKey
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ generate_element!(
|
||||||
IV, "iv", LEGACY_OMEMO,
|
IV, "iv", LEGACY_OMEMO,
|
||||||
text: (
|
text: (
|
||||||
/// IV bytes
|
/// IV bytes
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ generate_element!(
|
||||||
/// The 16 bytes key and the GCM authentication tag concatenated together
|
/// The 16 bytes key and the GCM authentication tag concatenated together
|
||||||
/// and encrypted using the corresponding long-standing SignalProtocol
|
/// and encrypted using the corresponding long-standing SignalProtocol
|
||||||
/// session
|
/// session
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ generate_element!(
|
||||||
Payload, "payload", LEGACY_OMEMO,
|
Payload, "payload", LEGACY_OMEMO,
|
||||||
text: (
|
text: (
|
||||||
/// Encrypted with AES-128 in Galois/Counter Mode (GCM)
|
/// Encrypted with AES-128 in Galois/Counter Mode (GCM)
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::util::helpers::TrimmedPlainText;
|
use crate::util::text_node_codecs::{Codec, Text, Trimmed};
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Represents an URI used in a media element.
|
/// Represents an URI used in a media element.
|
||||||
|
|
@ -21,7 +21,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// The actual URI contained.
|
/// The actual URI contained.
|
||||||
uri: TrimmedPlainText<String>
|
uri: Trimmed<Text>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -168,7 +168,10 @@ mod tests {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "URI missing in uri.");
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
"The text in the element's text node was empty after trimming."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::date::DateTime;
|
use crate::date::DateTime;
|
||||||
use crate::pubsub::PubSubPayload;
|
use crate::pubsub::PubSubPayload;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
|
|
||||||
// TODO: Merge this container with the PubKey struct
|
// TODO: Merge this container with the PubKey struct
|
||||||
generate_element!(
|
generate_element!(
|
||||||
|
|
@ -14,7 +14,7 @@ generate_element!(
|
||||||
PubKeyData, "data", OX,
|
PubKeyData, "data", OX,
|
||||||
text: (
|
text: (
|
||||||
/// Base64 data
|
/// Base64 data
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use crate::message::MessagePayload;
|
use crate::message::MessagePayload;
|
||||||
use crate::util::helpers::Text;
|
use crate::util::text_node_codecs::{Codec, Text};
|
||||||
|
|
||||||
generate_element!(
|
generate_element!(
|
||||||
/// Container for a set of reactions.
|
/// Container for a set of reactions.
|
||||||
|
|
@ -27,7 +27,7 @@ generate_element!(
|
||||||
Reaction, "reaction", REACTIONS,
|
Reaction, "reaction", REACTIONS,
|
||||||
text: (
|
text: (
|
||||||
/// The text of this reaction.
|
/// The text of this reaction.
|
||||||
emoji: Text<String>
|
emoji: Text
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::PlainText;
|
use crate::util::text_node_codecs::{Codec, OptionalCodec, Text};
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
|
|
@ -39,7 +39,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// Text to insert.
|
/// Text to insert.
|
||||||
text: PlainText<Option<String>>
|
text: OptionalCodec<Text>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::util::error::Error;
|
use crate::util::error::Error;
|
||||||
use crate::util::helpers::Base64;
|
use crate::util::text_node_codecs::{Base64, Codec};
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ generate_element!(
|
||||||
],
|
],
|
||||||
text: (
|
text: (
|
||||||
/// The content of the handshake.
|
/// The content of the handshake.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ generate_element!(
|
||||||
Challenge, "challenge", SASL,
|
Challenge, "challenge", SASL,
|
||||||
text: (
|
text: (
|
||||||
/// The challenge data.
|
/// The challenge data.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ generate_element!(
|
||||||
Response, "response", SASL,
|
Response, "response", SASL,
|
||||||
text: (
|
text: (
|
||||||
/// The response data.
|
/// The response data.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ generate_element!(
|
||||||
Success, "success", SASL,
|
Success, "success", SASL,
|
||||||
text: (
|
text: (
|
||||||
/// Possible data sent on success.
|
/// Possible data sent on success.
|
||||||
data: Base64<Vec<u8>>
|
data: Base64
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
use crate::util::error::Error;
|
|
||||||
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
|
|
||||||
use jid::Jid;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// Codec for text content.
|
|
||||||
pub struct Text;
|
|
||||||
|
|
||||||
impl Text {
|
|
||||||
pub fn decode(s: &str) -> Result<String, Error> {
|
|
||||||
Ok(s.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(string: &str) -> Option<String> {
|
|
||||||
Some(string.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for plain text content.
|
|
||||||
pub struct PlainText;
|
|
||||||
|
|
||||||
impl PlainText {
|
|
||||||
pub fn decode(s: &str) -> Result<Option<String>, Error> {
|
|
||||||
Ok(match s {
|
|
||||||
"" => None,
|
|
||||||
text => Some(text.to_owned()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(string: &Option<String>) -> Option<String> {
|
|
||||||
string.as_ref().map(ToOwned::to_owned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for trimmed plain text content.
|
|
||||||
pub struct TrimmedPlainText;
|
|
||||||
|
|
||||||
impl TrimmedPlainText {
|
|
||||||
pub fn decode(s: &str) -> Result<String, Error> {
|
|
||||||
Ok(match s.trim() {
|
|
||||||
"" => return Err(Error::ParseError("URI missing in uri.")),
|
|
||||||
text => text.to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(string: &str) -> Option<String> {
|
|
||||||
Some(string.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec wrapping base64 encode/decode.
|
|
||||||
pub struct Base64;
|
|
||||||
|
|
||||||
impl Base64 {
|
|
||||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
|
||||||
Ok(Base64Engine.decode(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(b: &[u8]) -> Option<String> {
|
|
||||||
Some(Base64Engine.encode(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec wrapping base64 encode/decode, while ignoring whitespace characters.
|
|
||||||
pub struct WhitespaceAwareBase64;
|
|
||||||
|
|
||||||
impl WhitespaceAwareBase64 {
|
|
||||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
|
||||||
let s: String = s
|
|
||||||
.chars()
|
|
||||||
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
|
|
||||||
.collect();
|
|
||||||
Ok(Base64Engine.decode(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(b: &[u8]) -> Option<String> {
|
|
||||||
Some(Base64Engine.encode(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for bytes of lowercase hexadecimal.
|
|
||||||
pub struct Hex;
|
|
||||||
|
|
||||||
impl Hex {
|
|
||||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
|
||||||
let mut bytes = Vec::with_capacity(s.len() / 2);
|
|
||||||
for i in 0..s.len() / 2 {
|
|
||||||
bytes.push(u8::from_str_radix(&s[2 * i..2 * i + 2], 16)?);
|
|
||||||
}
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(b: &[u8]) -> Option<String> {
|
|
||||||
let mut bytes = String::with_capacity(b.len() * 2);
|
|
||||||
for byte in b {
|
|
||||||
bytes.extend(format!("{:02x}", byte).chars());
|
|
||||||
}
|
|
||||||
Some(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for colon-separated bytes of uppercase hexadecimal.
|
|
||||||
pub struct ColonSeparatedHex;
|
|
||||||
|
|
||||||
impl ColonSeparatedHex {
|
|
||||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
|
||||||
let mut bytes = vec![];
|
|
||||||
for i in 0..(1 + s.len()) / 3 {
|
|
||||||
let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
|
|
||||||
if 3 * i + 2 < s.len() {
|
|
||||||
assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
|
|
||||||
}
|
|
||||||
bytes.push(byte);
|
|
||||||
}
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(b: &[u8]) -> Option<String> {
|
|
||||||
let mut bytes = vec![];
|
|
||||||
for byte in b {
|
|
||||||
bytes.push(format!("{:02X}", byte));
|
|
||||||
}
|
|
||||||
Some(bytes.join(":"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for a JID.
|
|
||||||
pub struct JidCodec;
|
|
||||||
|
|
||||||
impl JidCodec {
|
|
||||||
pub fn decode(s: &str) -> Result<Jid, Error> {
|
|
||||||
Ok(Jid::from_str(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(jid: &Jid) -> Option<String> {
|
|
||||||
Some(jid.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hex() {
|
|
||||||
let value = [0x01, 0xfe, 0xef];
|
|
||||||
|
|
||||||
// Test that we support both lowercase and uppercase as input.
|
|
||||||
let hex = Hex::decode("01feEF").unwrap();
|
|
||||||
assert_eq!(hex, &value);
|
|
||||||
|
|
||||||
// Test that we do output lowercase.
|
|
||||||
let hex = Hex::encode(&value).unwrap();
|
|
||||||
assert_eq!(hex, "01feef");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bad_hex() {
|
|
||||||
// No colon supported.
|
|
||||||
Hex::decode("01:fe:EF").unwrap_err();
|
|
||||||
|
|
||||||
// No non-hex character allowed.
|
|
||||||
Hex::decode("01defg").unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -618,13 +618,13 @@ macro_rules! generate_element {
|
||||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),+$(,)?]) => (
|
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),+$(,)?]) => (
|
||||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
|
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
|
||||||
);
|
);
|
||||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
|
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ty )) => (
|
||||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
|
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec));
|
||||||
);
|
);
|
||||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+$(,)?], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
|
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+$(,)?], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ty )) => (
|
||||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
|
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec));
|
||||||
);
|
);
|
||||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*$(,)?], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*$(,)?] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >))*) => (
|
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*$(,)?], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*$(,)?] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ty ))*) => (
|
||||||
$(#[$meta])*
|
$(#[$meta])*
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct $elem {
|
pub struct $elem {
|
||||||
|
|
@ -638,7 +638,7 @@ macro_rules! generate_element {
|
||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
$(#[$text_meta])*
|
$(#[$text_meta])*
|
||||||
pub $text_ident: $text_type,
|
pub $text_ident: <$codec as Codec>::Decoded,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -668,7 +668,7 @@ macro_rules! generate_element {
|
||||||
$child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
|
$child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
|
||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
$text_ident: $codec::decode(&elem.text())?,
|
$text_ident: <$codec>::decode(&elem.text())?,
|
||||||
)*
|
)*
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -684,7 +684,7 @@ macro_rules! generate_element {
|
||||||
builder = generate_serialiser!(builder, elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns));
|
builder = generate_serialiser!(builder, elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns));
|
||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
builder = builder.append_all($codec::encode(&elem.$text_ident).map(::minidom::Node::Text).into_iter());
|
builder = builder.append_all(<$codec>::encode(&elem.$text_ident).map(::minidom::Node::Text).into_iter());
|
||||||
)*
|
)*
|
||||||
|
|
||||||
builder.build()
|
builder.build()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
/// Various helpers.
|
/// Various helpers.
|
||||||
pub(crate) mod helpers;
|
pub(crate) mod text_node_codecs;
|
||||||
|
|
||||||
/// Helper macros to parse and serialise more easily.
|
/// Helper macros to parse and serialise more easily.
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
||||||
232
parsers/src/util/text_node_codecs.rs
Normal file
232
parsers/src/util/text_node_codecs.rs
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use crate::util::error::Error;
|
||||||
|
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
|
||||||
|
use jid::Jid;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// A trait for codecs that can decode and encode text nodes.
|
||||||
|
pub trait Codec {
|
||||||
|
type Decoded;
|
||||||
|
|
||||||
|
/// Decode the given string into the codec’s output.
|
||||||
|
fn decode(s: &str) -> Result<Self::Decoded, Error>;
|
||||||
|
|
||||||
|
/// Encode the given value; return None to not produce a text node at all.
|
||||||
|
fn encode(decoded: &Self::Decoded) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec for text content.
|
||||||
|
pub struct Text;
|
||||||
|
|
||||||
|
impl Codec for Text {
|
||||||
|
type Decoded = String;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<String, Error> {
|
||||||
|
Ok(s.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &String) -> Option<String> {
|
||||||
|
Some(decoded.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec transformer that makes the text optional; a "" string is decoded as None.
|
||||||
|
pub struct OptionalCodec<T: Codec>(std::marker::PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> Codec for OptionalCodec<T>
|
||||||
|
where
|
||||||
|
T: Codec,
|
||||||
|
{
|
||||||
|
type Decoded = Option<T::Decoded>;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Option<T::Decoded>, Error> {
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(T::decode(s)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &Option<T::Decoded>) -> Option<String> {
|
||||||
|
decoded.as_ref().and_then(T::encode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec that trims whitespace around the text.
|
||||||
|
pub struct Trimmed<T: Codec>(std::marker::PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> Codec for Trimmed<T>
|
||||||
|
where
|
||||||
|
T: Codec,
|
||||||
|
{
|
||||||
|
type Decoded = T::Decoded;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<T::Decoded, Error> {
|
||||||
|
match s.trim() {
|
||||||
|
// TODO: This error message can be a bit opaque when used
|
||||||
|
// in-context; ideally it'd be configurable.
|
||||||
|
"" => Err(Error::ParseError(
|
||||||
|
"The text in the element's text node was empty after trimming.",
|
||||||
|
)),
|
||||||
|
trimmed => T::decode(trimmed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &T::Decoded) -> Option<String> {
|
||||||
|
T::encode(decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec wrapping that encodes/decodes a string as base64.
|
||||||
|
pub struct Base64;
|
||||||
|
|
||||||
|
impl Codec for Base64 {
|
||||||
|
type Decoded = Vec<u8>;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
Ok(Base64Engine.decode(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &Vec<u8>) -> Option<String> {
|
||||||
|
Some(Base64Engine.encode(decoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec wrapping base64 encode/decode, while ignoring whitespace characters.
|
||||||
|
pub struct WhitespaceAwareBase64;
|
||||||
|
|
||||||
|
impl Codec for WhitespaceAwareBase64 {
|
||||||
|
type Decoded = Vec<u8>;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Self::Decoded, Error> {
|
||||||
|
let s: String = s
|
||||||
|
.chars()
|
||||||
|
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Base64Engine.decode(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &Self::Decoded) -> Option<String> {
|
||||||
|
Some(Base64Engine.encode(decoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec for bytes of lowercase hexadecimal, with a fixed length `N` (in bytes).
|
||||||
|
pub struct FixedHex<const N: usize>;
|
||||||
|
|
||||||
|
impl<const N: usize> Codec for FixedHex<N> {
|
||||||
|
type Decoded = [u8; N];
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Self::Decoded, Error> {
|
||||||
|
if s.len() != 2 * N {
|
||||||
|
return Err(Error::ParseError("Invalid length"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bytes = [0u8; N];
|
||||||
|
for i in 0..N {
|
||||||
|
bytes[i] = u8::from_str_radix(&s[2 * i..2 * i + 2], 16)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &Self::Decoded) -> Option<String> {
|
||||||
|
let mut bytes = String::with_capacity(N * 2);
|
||||||
|
for byte in decoded {
|
||||||
|
bytes.extend(format!("{:02x}", byte).chars());
|
||||||
|
}
|
||||||
|
Some(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec for colon-separated bytes of uppercase hexadecimal.
|
||||||
|
pub struct ColonSeparatedHex;
|
||||||
|
|
||||||
|
impl Codec for ColonSeparatedHex {
|
||||||
|
type Decoded = Vec<u8>;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Self::Decoded, Error> {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
for i in 0..(1 + s.len()) / 3 {
|
||||||
|
let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
|
||||||
|
if 3 * i + 2 < s.len() {
|
||||||
|
assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
|
||||||
|
}
|
||||||
|
bytes.push(byte);
|
||||||
|
}
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(decoded: &Self::Decoded) -> Option<String> {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
for byte in decoded {
|
||||||
|
bytes.push(format!("{:02X}", byte));
|
||||||
|
}
|
||||||
|
Some(bytes.join(":"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codec for a JID.
|
||||||
|
pub struct JidCodec;
|
||||||
|
|
||||||
|
impl Codec for JidCodec {
|
||||||
|
type Decoded = Jid;
|
||||||
|
|
||||||
|
fn decode(s: &str) -> Result<Jid, Error> {
|
||||||
|
Ok(Jid::from_str(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(jid: &Jid) -> Option<String> {
|
||||||
|
Some(jid.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixed_hex() {
|
||||||
|
let value = [0x01, 0xfe, 0xef];
|
||||||
|
|
||||||
|
// Test that we support both lowercase and uppercase as input.
|
||||||
|
let hex = FixedHex::<3>::decode("01feEF").unwrap();
|
||||||
|
assert_eq!(&hex, &value);
|
||||||
|
|
||||||
|
// Test that we do output lowercase.
|
||||||
|
let hex = FixedHex::<3>::encode(&value).unwrap();
|
||||||
|
assert_eq!(hex, "01feef");
|
||||||
|
|
||||||
|
// What if we give it a string that's too long?
|
||||||
|
let err = FixedHex::<3>::decode("01feEF01").unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "parse error: Invalid length");
|
||||||
|
|
||||||
|
// Too short?
|
||||||
|
let err = FixedHex::<3>::decode("01fe").unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "parse error: Invalid length");
|
||||||
|
|
||||||
|
// Not-even numbers?
|
||||||
|
let err = FixedHex::<3>::decode("01feE").unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "parse error: Invalid length");
|
||||||
|
|
||||||
|
// No colon supported.
|
||||||
|
let err = FixedHex::<3>::decode("0:f:EF").unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"integer parsing error: invalid digit found in string"
|
||||||
|
);
|
||||||
|
|
||||||
|
// No non-hex character allowed.
|
||||||
|
let err = FixedHex::<3>::decode("01defg").unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"integer parsing error: invalid digit found in string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue