Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tool: add command line variables #11346

Closed
wants to merge 3 commits into from
Closed

tool: add command line variables #11346

wants to merge 3 commits into from

Conversation

bagder
Copy link
Member

@bagder bagder commented Jun 19, 2023

Support command line variables. Set variables with --variable name=content or --variable name@file (where "file" can be stdin if set to a single dash (-)).

Variable contents can be expanded in option parameters using {{name}}" if the option name is prefixed with --expand-. This gets the contents of the variable name inserted, or a blank if the name does not exist as a variable. Insert {{ verbatim in the string by prefixing it with a backslash, like \{{.

You an access and expand environment variables by first importing them. You can select to either require the environment variable to be set or you can provide a default value in case it is not already set. Plain --variable %name imports the variable called name but exits with an error if that environment variable is not already set. To provide a default value if it is not set, use --variable %name=content or --variable %name@content.

Example: get the USER environment variable into the URL. Fail if USER is not set:

--variable '%USER'
--expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make the variable contents more convenient to use. It can trim leading and trailing white space with trim, it can output the contents as a JSON quoted string with json and it can URL encode the string with urlencode. You apply the function(s) to a variable expansion, add them colon separated to the right side of the variable. They are then executed in a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable called "fix". Make sure that the content is trimmed and percent-encoded sent as POST data. if HOME is not set, use "dummy" as default:

--variable %HOME=dummy
--expand-variable fix@{{HOME}}/.secret
--expand-data "{{fix:trim:urlencode}}"
https://example.com/

Documented. Eight new test cases.

  • Tests
  • Documentation
  • torture test safe
  • build + tests work on all platforms
  • embedded null-byte in content safe
  • positive user feedback

@bagder bagder added cmdline tool feature-window A merge of this requires an open feature window labels Jun 19, 2023
@github-actions github-actions bot added the tests label Jun 19, 2023
@jay
Copy link
Member

jay commented Jun 20, 2023

I think 'encode' or 'urlencode' is easier to understand than 'percent' or %. --expand-data "{{fix:urlencode}}", also wouldn't it be easier if it could all be done in one step

edit:

Percent-encodes the entire contents.

ah, I didn't see that. Why would you percent encode the entire thing? Is there really a need for this type of functionality?

@emanuele6
Copy link
Contributor

emanuele6 commented Jun 20, 2023

@jay percent urlencodes only the value of the variable when expanding it, you use it when you want to a variable as a path component of a URL for example

--variable "foo=$foo" --expand-url 'https://example.org/{{foo:percent}}'

If you don't use :percent you will expand the value of {{foo}} directly potentially adding queries, fragments, slashes, etc

You mostly use bare {{foo}} for passing full values like --expand-user-agent {{foo}} or --expand-url {{foo}}, or for --expand-data '@{{foo}}/file.txt' where you don't need to prepare the value for an injection. Then :percent when you need to inject into a URL, :json when you need inject a compent into a JSON payload, etc

bagder added a commit that referenced this pull request Jun 20, 2023
Support command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable contents can be expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Expand environment variables in addition to curl variables by prefixing
the name with "env:"

Example: get the USER environment variable into the URL:

 --expand-url = "https://example.com/api/{{env:USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"percent". You apply function to a variable expansion, add them colon
separated to the right side of the variable.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --expand-variable fix@{{env:HOME}}/.secret
  --expand-data "{{fix:trim:percent}}"
  https://example.com/

Documented. Six new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
@bagder
Copy link
Member Author

bagder commented Jun 20, 2023

To consider: expanding a variable that contains a null byte. What to do with that null byte if neither json nor percent are applied? Should we cut the content at the null? Seems illogical. Should we replace/encode it with something that can be displayed? If so, with what? A period?

@emanuele6
Copy link
Contributor

emanuele6 commented Jun 20, 2023

Most UNIX shells (with few exceptions, e.g. ksh93 deletes everything after the first NUL) will remove all NUL bytes when reading the output of a command for $(), so "$(printf 'a\0b\0\0\0cdef')" => abcdef; that could be another option, I think it would be less surprising than adding dots to the value.
Bash also prints a warning when a NUL byte is removed this way (at most one warning per expansion) to let the user know it is happening.

Another option is to just fail with a fatal error if a user tries to expand a variable that contains NUL without using a function that escapes, which I honestly don't think is that horrible. Afterall, this can only happen if the user uses something like --variable foo@file (it cannot happen for environment variables or variables set with =), and they uses {{foo}} without :json/:percent which I think would be pretty niche; then if {{foo}} contains NUL in that case, the input is most definitely malformed.

@bagder
Copy link
Member Author

bagder commented Jun 21, 2023

After having slept on it, I believe I now favor the "fail with a fatal error" option. This will be the most helpful thing to users as it makes it immediately apparent what happens, as removing or replacing the null bytes will lead to more hard-to-debug errors to puzzle users.

