Don\'t Quote Me

This command sequence is bad in various respects.

    user_email=foo@example.net
    perl -p -e "s/EXAMPLE_MAIL_CONTACT/${user_email}/g" test.yaml

A less bad version is as follows.

    user_email=foo@example.net \
    perl -pe 's/EXAMPLE_MAIL_CONTACT/$ENV{user_email}/g' test.yaml

A simple example may help illustrate one issue. Suppose we have a requirement to replace any instances of DOG in the input with CAT, and that CAT may need to be varied. This requirement is vague, but that is a different discussion. Given the above commands, we might derive two implementations:

    $ x=CAT ; echo DOG | perl -pe "s/DOG/$x/g"
    CAT
    $ echo DOG | x=CAT perl -pe 's/DOG/$ENV{x}/g'
    CAT

These may appear to be equivalent--but are not!

    $ x='$' ; echo DOG | perl -pe "s/DOG/$x/g"
    37525
    $ echo DOG | x='$' perl -pe 's/DOG/$ENV{x}/g'
    $

The problematic version has leaked the contents of a variable. Worse, with suitable inputs it will execute arbitrary code.

    $ ls ooops
    ls: ooops: No such file or directory
    $ x='CAT/;`touch ooops`;s//' ; echo DOG | perl -pe "s/DOG/$x/g"
    CAT
    $ ls ooops
    ooops
    $ x='CAT/;`touch ooops`;s//' ; echo perl -pe "s/DOG/$x/g"
    perl -pe s/DOG/CAT/;`touch ooops`;s///g

This may be problematic should user_email come from somewhere untrusted. It will also be problematic if someone ignorant of how the string is built into perl code inputs a string such as foo@example.net, which obviously includes the @example array to interpolate.

The non-problematic code instead substitutes only the contents of an environment variable; perl code is not built dynamically by the shell.

Oblivious Substitutions Can Be Dangerous

Another option (at the cost of time, complexity, and other drawbacks) would be to use a YAML library to modify test.yml. An advantage is that the substitutions can be better controlled; if test.yaml were ever modified (and how would you know?) to have a key such as EXAMPLE_MAIL_CONTACT_FILE, this new key would be mangled by the simple substitution on EXAMPLE_MAIL_CONTACT. This would not be the case with the substitutions confined to non-key portions of the YAML.

On the other hand, YAML libraries can be complicated and may do undesirable things such as to reorder your carefully organized Ansible configuration stanzas. In that case, I've used simple regex modifications, similar to the above, combined with a very careful review of the diff. This was suitable for a one-time bulk edit of the YAML.

Another option would be to template the YAML; in this case the test.yaml might instead be test.yaml.mm for "my mustache" and then the test.yaml would be built from that.

    $ cpanm Mustache::Simple new
    ...
    $ printf 'DOG {{DOG}}\n' |
      perl -Mnew=Mustache::Simple -0777 \
           -ne 'print $O->render( $_, { DOG => "CAT" } )'
    DOG CAT

A downside is that some template libraries come with a Gorilla, and the rest of the forest, too.

Folks who support the legacy web probably need to worry about HTML Injection attacks (XSS); a good template library should handle those better than simple homegrown substitution code does. Hopefully.

On a mostly unrelated note, I conclude with

cats.mkv

tags #sh #perl #cats