-
-
Save ilyakurdyukov/db5e81b87a7be5ac38813104c111799f to your computer and use it in GitHub Desktop.
#ifndef SAFESYS_H | |
#define SAFESYS_H | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#ifndef SAFESYS_NOMEM | |
#include <errno.h> | |
#define SAFESYS_NOMEM ENOMEM | |
#endif | |
static int safesys(const char *format, ...) { | |
va_list va; size_t size = 0; | |
const char *next, *p, *f, *esc; | |
char *d, *cmd, c; int ret, nquot; | |
esc = "\t\n\r !\"#$&()*;<=>?[\\]^`{|}~"; | |
va_start(va, format); | |
f = format; | |
while ((c = *f++)) if (c == '%') { | |
p = next = va_arg(va, const char*); | |
nquot = 0; | |
while ((c = *p++)) | |
nquot = c == '\'' ? | |
size += nquot > 1 ? 3 : nquot + 1, 0 : | |
nquot + !!strchr(esc, c); | |
size += (nquot > 1 ? 2 : nquot) + (p - next) - 2; | |
} | |
va_end(va); | |
size += f - format; | |
cmd = malloc(size); | |
if (!cmd) return SAFESYS_NOMEM; | |
#define SAFESYS_COPY \ | |
if (nquot > 1) *d++ = '\''; \ | |
while (next < p - 1) { \ | |
c = *next++; \ | |
if (nquot == 1 && strchr(esc, c)) \ | |
*d++ = '\\', nquot = 0; \ | |
*d++ = c; \ | |
} \ | |
if (nquot > 1) *d++ = '\''; | |
d = cmd; | |
va_start(va, format); | |
f = format; | |
while ((c = *f++)) | |
if (c != '%') *d++ = c; | |
else { | |
p = next = va_arg(va, const char*); | |
nquot = 0; | |
while ((c = *p++)) | |
if (c == '\'') { | |
SAFESYS_COPY | |
nquot = 0; next++; | |
*d++ = '\\'; *d++ = '\''; | |
} else nquot += !!strchr(esc, c); | |
SAFESYS_COPY | |
} | |
va_end(va); | |
*d = 0; | |
puts(cmd); | |
ret = system(cmd); | |
free(cmd); | |
return ret; | |
} | |
#endif // SAFESYS_H |
While you might consider a more modal interface, you may wish to offer different types of argument protection:
- back quoting shell unsafe chars in a given arg
- single routes around an given arg
- double quotes around a given arg (say if the arg is something like $FOO)
Minor suggestion:
You might wish to split the code into a safesys.c and a safesys.h to make is easier to link with and to make is less awkward other code to use in more than one .c file.
Again this code is an excellent concept that is, I believe, worthy of refinement.
@lcn2
It's written as a concept because you'll rewrite it anyway.
But there is a working code for escaping single quotes that suits the needs of mkiocccentry.
cd ~/tmp && wc -l -- __unsafe_arg__
is done like this:
safesys("cd ~/tmp && wc -l -- %", __unsafe_arg__);
mkiocccentry does not require double quotes escaping, because if one wants to use $FOO on the command line to invoke mkiocccentry, those variables will be expanded by the shell before mkiocccentry is run.
You might wish to have the code simply return a malloced string that is "safe" and let the caller pass it to system() or popen()
This can be done very simply:
-static int safesys(const char *format, ...) {
+static char* cmdprintf(const char *format, ...) {
- if (!cmd) return SAFESYS_NOMEM;
+ if (!cmd) return cmd;
- puts(cmd);
- ret = system(cmd);
- free(cmd);
- return ret;
+ return cmd;
I just want to keep system() in this gist as an example.
Consider the hypothetical need to execute the following command:
cd ~/tmp && wc -l -- __unsafe_arg__
Here the "unsafe_arg" needs to be escape protected, but the other args are perfectly fine.
Also consider the hypothetical need to execute this command:
ls -ld -- __user_arg__ .. > foo 2> /dev/null
Here the "user_arg .." is a variable number of arguments that need to be escape protected, but the other args are perfectly fine.
etc.
You might consider more modal interface that allows some arguments to be protected while other arguments pass thru unprotected.