If we want a replace option, we could instead implement a dedicated function for that purpose.

@eloydegen
Copy link

I think the documentation should include some note about the security risks. If an attacker is able to modify an environment variable it might be possible to exfiltrate data which might otherwise not be possible.

@bagder
Copy link
Member Author

bagder commented Jun 21, 2023

I think the documentation should include some note about the security risks. If an attacker is able to modify an environment variable it might be possible to exfiltrate data which might otherwise not be possible.

Providing clear documentation is of course valuable and a priority. Do you have any proposed wording?

An attacker that can change environment variables for curl is a serious attack already before this, since curl will acknowledge several variables outside of this new feature.

Also, this new feature is only expanding environment variables that is explicitly asked for, which is a reason we add this --expand- prefix thing.

bagder added a commit that referenced this pull request Jun 21, 2023
Support command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable contents can be expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Expand environment variables in addition to curl variables by prefixing
the name with "env:"

Example: get the USER environment variable into the URL:

 --expand-url = "https://example.com/api/{{env:USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"percent". You apply function to a variable expansion, add them colon
separated to the right side of the variable.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --expand-variable fix@{{env:HOME}}/.secret
  --expand-data "{{fix:trim:percent}}"
  https://example.com/

Documented. Eight new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
@bagder
Copy link
Member Author

bagder commented Jun 21, 2023

Me and @emanuele6 brainstormed a bit more on IRC.

  1. What if you want to force an environment variable to be set or
  2. What if you want a default value in case the environment variable is not set?

We came up with this tweak to support both those things, that I think fits rather nicely:

  • remove {{%name}} support, get back to a single name space where we need to "import" environment variables to be able to expand them

Import environment variables like this:

  • --variable %name imports an environment variable, fails with an error if not set. It can then be expanded as normal with {{name}}.
  • --variable %name=default imports an environment variable, or sets it to the default value. The default can then also be read from a file with --variable %name@default
    - name can not be the same name as an existing variable

@emanuele6
Copy link
Contributor

  • name can not be the same name as an existing variable

Personally I think you should be allowed to redefine a variable.

While --variable foo=bar --variable foo=baz is likely a mistake, it is possible that the curlrc is defining with common names like proxy, host or ua, and if a script tries to use --variable proxy= unknowingly, it will fail unexpectedly.

Since variables are only used by the user for --expand- options, and not by curl internally, I think it should be fine to allow redefining them.

@bagder
Copy link
Member Author

bagder commented Jun 21, 2023

I think it should be fine to allow redefining them.

It works for me. I'll amend.

src/var.c Fixed Show fixed Hide fixed
bagder added a commit that referenced this pull request Jun 21, 2023
Support command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable contents can be expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Expand environment variables in addition to curl variables by prefixing
the name with "env:"

Example: get the USER environment variable into the URL:

 --expand-url = "https://example.com/api/{{env:USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"percent". You apply function to a variable expansion, add them colon
separated to the right side of the variable.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --expand-variable fix@{{env:HOME}}/.secret
  --expand-data "{{fix:trim:percent}}"
  https://example.com/

Documented. Eight new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
bagder added a commit that referenced this pull request Jun 22, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"urlencode". You apply function to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
@bagder bagder changed the title tool: add "variable" support tool: add command line variables Jun 22, 2023
@bagder bagder self-assigned this Jun 22, 2023
bagder added a commit that referenced this pull request Jul 9, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"urlencode". You apply function to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
bagder added a commit that referenced this pull request Jul 9, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"urlencode". You apply function to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
bagder added a commit that referenced this pull request Jul 13, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"urlencode". You apply function to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
bagder added a commit that referenced this pull request Jul 21, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", it can output the contents as a JSON
quoted string with "json" and it can URL encode the string with
"urlencode". You apply function to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
bagder added a commit that referenced this pull request Jul 22, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", output the contents as a JSON quoted
string with "json", URL encode it with "url" and base 64 encode it with
"b64". To apply functions to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", output the contents as a JSON quoted
string with "json", URL encode it with "url" and base 64 encode it with
"b64". To apply functions to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:urlencode}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Closes #11346
src/var.c Dismissed Show dismissed Hide dismissed
@bagder
Copy link
Member Author

bagder commented Jul 31, 2023

thanks @jay!

@bagder bagder closed this in 2e160c9 Jul 31, 2023
@bagder bagder deleted the bagder/variables branch July 31, 2023 09:52
ptitSeb pushed a commit to wasix-org/curl that referenced this pull request Sep 25, 2023
Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", output the contents as a JSON quoted
string with "json", URL encode it with "url" and base 64 encode it with
"b64". To apply functions to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:url}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Assisted-by: Jat Satiro
Closes curl#11346
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cmdline tool feature-window A merge of this requires an open feature window tests
Development

Successfully merging this pull request may close these issues.

None yet

5 participants