2026-01-16 21:48:27 +00:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2026-01-09 13:46:37 +00:00
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
2026-01-09 23:12:03 +00:00
|
|
|
#include "../utils/ast/ast.h"
|
2026-01-18 01:27:31 +00:00
|
|
|
#include "../utils/hash_map/hash_map.h"
|
|
|
|
|
#include "../utils/string_utils/string_utils.h"
|
|
|
|
|
#include "../utils/vars/vars.h"
|
2026-01-09 13:46:37 +00:00
|
|
|
|
2026-01-16 21:48:27 +00:00
|
|
|
static bool is_var_start_char(char c)
|
|
|
|
|
{
|
|
|
|
|
return isalpha(c) || c == '_';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool is_var_char(char c)
|
|
|
|
|
{
|
|
|
|
|
return isalnum(c) || c == '_';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool is_special_var_char(char c)
|
|
|
|
|
{
|
|
|
|
|
return c == '@' || c == '*' || c == '?' || c == '$' || isdigit(c)
|
|
|
|
|
|| c == '#';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t parse_var_name(char *str, char **res)
|
|
|
|
|
{
|
|
|
|
|
char *brace = NULL;
|
|
|
|
|
size_t i = 1; // skip the '$'
|
|
|
|
|
|
|
|
|
|
if (str[i] == '{' && str[i + 1] != 0 && str[i + 1] != '}')
|
|
|
|
|
{
|
|
|
|
|
if (is_special_var_char(str[i + 1]) && str[i + 2] == '}')
|
|
|
|
|
{
|
|
|
|
|
// Special variable like ${1}, ${?}
|
|
|
|
|
*res = strndup(str + i + 1, 1);
|
|
|
|
|
return 4; // length of ${X}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
brace = str + i;
|
|
|
|
|
i++; // skip the '{'
|
|
|
|
|
}
|
|
|
|
|
else if (is_special_var_char(str[i]))
|
|
|
|
|
{
|
|
|
|
|
*res = strndup(str + i, 1);
|
|
|
|
|
return 2; // length of $X
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_var_start_char(str[i]))
|
|
|
|
|
{
|
|
|
|
|
// Not a valid variable start
|
|
|
|
|
*res = NULL;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (1)
|
|
|
|
|
{
|
|
|
|
|
if (str[i] == '}' && *brace == '{')
|
|
|
|
|
{
|
|
|
|
|
*res = strndup(str + 2, i - 2);
|
|
|
|
|
return i + 1;
|
|
|
|
|
}
|
|
|
|
|
else if (!is_var_char(str[i]))
|
|
|
|
|
{
|
|
|
|
|
if (brace != NULL)
|
|
|
|
|
{
|
|
|
|
|
// Missing closing '}'
|
|
|
|
|
*res = NULL;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*res = strndup(str + 1, i - 1);
|
|
|
|
|
return i;
|
|
|
|
|
}
|
2026-01-09 16:17:38 +00:00
|
|
|
|
2026-01-18 01:27:31 +00:00
|
|
|
static bool expand_var(char **str, size_t pos, const struct hash_map *vars)
|
|
|
|
|
{
|
|
|
|
|
char *var_name = NULL;
|
|
|
|
|
size_t r = parse_var_name(*str + pos, &var_name);
|
|
|
|
|
if (r > 0 && var_name != NULL)
|
|
|
|
|
{
|
|
|
|
|
char *value = get_var_or_env(vars, var_name);
|
|
|
|
|
if (value == NULL)
|
|
|
|
|
// Undefined variable: expand to empty string
|
|
|
|
|
value = "";
|
|
|
|
|
|
|
|
|
|
char *p = insert_into(*str, value, pos, r);
|
|
|
|
|
free(var_name);
|
|
|
|
|
if (p == NULL)
|
|
|
|
|
{
|
|
|
|
|
// error: insertion failed
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
*str = p;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ast_command *expand(struct ast_command *command,
|
|
|
|
|
const struct hash_map *vars)
|
2026-01-09 13:46:37 +00:00
|
|
|
{
|
2026-01-15 18:49:42 +01:00
|
|
|
if (command == NULL)
|
2026-01-09 13:46:37 +00:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
char *str;
|
|
|
|
|
size_t len;
|
2026-01-18 01:27:31 +00:00
|
|
|
bool in_quotes;
|
2026-01-15 18:49:42 +01:00
|
|
|
struct list *l = command->command;
|
2026-01-09 13:46:37 +00:00
|
|
|
|
|
|
|
|
while (l != NULL)
|
|
|
|
|
{
|
|
|
|
|
in_quotes = false;
|
|
|
|
|
str = (char *)l->data;
|
|
|
|
|
len = strlen(str);
|
|
|
|
|
|
2026-01-18 01:27:31 +00:00
|
|
|
for (size_t i = 0; str[i] != 0; i++)
|
2026-01-09 13:46:37 +00:00
|
|
|
{
|
2026-01-18 01:27:31 +00:00
|
|
|
if (str[i] == '\'')
|
2026-01-09 13:46:37 +00:00
|
|
|
{
|
2026-01-16 21:48:27 +00:00
|
|
|
// remove quote
|
2026-01-09 13:46:37 +00:00
|
|
|
in_quotes = !in_quotes;
|
2026-01-18 01:27:31 +00:00
|
|
|
memmove(str + i, str + i + 1, strlen(str + i + 1) + 1);
|
2026-01-16 21:48:27 +00:00
|
|
|
i--;
|
2026-01-09 13:46:37 +00:00
|
|
|
}
|
2026-01-18 01:27:31 +00:00
|
|
|
else if (in_quotes)
|
|
|
|
|
{
|
|
|
|
|
continue; // do nothing
|
|
|
|
|
}
|
2026-01-09 16:17:38 +00:00
|
|
|
else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1]))
|
2026-01-09 13:46:37 +00:00
|
|
|
{
|
2026-01-16 21:48:27 +00:00
|
|
|
// variable expansion
|
2026-01-18 01:27:31 +00:00
|
|
|
bool r = expand_var(&str, i, vars);
|
|
|
|
|
if (r == false || str == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
i--; // -1 because loop will increment i
|
2026-01-09 13:46:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (in_quotes)
|
|
|
|
|
{
|
|
|
|
|
// error: quote not closed
|
2026-01-18 01:27:31 +00:00
|
|
|
fprintf(stderr, "Error: quote not closed in string: %s\n", str);
|
|
|
|
|
return NULL;
|
2026-01-09 13:46:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (len != strlen(str))
|
|
|
|
|
{
|
|
|
|
|
char *new_str = realloc(str, strlen(str) + 1);
|
|
|
|
|
if (new_str == NULL)
|
|
|
|
|
{
|
|
|
|
|
// error: realloc fail
|
2026-01-18 01:27:31 +00:00
|
|
|
return NULL;
|
2026-01-09 13:46:37 +00:00
|
|
|
}
|
|
|
|
|
l->data = new_str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l = l->next;
|
|
|
|
|
}
|
2026-01-15 18:49:42 +01:00
|
|
|
return command;
|
2026-01-09 13:46:37 +00:00
|
|
|
}
|