Cheat Sheet - curl

The examples in this post either show sample requests to example.com for authentication or from example.com to github.com for CORS (cross origin resource sharing).

Tips and tricks

Helpful parameters

Parameter Description
--connect-timeout <seconds> Maximum time in seconds that you allow curl’s connection to take. This only limits the connection phase, so if curl connects within the given period it will continue - if not it will exit.
-s, --silent Don’t show progress meter or error messages
-k, --insecure This option allows curl to proceed and operate even for server connections otherwise considered insecure.
-L, --location Follow redirects.

DNS override

The curl option --resolve helps with querying virtual hosts locally. Instead of

curl -v -H 'Host: www.example.com' http://127.0.0.1

I chose to use

curl -v --resolve 'www.example.com:80:127.0.0.1' http://www.example.com

What’s the difference, you ask?

Among other things, this works with HTTPS. Assuming your local server has a certificate for www.example.com, the first example above will fail because the www.example.com certificate doesn’t match the 127.0.0.1 hostname in the URL.

The second example works correctly with HTTPS.

In essence, passing a “Host” header via -H does hack your Host into the header set, but bypasses all of curl’s host-specific intelligence. Using --resolve leverages all of the normal logic that applies, but simply pretends the DNS lookup returned the data in your command-line option. It works just like /etc/hosts should.

Note --resolve takes a port number, so for HTTPS you would use

curl -v --resolve 'www.example.com:443:127.0.0.1' https://www.example.com/api/vi/status

rather than

curl -v --insecure -H 'Host: www.example.com' https://localhost:443/api/vi/status

This can also be used to test CDNs and redirects:

$ curl -v -L --resolve 'www.example.com:443:mpc.example.com.edgesuite-staging.net' https://www.example.com/gb/en/contact-us
$ curl -v -L -X HEAD -H 'Host: www.example.com' https://mpc.example.com.edgesuite-staging.net

Avoid repetition

If we need to send multiple requests with a set of common parameters we can shorten the commands as follows - see CURL_PARAMS:

USERNAME="john.doe"
API_TOKEN="${PORTUS_USER_API_TOKEN}"
REGISTRY_BASE_URL="https://registry.example.com"
NAMESPACE="someproject"
SERVICE="samplesvc"
 
 
CURL_PARAMS=( -X GET --silent --header 'Accept: application/json' --header "Portus-Auth: ${USERNAME}:${API_TOKEN}" )
 
NAMESPACE_ID=$(curl "${CURL_PARAMS[@]}" "${REGISTRY_BASE_URL}/api/v1/namespaces" | jq ".[] | select(.name == \"${NAMESPACE}\").id")
REPOSITORY_ID=$(curl "${CURL_PARAMS[@]}" "${REGISTRY_BASE_URL}/api/v1/namespaces/${NAMESPACE_ID}/repositories" | jq ".[] | select(.name == \"${SERVICE}\").id")
IMAGE_TAGS=$(curl "${CURL_PARAMS[@]}" "${REGISTRY_BASE_URL}/api/v1/repositories/${REPOSITORY_ID}/tags" | jq 'sort_by(.updated_at) | map(.name)')
 
echo "${IMAGE_TAGS}"

Test CORS headers

Here’s how you can debug CORS requests using curl:

Sending a regular CORS request using curl:

As HEAD request:

$ curl -i https://api.github.com \
    -H "Origin: http://example.com"
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval

Or with GET request:

$ curl -v https://api.github.com \
    -H "Origin: http://example.com"   
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
  • The -H "Origin: http://example.com" flag is the third party domain making the request. Substitute in whatever your domain is.
  • The --verbose flag prints out the entire response so you can see the request and response headers.
  • The response should include the Access-Control-Allow-Origin header.

Sending a preflight request using curl:

In general a server must respond to OPTIONS requests with a 2xx success status — typically 200 or 204. A CORS preflight request requires a 204 No Content response.

This is what the CORS preflight request looks like:

$ curl -v -i https://api.github.com \
    -H "Origin: http://example.com" \
    -X OPTIONS
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-GitHub-OTP, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Max-Age: 86400

And with more specific request headers:

$ curl -v https://api.github.com \
    -H "Origin: http://example.com" \
    -H "Access-Control-Request-Method: POST" \
    -H "Access-Control-Request-Headers: X-Requested-With" \
    -X OPTIONS

This looks similar to the regular CORS request with a few additions:

  • The -H flags send additional preflight request headers to the server
  • The -X OPTIONS flag indicates that this is an HTTP OPTIONS request.

If the preflight request is successful, the response should include the Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers response headers. If the preflight request was not successful, these headers shouldn’t appear, or the HTTP response won’t be 200.

Send data from file

Instead of directly providing the request body contents as parameter you can also read it from a file. Instead of:

$ curl -X POST -d '{"key1":"value1", "key2":"value2", "array": ["a","b"]}' https://example.com

# or multiline

$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' --data '{
  "key1": "value1",
  "key2": "value2",
  "array": [
    "a",
    "b"
  ]
}' https://example.com

use:

# --data @"${FILEPATH}"
 --data @"$HOME/myinput.json"
 --data @input.json

Basic Auth (base64)

Some requests that rely on Basic authentication require you to pass a base64 encoded string in the format ${username}:${password} to the target:

# encode Linux
#
# options
#   echo
#     -n do not output the trailing newline
#     -e enable interpretation of backslash escapes
#   base64
#     -d, --decode Decode data
#     -w, --wrap=COLS Wrap encoded lines after COLS character (default 76). Use 0 to disable line wrapping
BASE64_CREDENTIAL=$(echo -ne "${BASIC_AUTH_USER}:${BASIC_AUTH_PASSWORD}" | base64 --wrap 0)

