diff --git a/README.md b/README.md index 4e7d6a8d616..494c1d11b0b 100644 --- a/README.md +++ b/README.md @@ -1262,6 +1262,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/completions/_hurl b/completions/_hurl index 9e62ed4cbeb..541eb8d69e0 100644 --- a/completions/_hurl +++ b/completions/_hurl @@ -34,6 +34,7 @@ _hurl() { '--location-trusted[Follow redirects but allows sending the name + password to all hosts that the site may redirect to]' \ '--from-entry[Execute Hurl file from ENTRY_NUMBER (starting at 1)]: :' \ '*--glob[Specify input files that match the given GLOB. Multiple glob flags may be used]: :' \ + '*(-H --header)'{-H,--header}'[Pass custom header(s) to server]: :' \ '(-0 --http1.0)'{-0,--http1.0}'[Tell Hurl to use HTTP version 1.0]' \ '--http1.1[Tell Hurl to use HTTP version 1.1]' \ '--http2[Tell Hurl to use HTTP version 2]' \ diff --git a/completions/_hurl.ps1 b/completions/_hurl.ps1 index e80f9f87470..568fff40397 100644 --- a/completions/_hurl.ps1 +++ b/completions/_hurl.ps1 @@ -39,6 +39,7 @@ Register-ArgumentCompleter -Native -CommandName 'hurl' -ScriptBlock { [CompletionResult]::new('--location-trusted', 'location-trusted', [CompletionResultType]::ParameterName, 'Follow redirects but allows sending the name + password to all hosts that the site may redirect to') [CompletionResult]::new('--from-entry', 'from-entry', [CompletionResultType]::ParameterName, 'Execute Hurl file from ENTRY_NUMBER (starting at 1)') [CompletionResult]::new('--glob', 'glob', [CompletionResultType]::ParameterName, 'Specify input files that match the given GLOB. Multiple glob flags may be used') + [CompletionResult]::new('--header', 'header', [CompletionResultType]::ParameterName, 'Pass custom header(s) to server') [CompletionResult]::new('--http1.0', 'http1.0', [CompletionResultType]::ParameterName, 'Tell Hurl to use HTTP version 1.0') [CompletionResult]::new('--http1.1', 'http1.1', [CompletionResultType]::ParameterName, 'Tell Hurl to use HTTP version 1.1') [CompletionResult]::new('--http2', 'http2', [CompletionResultType]::ParameterName, 'Tell Hurl to use HTTP version 2') diff --git a/completions/hurl.bash b/completions/hurl.bash index b21be5b3800..da2fc369092 100644 --- a/completions/hurl.bash +++ b/completions/hurl.bash @@ -5,7 +5,7 @@ _hurl() _init_completion || return if [[ $cur == -* ]]; then - COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --curl --delay --error-format --file-root --location --location-trusted --from-entry --glob --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --limit-rate --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur")) + COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --curl --delay --error-format --file-root --location --location-trusted --from-entry --glob --header --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --limit-rate --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur")) return fi diff --git a/completions/hurl.fish b/completions/hurl.fish index 0c933d6a86b..abba20d1dfa 100644 --- a/completions/hurl.fish +++ b/completions/hurl.fish @@ -17,6 +17,7 @@ complete -c hurl -l location -d 'Follow redirects' complete -c hurl -l location-trusted -d 'Follow redirects but allows sending the name + password to all hosts that the site may redirect to' complete -c hurl -l from-entry -d 'Execute Hurl file from ENTRY_NUMBER (starting at 1)' complete -c hurl -l glob -d 'Specify input files that match the given GLOB. Multiple glob flags may be used' +complete -c hurl -l header -d 'Pass custom header(s) to server' complete -c hurl -l http1.0 -d 'Tell Hurl to use HTTP version 1.0' complete -c hurl -l http1.1 -d 'Tell Hurl to use HTTP version 1.1' complete -c hurl -l http2 -d 'Tell Hurl to use HTTP version 2' diff --git a/docs/manual.md b/docs/manual.md index fdbaf2d8c88..a8a4776375d 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -165,6 +165,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/docs/manual/hurl.1 b/docs/manual/hurl.1 index 8a7f2fa1ce4..6cb4dbecf4b 100644 --- a/docs/manual/hurl.1 +++ b/docs/manual/hurl.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "04 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "05 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" .SH NAME hurl - run and test HTTP requests. @@ -237,6 +237,14 @@ However, to avoid your shell accidentally expanding glob patterns before Hurl ha This is a cli-only option. +.IP "-H, --header
" + +Add an extra header to include in information sent. Can be used several times in a command + +Do not add newlines or carriage returns + +This is a cli-only option. + .IP "-0, --http1.0 " Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version. diff --git a/docs/manual/hurl.md b/docs/manual/hurl.md index ba64a71186f..cea5e6a0c74 100644 --- a/docs/manual/hurl.md +++ b/docs/manual/hurl.md @@ -256,6 +256,14 @@ However, to avoid your shell accidentally expanding glob patterns before Hurl ha This is a cli-only option. +### -H, --header
{#header} + +Add an extra header to include in information sent. Can be used several times in a command + +Do not add newlines or carriage returns + +This is a cli-only option. + ### -0, --http1.0 {#http10} Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version. diff --git a/docs/manual/hurlfmt.1 b/docs/manual/hurlfmt.1 index 30656b7b7e0..31a356e0566 100644 --- a/docs/manual/hurlfmt.1 +++ b/docs/manual/hurlfmt.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "04 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "05 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" .SH NAME hurlfmt - format Hurl files diff --git a/docs/spec/options/hurl/header.option b/docs/spec/options/hurl/header.option new file mode 100644 index 00000000000..2a1038ee29d --- /dev/null +++ b/docs/spec/options/hurl/header.option @@ -0,0 +1,12 @@ +name: header +long: header +short: H +value: HEADER +help: Pass custom header(s) to server +help_heading: HTTP options +multi: append +cli_only: true +--- +Add an extra header to include in information sent. Can be used several times in a command + +Do not add newlines or carriage returns diff --git a/integration/hurl/tests_ok/help.out.pattern b/integration/hurl/tests_ok/help.out.pattern index 5cadb7333f3..8dd4a102ccc 100644 --- a/integration/hurl/tests_ok/help.out.pattern +++ b/integration/hurl/tests_ok/help.out.pattern @@ -22,6 +22,8 @@ HTTP options: Maximum time allowed for connection [default: 300] --connect-to For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead + -H, --header
+ Pass custom header(s) to server -0, --http1.0 Tell Hurl to use HTTP version 1.0 --http1.1 diff --git a/packages/hurl/README.md b/packages/hurl/README.md index 354acff480d..ad787f9fa35 100644 --- a/packages/hurl/README.md +++ b/packages/hurl/README.md @@ -1262,6 +1262,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/packages/hurl/src/cli/options/commands.rs b/packages/hurl/src/cli/options/commands.rs index 6a30c656d93..c374f48cfd0 100644 --- a/packages/hurl/src/cli/options/commands.rs +++ b/packages/hurl/src/cli/options/commands.rs @@ -205,6 +205,17 @@ pub fn glob() -> clap::Arg { .action(clap::ArgAction::Append) } +pub fn header() -> clap::Arg { + clap::Arg::new("header") + .long("header") + .short('H') + .value_name("HEADER") + .help("Pass custom header(s) to server") + .help_heading("HTTP options") + .num_args(1) + .action(clap::ArgAction::Append) +} + pub fn http10() -> clap::Arg { clap::Arg::new("http10") .long("http1.0") diff --git a/packages/hurl/src/cli/options/matches.rs b/packages/hurl/src/cli/options/matches.rs index 79fc034d6ff..5daf665555f 100644 --- a/packages/hurl/src/cli/options/matches.rs +++ b/packages/hurl/src/cli/options/matches.rs @@ -155,6 +155,10 @@ pub fn from_entry(arg_matches: &ArgMatches) -> Option { get::(arg_matches, "from_entry").map(|x| x as usize) } +pub fn headers(arg_matches: &ArgMatches) -> Vec { + get_strings(arg_matches, "header").unwrap_or_default() +} + pub fn html_dir(arg_matches: &ArgMatches) -> Result, CliOptionsError> { if let Some(dir) = get::(arg_matches, "report_html") { let path = Path::new(&dir); diff --git a/packages/hurl/src/cli/options/mod.rs b/packages/hurl/src/cli/options/mod.rs index f3917b991fc..c44752d9ab4 100644 --- a/packages/hurl/src/cli/options/mod.rs +++ b/packages/hurl/src/cli/options/mod.rs @@ -61,6 +61,7 @@ pub struct CliOptions { pub follow_location: bool, pub follow_location_trusted: bool, pub from_entry: Option, + pub headers: Vec, pub html_dir: Option, pub http_version: Option, pub ignore_asserts: bool, @@ -177,6 +178,7 @@ pub fn parse() -> Result { .arg(commands::compressed()) .arg(commands::connect_timeout()) .arg(commands::connect_to()) + .arg(commands::header()) .arg(commands::http10()) .arg(commands::http11()) .arg(commands::http2()) @@ -282,6 +284,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result Result version.into(), None => RequestedHttpVersion::default(), @@ -473,6 +478,7 @@ impl CliOptions { .follow_location(follow_location) .follow_location_trusted(follow_location_trusted) .from_entry(from_entry) + .headers(&headers) .http_version(http_version) .ignore_asserts(ignore_asserts) .insecure(insecure) diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index c49db4fb346..73b788aa37e 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -488,7 +488,12 @@ impl Client { self.set_multipart(&request_spec.multipart)?; let request_spec_body = &request_spec.body.bytes(); self.set_body(request_spec_body)?; - let headers_spec = &request_spec.headers; + let options_headers = options + .headers + .iter() + .map(|h| h.as_str()) + .collect::>(); + let headers_spec = &request_spec.headers.aggregate_raw_headers(&options_headers); self.set_headers( headers_spec, request_spec.implicit_content_type.as_deref(), diff --git a/packages/hurl/src/http/curl_cmd.rs b/packages/hurl/src/http/curl_cmd.rs index 0eacb61e66a..b30cd75677f 100644 --- a/packages/hurl/src/http/curl_cmd.rs +++ b/packages/hurl/src/http/curl_cmd.rs @@ -63,7 +63,12 @@ impl CurlCmd { let mut params = method_params(request_spec); args.append(&mut params); - let mut params = headers_params(request_spec); + let options_headers = options + .headers + .iter() + .map(|h| h.as_str()) + .collect::>(); + let mut params = headers_params(request_spec, &options_headers); args.append(&mut params); let mut params = body_params(request_spec, context_dir); @@ -90,11 +95,13 @@ fn method_params(request_spec: &RequestSpec) -> Vec { request_spec.method.curl_args(has_body) } -/// Returns the curl args corresponding to the HTTP headers, from a request spec. -fn headers_params(request_spec: &RequestSpec) -> Vec { +/// Returns the curl args corresponding to the HTTP headers, from a request spec and a list of raw headers from client options. +fn headers_params(request_spec: &RequestSpec, options_headers: &[&str]) -> Vec { let mut args = vec![]; - for header in request_spec.headers.iter() { + let aggregated_headers = request_spec.headers.aggregate_raw_headers(options_headers); + + for header in aggregated_headers.iter() { args.append(&mut header.curl_args()); } @@ -608,6 +615,10 @@ mod tests { cookie_input_file: Some("cookie_file".to_string()), follow_location: true, follow_location_trusted: false, + headers: vec![ + "Test-Header-1: content-1".to_string(), + "Test-Header-2: content-2".to_string(), + ], http_version: RequestedHttpVersion::Http10, insecure: true, ip_resolve: IpResolve::IpV6, @@ -637,6 +648,8 @@ mod tests { assert_eq!( cmd.to_string(), "curl \ + --header 'Test-Header-1: content-1' \ + --header 'Test-Header-2: content-2' \ --compressed \ --connect-timeout 20 \ --connect-to example.com:443:host-47.example.com:443 \ diff --git a/packages/hurl/src/http/headers_helper.rs b/packages/hurl/src/http/headers_helper.rs index 5f4a7ae18e7..a745046792d 100644 --- a/packages/hurl/src/http/headers_helper.rs +++ b/packages/hurl/src/http/headers_helper.rs @@ -64,7 +64,6 @@ impl HeaderVec { /// Aggregates the headers from `self` and `raw_headers` /// /// Returns the aggregated `HeaderVec` - #[allow(dead_code)] pub fn aggregate_raw_headers(&self, raw_headers: &[&str]) -> HeaderVec { let mut headers = self.clone(); let to_aggregate = raw_headers.iter().filter_map(|h| Header::parse(h)); diff --git a/packages/hurl/src/http/options.rs b/packages/hurl/src/http/options.rs index 648c269a5cb..2572d2c0110 100644 --- a/packages/hurl/src/http/options.rs +++ b/packages/hurl/src/http/options.rs @@ -33,6 +33,7 @@ pub struct ClientOptions { pub cookie_input_file: Option, pub follow_location: bool, pub follow_location_trusted: bool, + pub headers: Vec, pub http_version: RequestedHttpVersion, pub insecure: bool, pub ip_resolve: IpResolve, @@ -75,6 +76,7 @@ impl Default for ClientOptions { cookie_input_file: None, follow_location: false, follow_location_trusted: false, + headers: vec![], http_version: RequestedHttpVersion::default(), insecure: false, ip_resolve: IpResolve::default(), diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index 1b887415aba..5ffeffa2fc5 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -219,6 +219,7 @@ impl ClientOptions { cookie_input_file: runner_options.cookie_input_file.clone(), follow_location: runner_options.follow_location, follow_location_trusted: runner_options.follow_location_trusted, + headers: runner_options.headers.clone(), http_version: runner_options.http_version, ip_resolve: runner_options.ip_resolve, max_filesize: runner_options.max_filesize, diff --git a/packages/hurl/src/runner/runner_options.rs b/packages/hurl/src/runner/runner_options.rs index 0d8db1319bb..86981055fca 100644 --- a/packages/hurl/src/runner/runner_options.rs +++ b/packages/hurl/src/runner/runner_options.rs @@ -39,6 +39,7 @@ pub struct RunnerOptionsBuilder { follow_location: bool, follow_location_trusted: bool, from_entry: Option, + headers: Vec, http_version: RequestedHttpVersion, ignore_asserts: bool, insecure: bool, @@ -86,6 +87,7 @@ impl Default for RunnerOptionsBuilder { follow_location: false, follow_location_trusted: false, from_entry: None, + headers: vec![], http_version: RequestedHttpVersion::default(), ignore_asserts: false, insecure: false, @@ -235,6 +237,12 @@ impl RunnerOptionsBuilder { self } + /// Sets additional headers (overrides if a header already exists). + pub fn headers(&mut self, header: &[String]) -> &mut Self { + self.headers = header.to_vec(); + self + } + /// Set requested HTTP version (can be different of the effective HTTP version). pub fn http_version(&mut self, version: RequestedHttpVersion) -> &mut Self { self.http_version = version; @@ -425,6 +433,7 @@ impl RunnerOptionsBuilder { follow_location: self.follow_location, follow_location_trusted: self.follow_location_trusted, from_entry: self.from_entry, + headers: self.headers.clone(), http_version: self.http_version, ignore_asserts: self.ignore_asserts, insecure: self.insecure, @@ -473,6 +482,7 @@ pub struct RunnerOptions { pub(crate) follow_location: bool, pub(crate) follow_location_trusted: bool, pub(crate) from_entry: Option, + pub(crate) headers: Vec, pub(crate) http_version: RequestedHttpVersion, pub(crate) ignore_asserts: bool, pub(crate) ip_resolve: IpResolve,