mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-25 12:05:32 +03:00
This PR adds support for configuring the client cert and private key for establishing a mutual TLS connection to a server.
Example: ``` GET https://localhost:8443/ [Options] cacert: out/HurlCA.crt cert: out/client.crt key: out/client.key HTTP 200 ``` outputs: ``` Request can be run with the following curl command: * curl 'https://localhost:8443/' --cacert out/HurlCA.crt --cert out/client.crt --key out/client.key ```
This commit is contained in:
parent
e2fd4405c3
commit
04ebc958c6
@ -34,6 +34,8 @@ use crate::runner::Value;
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CliOptions {
|
pub struct CliOptions {
|
||||||
pub cacert_file: Option<String>,
|
pub cacert_file: Option<String>,
|
||||||
|
pub client_cert_file: Option<String>,
|
||||||
|
pub client_key_file: Option<String>,
|
||||||
pub color: bool,
|
pub color: bool,
|
||||||
pub compressed: bool,
|
pub compressed: bool,
|
||||||
pub connect_timeout: Duration,
|
pub connect_timeout: Duration,
|
||||||
@ -105,6 +107,21 @@ pub fn app(version: &str) -> Command {
|
|||||||
.help("CA certificate to verify peer against (PEM format)")
|
.help("CA certificate to verify peer against (PEM format)")
|
||||||
.num_args(1)
|
.num_args(1)
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("client_cert_file")
|
||||||
|
.long("cert")
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("Client certificate file and password")
|
||||||
|
.num_args(1)
|
||||||
|
.short('E')
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("client_key_file")
|
||||||
|
.long("key")
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("Private key file name")
|
||||||
|
.num_args(1)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
clap::Arg::new("color")
|
clap::Arg::new("color")
|
||||||
.long("color")
|
.long("color")
|
||||||
@ -378,6 +395,28 @@ pub fn parse_options(matches: &ArgMatches) -> Result<CliOptions, CliError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let client_cert_file = match get::<String>(matches, "client_cert_file") {
|
||||||
|
None => None,
|
||||||
|
Some(filename) => {
|
||||||
|
if !Path::new(&filename).is_file() {
|
||||||
|
let message = format!("File {} does not exist", filename);
|
||||||
|
return Err(CliError { message });
|
||||||
|
} else {
|
||||||
|
Some(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let client_key_file = match get::<String>(matches, "client_key_file") {
|
||||||
|
None => None,
|
||||||
|
Some(filename) => {
|
||||||
|
if !Path::new(&filename).is_file() {
|
||||||
|
let message = format!("File {} does not exist", filename);
|
||||||
|
return Err(CliError { message });
|
||||||
|
} else {
|
||||||
|
Some(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
let color = output_color(matches);
|
let color = output_color(matches);
|
||||||
let compressed = has_flag(matches, "compressed");
|
let compressed = has_flag(matches, "compressed");
|
||||||
let connect_timeout = get::<u64>(matches, "connect_timeout").unwrap();
|
let connect_timeout = get::<u64>(matches, "connect_timeout").unwrap();
|
||||||
@ -450,6 +489,8 @@ pub fn parse_options(matches: &ArgMatches) -> Result<CliOptions, CliError> {
|
|||||||
|
|
||||||
Ok(CliOptions {
|
Ok(CliOptions {
|
||||||
cacert_file,
|
cacert_file,
|
||||||
|
client_cert_file,
|
||||||
|
client_key_file,
|
||||||
color,
|
color,
|
||||||
compressed,
|
compressed,
|
||||||
connect_timeout,
|
connect_timeout,
|
||||||
|
@ -144,6 +144,16 @@ impl Client {
|
|||||||
self.handle.ssl_cert_type("PEM").unwrap();
|
self.handle.ssl_cert_type("PEM").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(client_cert_file) = options.client_cert_file.clone() {
|
||||||
|
self.handle.ssl_cert(client_cert_file).unwrap();
|
||||||
|
self.handle.ssl_cert_type("PEM").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(client_key_file) = options.client_key_file.clone() {
|
||||||
|
self.handle.ssl_key(client_key_file).unwrap();
|
||||||
|
self.handle.ssl_cert_type("PEM").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(proxy) = options.proxy.clone() {
|
if let Some(proxy) = options.proxy.clone() {
|
||||||
self.handle.proxy(proxy.as_str()).unwrap();
|
self.handle.proxy(proxy.as_str()).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ use std::time::Duration;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ClientOptions {
|
pub struct ClientOptions {
|
||||||
pub cacert_file: Option<String>,
|
pub cacert_file: Option<String>,
|
||||||
|
pub client_cert_file: Option<String>,
|
||||||
|
pub client_key_file: Option<String>,
|
||||||
pub follow_location: bool,
|
pub follow_location: bool,
|
||||||
pub max_redirect: Option<usize>,
|
pub max_redirect: Option<usize>,
|
||||||
pub cookie_input_file: Option<String>,
|
pub cookie_input_file: Option<String>,
|
||||||
@ -45,6 +47,8 @@ impl Default for ClientOptions {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ClientOptions {
|
ClientOptions {
|
||||||
cacert_file: None,
|
cacert_file: None,
|
||||||
|
client_cert_file: None,
|
||||||
|
client_key_file: None,
|
||||||
follow_location: false,
|
follow_location: false,
|
||||||
max_redirect: Some(50),
|
max_redirect: Some(50),
|
||||||
cookie_input_file: None,
|
cookie_input_file: None,
|
||||||
@ -71,6 +75,16 @@ impl ClientOptions {
|
|||||||
arguments.push(cacert_file.clone());
|
arguments.push(cacert_file.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref client_cert_file) = self.client_cert_file {
|
||||||
|
arguments.push("--cert".to_string());
|
||||||
|
arguments.push(client_cert_file.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref client_key_file) = self.client_key_file {
|
||||||
|
arguments.push("--key".to_string());
|
||||||
|
arguments.push(client_key_file.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if self.compressed {
|
if self.compressed {
|
||||||
arguments.push("--compressed".to_string());
|
arguments.push("--compressed".to_string());
|
||||||
}
|
}
|
||||||
@ -130,6 +144,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
ClientOptions {
|
ClientOptions {
|
||||||
cacert_file: None,
|
cacert_file: None,
|
||||||
|
client_cert_file: None,
|
||||||
|
client_key_file: None,
|
||||||
follow_location: true,
|
follow_location: true,
|
||||||
max_redirect: Some(10),
|
max_redirect: Some(10),
|
||||||
cookie_input_file: Some("cookie_file".to_string()),
|
cookie_input_file: Some("cookie_file".to_string()),
|
||||||
|
@ -108,6 +108,7 @@ fn execute(
|
|||||||
logger.debug(format!(" fail fast: {}", cli_options.fail_fast).as_str());
|
logger.debug(format!(" fail fast: {}", cli_options.fail_fast).as_str());
|
||||||
logger.debug(format!(" follow redirect: {}", cli_options.follow_location).as_str());
|
logger.debug(format!(" follow redirect: {}", cli_options.follow_location).as_str());
|
||||||
logger.debug(format!(" insecure: {}", cli_options.insecure).as_str());
|
logger.debug(format!(" insecure: {}", cli_options.insecure).as_str());
|
||||||
|
|
||||||
if let Some(n) = cli_options.max_redirect {
|
if let Some(n) = cli_options.max_redirect {
|
||||||
logger.debug(format!(" max redirect: {}", n).as_str());
|
logger.debug(format!(" max redirect: {}", n).as_str());
|
||||||
}
|
}
|
||||||
|
@ -217,6 +217,8 @@ impl From<&RunnerOptions> for ClientOptions {
|
|||||||
fn from(runner_options: &RunnerOptions) -> Self {
|
fn from(runner_options: &RunnerOptions) -> Self {
|
||||||
ClientOptions {
|
ClientOptions {
|
||||||
cacert_file: runner_options.cacert_file.clone(),
|
cacert_file: runner_options.cacert_file.clone(),
|
||||||
|
client_cert_file: runner_options.client_cert_file.clone(),
|
||||||
|
client_key_file: runner_options.client_key_file.clone(),
|
||||||
follow_location: runner_options.follow_location,
|
follow_location: runner_options.follow_location,
|
||||||
max_redirect: runner_options.max_redirect,
|
max_redirect: runner_options.max_redirect,
|
||||||
cookie_input_file: runner_options.cookie_input_file.clone(),
|
cookie_input_file: runner_options.cookie_input_file.clone(),
|
||||||
@ -300,6 +302,14 @@ pub fn get_entry_options(
|
|||||||
runner_options.cacert_file = Some(option.filename.value.clone());
|
runner_options.cacert_file = Some(option.filename.value.clone());
|
||||||
logger.debug(format!("cacert: {}", option.filename.value).as_str());
|
logger.debug(format!("cacert: {}", option.filename.value).as_str());
|
||||||
}
|
}
|
||||||
|
EntryOption::ClientCert(option) => {
|
||||||
|
runner_options.client_cert_file = Some(option.filename.value.clone());
|
||||||
|
logger.debug(format!("cert: {}", option.filename.value).as_str());
|
||||||
|
}
|
||||||
|
EntryOption::ClientKey(option) => {
|
||||||
|
runner_options.client_key_file = Some(option.filename.value.clone());
|
||||||
|
logger.debug(format!("key: {}", option.filename.value).as_str());
|
||||||
|
}
|
||||||
EntryOption::Compressed(option) => {
|
EntryOption::Compressed(option) => {
|
||||||
runner_options.compressed = option.value;
|
runner_options.compressed = option.value;
|
||||||
logger.debug(format!("compressed: {}", option.value).as_str());
|
logger.debug(format!("compressed: {}", option.value).as_str());
|
||||||
|
@ -26,6 +26,8 @@ use std::time::Duration;
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RunnerOptions {
|
pub struct RunnerOptions {
|
||||||
pub cacert_file: Option<String>,
|
pub cacert_file: Option<String>,
|
||||||
|
pub client_cert_file: Option<String>,
|
||||||
|
pub client_key_file: Option<String>,
|
||||||
pub compressed: bool,
|
pub compressed: bool,
|
||||||
pub connect_timeout: Duration,
|
pub connect_timeout: Duration,
|
||||||
pub context_dir: ContextDir,
|
pub context_dir: ContextDir,
|
||||||
@ -54,6 +56,8 @@ impl Default for RunnerOptions {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
RunnerOptions {
|
RunnerOptions {
|
||||||
cacert_file: None,
|
cacert_file: None,
|
||||||
|
client_cert_file: None,
|
||||||
|
client_key_file: None,
|
||||||
compressed: false,
|
compressed: false,
|
||||||
connect_timeout: Duration::from_secs(300),
|
connect_timeout: Duration::from_secs(300),
|
||||||
context_dir: Default::default(),
|
context_dir: Default::default(),
|
||||||
@ -83,6 +87,8 @@ impl Default for RunnerOptions {
|
|||||||
impl RunnerOptions {
|
impl RunnerOptions {
|
||||||
pub fn from(filename: &str, current_dir: &Path, cli_options: &CliOptions) -> Self {
|
pub fn from(filename: &str, current_dir: &Path, cli_options: &CliOptions) -> Self {
|
||||||
let cacert_file = cli_options.cacert_file.clone();
|
let cacert_file = cli_options.cacert_file.clone();
|
||||||
|
let client_cert_file = cli_options.client_cert_file.clone();
|
||||||
|
let client_key_file = cli_options.client_key_file.clone();
|
||||||
let follow_location = cli_options.follow_location;
|
let follow_location = cli_options.follow_location;
|
||||||
let verbosity = match (cli_options.verbose, cli_options.very_verbose) {
|
let verbosity = match (cli_options.verbose, cli_options.very_verbose) {
|
||||||
(true, true) => Some(Verbosity::VeryVerbose),
|
(true, true) => Some(Verbosity::VeryVerbose),
|
||||||
@ -130,6 +136,8 @@ impl RunnerOptions {
|
|||||||
let very_verbose = cli_options.very_verbose;
|
let very_verbose = cli_options.very_verbose;
|
||||||
RunnerOptions {
|
RunnerOptions {
|
||||||
cacert_file,
|
cacert_file,
|
||||||
|
client_cert_file,
|
||||||
|
client_key_file,
|
||||||
compressed,
|
compressed,
|
||||||
connect_timeout,
|
connect_timeout,
|
||||||
context_dir,
|
context_dir,
|
||||||
|
@ -694,6 +694,8 @@ pub struct Variable {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum EntryOption {
|
pub enum EntryOption {
|
||||||
CaCertificate(CaCertificateOption),
|
CaCertificate(CaCertificateOption),
|
||||||
|
ClientCert(ClientCertOption),
|
||||||
|
ClientKey(ClientKeyOption),
|
||||||
Compressed(CompressedOption),
|
Compressed(CompressedOption),
|
||||||
Insecure(InsecureOption),
|
Insecure(InsecureOption),
|
||||||
FollowLocation(FollowLocationOption),
|
FollowLocation(FollowLocationOption),
|
||||||
@ -736,6 +738,26 @@ pub struct CaCertificateOption {
|
|||||||
pub line_terminator0: LineTerminator,
|
pub line_terminator0: LineTerminator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ClientCertOption {
|
||||||
|
pub line_terminators: Vec<LineTerminator>,
|
||||||
|
pub space0: Whitespace,
|
||||||
|
pub space1: Whitespace,
|
||||||
|
pub space2: Whitespace,
|
||||||
|
pub filename: Filename,
|
||||||
|
pub line_terminator0: LineTerminator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ClientKeyOption {
|
||||||
|
pub line_terminators: Vec<LineTerminator>,
|
||||||
|
pub space0: Whitespace,
|
||||||
|
pub space1: Whitespace,
|
||||||
|
pub space2: Whitespace,
|
||||||
|
pub filename: Filename,
|
||||||
|
pub line_terminator0: LineTerminator,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RetryOption {
|
pub struct RetryOption {
|
||||||
pub line_terminators: Vec<LineTerminator>,
|
pub line_terminators: Vec<LineTerminator>,
|
||||||
|
@ -234,6 +234,8 @@ impl Htmlable for EntryOption {
|
|||||||
fn to_html(&self) -> String {
|
fn to_html(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
EntryOption::CaCertificate(option) => option.to_html(),
|
EntryOption::CaCertificate(option) => option.to_html(),
|
||||||
|
EntryOption::ClientCert(option) => option.to_html(),
|
||||||
|
EntryOption::ClientKey(option) => option.to_html(),
|
||||||
EntryOption::Compressed(option) => option.to_html(),
|
EntryOption::Compressed(option) => option.to_html(),
|
||||||
EntryOption::Insecure(option) => option.to_html(),
|
EntryOption::Insecure(option) => option.to_html(),
|
||||||
EntryOption::FollowLocation(option) => option.to_html(),
|
EntryOption::FollowLocation(option) => option.to_html(),
|
||||||
@ -299,6 +301,40 @@ impl Htmlable for CaCertificateOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Htmlable for ClientCertOption {
|
||||||
|
fn to_html(&self) -> String {
|
||||||
|
let mut buffer = String::from("");
|
||||||
|
add_line_terminators(&mut buffer, self.line_terminators.clone());
|
||||||
|
buffer.push_str("<span class=\"line\">");
|
||||||
|
buffer.push_str(self.space0.to_html().as_str());
|
||||||
|
buffer.push_str("<span class=\"string\">cert</span>");
|
||||||
|
buffer.push_str(self.space1.to_html().as_str());
|
||||||
|
buffer.push_str("<span>:</span>");
|
||||||
|
buffer.push_str(self.space2.to_html().as_str());
|
||||||
|
buffer.push_str(self.filename.to_html().as_str());
|
||||||
|
buffer.push_str("</span>");
|
||||||
|
buffer.push_str(self.line_terminator0.to_html().as_str());
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Htmlable for ClientKeyOption {
|
||||||
|
fn to_html(&self) -> String {
|
||||||
|
let mut buffer = String::from("");
|
||||||
|
add_line_terminators(&mut buffer, self.line_terminators.clone());
|
||||||
|
buffer.push_str("<span class=\"line\">");
|
||||||
|
buffer.push_str(self.space0.to_html().as_str());
|
||||||
|
buffer.push_str("<span class=\"string\">key</span>");
|
||||||
|
buffer.push_str(self.space1.to_html().as_str());
|
||||||
|
buffer.push_str("<span>:</span>");
|
||||||
|
buffer.push_str(self.space2.to_html().as_str());
|
||||||
|
buffer.push_str(self.filename.to_html().as_str());
|
||||||
|
buffer.push_str("</span>");
|
||||||
|
buffer.push_str(self.line_terminator0.to_html().as_str());
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Htmlable for FollowLocationOption {
|
impl Htmlable for FollowLocationOption {
|
||||||
fn to_html(&self) -> String {
|
fn to_html(&self) -> String {
|
||||||
let mut buffer = String::from("");
|
let mut buffer = String::from("");
|
||||||
|
@ -349,6 +349,8 @@ fn option(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
|||||||
choice(
|
choice(
|
||||||
&[
|
&[
|
||||||
option_cacert,
|
option_cacert,
|
||||||
|
option_cert,
|
||||||
|
option_key,
|
||||||
option_compressed,
|
option_compressed,
|
||||||
option_insecure,
|
option_insecure,
|
||||||
option_follow_location,
|
option_follow_location,
|
||||||
@ -386,6 +388,50 @@ fn option_cacert(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
|||||||
Ok(EntryOption::CaCertificate(option))
|
Ok(EntryOption::CaCertificate(option))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn option_cert(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
||||||
|
let line_terminators = optional_line_terminators(reader)?;
|
||||||
|
let space0 = zero_or_more_spaces(reader)?;
|
||||||
|
try_literal("cert", reader)?;
|
||||||
|
let space1 = zero_or_more_spaces(reader)?;
|
||||||
|
try_literal(":", reader)?;
|
||||||
|
let space2 = zero_or_more_spaces(reader)?;
|
||||||
|
let f = filename::parse(reader)?;
|
||||||
|
let line_terminator0 = line_terminator(reader)?;
|
||||||
|
|
||||||
|
let option = ClientCertOption {
|
||||||
|
line_terminators,
|
||||||
|
space0,
|
||||||
|
space1,
|
||||||
|
space2,
|
||||||
|
filename: f,
|
||||||
|
line_terminator0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EntryOption::ClientCert(option))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_key(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
||||||
|
let line_terminators = optional_line_terminators(reader)?;
|
||||||
|
let space0 = zero_or_more_spaces(reader)?;
|
||||||
|
try_literal("key", reader)?;
|
||||||
|
let space1 = zero_or_more_spaces(reader)?;
|
||||||
|
try_literal(":", reader)?;
|
||||||
|
let space2 = zero_or_more_spaces(reader)?;
|
||||||
|
let f = filename::parse(reader)?;
|
||||||
|
let line_terminator0 = line_terminator(reader)?;
|
||||||
|
|
||||||
|
let option = ClientKeyOption {
|
||||||
|
line_terminators,
|
||||||
|
space0,
|
||||||
|
space1,
|
||||||
|
space2,
|
||||||
|
filename: f,
|
||||||
|
line_terminator0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EntryOption::ClientKey(option))
|
||||||
|
}
|
||||||
|
|
||||||
fn option_compressed(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
fn option_compressed(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
|
||||||
let line_terminators = optional_line_terminators(reader)?;
|
let line_terminators = optional_line_terminators(reader)?;
|
||||||
let space0 = zero_or_more_spaces(reader)?;
|
let space0 = zero_or_more_spaces(reader)?;
|
||||||
|
@ -842,6 +842,8 @@ impl Tokenizable for EntryOption {
|
|||||||
fn tokenize(&self) -> Vec<Token> {
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
match self {
|
match self {
|
||||||
EntryOption::CaCertificate(option) => option.tokenize(),
|
EntryOption::CaCertificate(option) => option.tokenize(),
|
||||||
|
EntryOption::ClientCert(option) => option.tokenize(),
|
||||||
|
EntryOption::ClientKey(option) => option.tokenize(),
|
||||||
EntryOption::Compressed(option) => option.tokenize(),
|
EntryOption::Compressed(option) => option.tokenize(),
|
||||||
EntryOption::Insecure(option) => option.tokenize(),
|
EntryOption::Insecure(option) => option.tokenize(),
|
||||||
EntryOption::FollowLocation(option) => option.tokenize(),
|
EntryOption::FollowLocation(option) => option.tokenize(),
|
||||||
@ -877,6 +879,48 @@ impl Tokenizable for CaCertificateOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Tokenizable for ClientCertOption {
|
||||||
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
|
let mut tokens: Vec<Token> = vec![];
|
||||||
|
tokens.append(
|
||||||
|
&mut self
|
||||||
|
.line_terminators
|
||||||
|
.iter()
|
||||||
|
.flat_map(|e| e.tokenize())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
tokens.append(&mut self.space0.tokenize());
|
||||||
|
tokens.push(Token::String("cert".to_string()));
|
||||||
|
tokens.append(&mut self.space1.tokenize());
|
||||||
|
tokens.push(Token::Colon(String::from(":")));
|
||||||
|
tokens.append(&mut self.space2.tokenize());
|
||||||
|
tokens.append(&mut self.filename.tokenize());
|
||||||
|
tokens.append(&mut self.line_terminator0.tokenize());
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tokenizable for ClientKeyOption {
|
||||||
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
|
let mut tokens: Vec<Token> = vec![];
|
||||||
|
tokens.append(
|
||||||
|
&mut self
|
||||||
|
.line_terminators
|
||||||
|
.iter()
|
||||||
|
.flat_map(|e| e.tokenize())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
tokens.append(&mut self.space0.tokenize());
|
||||||
|
tokens.push(Token::String("key".to_string()));
|
||||||
|
tokens.append(&mut self.space1.tokenize());
|
||||||
|
tokens.push(Token::Colon(String::from(":")));
|
||||||
|
tokens.append(&mut self.space2.tokenize());
|
||||||
|
tokens.append(&mut self.filename.tokenize());
|
||||||
|
tokens.append(&mut self.line_terminator0.tokenize());
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tokenizable for CompressedOption {
|
impl Tokenizable for CompressedOption {
|
||||||
fn tokenize(&self) -> Vec<Token> {
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
let mut tokens: Vec<Token> = vec![];
|
let mut tokens: Vec<Token> = vec![];
|
||||||
|
Loading…
Reference in New Issue
Block a user