# encode macOS
#
# options
#   echo
#     -n do not output the trailing newline
#   base64
#     -d, --decode Decode data
BASE64_CREDENTIAL=$(echo -n "${BASIC_AUTH_USER}:${BASIC_AUTH_PASSWORD}" | base64)

# decode with 
PLAINTEXT_CREDENTIAL=$(echo -n "${BASE64_CREDENTIAL}" | base64 -d)

Alternatively you can use curl’s -u parameter - from the documentation1 page:

-u, –user <user:password>

Specify the user name and password to use for server authentication. Overrides -n, –netrc and –netrc-optional.

If you simply specify the user name, curl will prompt for a password.

The user name and passwords are split up on the first colon, which makes it impossible to use a colon in the user name with this option. The password can, still.

Usage:

$ curl -u "username" https://api.example.com
$ curl -u "username:password" https://api.example.com

Reference

Test HTTP2 connection

$ curl -v --http2 --head --silent --output /dev/null https://example.com
$ curl -v --http2 --head --silent https://example.com > /dev/null

Basic Auth

To send a POST request to an endpoint that requires basic auth use:

$ curl https://example.com/ \
    -H 'Accept: application/json' -H "Content-Type: application/json" \
    -H "Authorization: Basic ${BASE64_CREDENTIAL}" \
    --request POST \
    --data  '{"key1":"value1", "key2":"value2"}'

# or 

$ curl https://example.com/ \
    -H 'Accept: application/json' -H "Content-Type: application/json" \
    --request POST \
    --data  '{"key1":"value1", "key2":"value2"}' \
    -u "${BASIC_AUTH_USER}"

Form actions + Basic Auth

-F, –form <name=content> (HTTP) This lets curl emulate a filled-in form in which a user has pressed the submit button. This causes curl to POST data using the Content-Type multipart/form-data according to RFC 2388. This enables uploading of binary files etc. To force the ‘content’ part to be a file, prefix the file name with an @ sign. To just get the content part from a file, prefix the file name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, while the < makes a text field and just get the contents for that text field from a file.

Upload a file to a service:

$ curl https://crm.example.com/crx/packmgr/service.jsp \
    -u "${BASIC_AUTH_USER}:${BASIC_AUTH_PASSWORD}" \
    -F file=@"${FILEPATH}/${PACKAGE_NAME}.zip" \
    -F name="${PACKAGE_NAME}" \
    -F force=true \
    -F install=false

Change some user settings:

$ curl https://crm.example.com/crx/explorer/ui/setpassword.jsp \
    -u "${BASIC_AUTH_USER}:${BASIC_AUTH_PASSWORD}" \
    -Fplain="${NEW_PASSWORD}" \
    -Fverify="${NEW_PASSWORD}" \
    -Fold="${OLD_PASSWORD}" \
    -FPath="${USER_PATH}" 

Client Cert + Basic Auth

To send a GET request that requires a client certificate in addition to basic auth use:

$ curl -X GET -vvv https://cert.example.com/api/example-svc/v1/status \
    -H 'Accept: application/json' -H 'Content-Type: application/json' \
    -H "X-ApplicationName: Some Application" \
    -H "Authorization: Basic ${BASE64_CREDENTIAL}" \
    --cert /app/certs/example-svc-eu-int-cert.pem \
    --key /app/certs/example-svc-eu-int-key.pem \
    --key-type PEM

First of all you have to get the cert and the key separated from the p12 file. Given you have a example-svc-eu-int-key.pfx file execute:

$ openssl pkcs12 -in example-svc-eu-int.pfx -out example-svc-eu-int-key.pem -nocerts -nodes
$ openssl pkcs12 -in example-svc-eu-int.pfx -out example-svc-eu-int-cert.pem -clcerts -nokeys

Newer versions of curl also support P12 (PFX):

$ curl -X GET -vvv https://cert.example.com/api/example-svc/v1/status \
    -H 'Accept: application/json' -H 'Content-Type: application/json' \
    -H "X-ApplicationName: Some Application" \
    -H "Authorization: Basic ${BASE64_CREDENTIAL}" \
    --cert-type P12 --cert /app/certs/example-svc-eu-int.pfx:${KEY_PASSPHRASE}

OAuth Credentials Grant

Here’s how to request an access token via theOauth2 process called the “Resource owner password credentials grant”. In this example:

  • User credentials should be provided in the body as application/x-www-form-urlencoded, with grant_type=password.
  • A separate HTTP header “x-api-key” should be specified for all requests. This header controls rate limiting and its purpose is not to limit access.
$ curl -X POST https://auth.example.com/v1/oauth/token \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --header 'Accept: application/json' \
    --header "Authorization: Basic ${BASE64_CREDENTIAL}" \
    --header "x-api-key: ${API_KEY}" \
    -d "grant_type=password&username=${USERNAME}&password=${PASSWORD}"

This will give us a response with an access_token that will be used as an HTTP Bearer token in all following requests. It is valid for the number of seconds specified as “expirese”, after which the authentication process needs to be repeated:

{
  "access_token": "280793ec-e123-4595-9fbc-32bea948ac34",
  "token_type": "bearer",
  "expirese": 7199
}

Use as Bearer token:

$ curl -X GET https://api.example.com/example-svc/v1/users/list \
    --header 'Accept: application/json' \
    --header 'Authorization: Bearer 280793ec-e123-4595-9fbc-32bea948ac34' \
    --header "x-api-key: ${API_KEY}"

Auth with Bearer token and API key

curl -X GET https://example.com/api/v1/status \
    --header 'Accept: application/json' \
    --header "Authorization: Bearer ${ACCESS_TOKEN}" \
    --header "x-api-key: ${API_KEY}"
  1. http://curl.haxx.se/docs/manpage.html#–basic