|
// retoor <retoor@molodetz.nl>
|
|
|
|
#include "bash_repair.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <ctype.h>
|
|
|
|
static char *ensure_shebang(const char *text) {
|
|
if (!text || !*text) return strdup("");
|
|
|
|
// Check if it already has a shebang
|
|
const char *p = text;
|
|
while (*p && isspace((unsigned char)*p)) p++;
|
|
if (strncmp(p, "#!", 2) == 0 || strncmp(p, ": <<", 4) == 0) {
|
|
return strdup(text);
|
|
}
|
|
|
|
// Heuristic: if it has multiple lines, add shebang
|
|
if (strchr(text, '\n')) {
|
|
char *result = malloc(strlen(text) + 32);
|
|
if (!result) return strdup(text);
|
|
strcpy(result, "#!/usr/bin/env bash\n");
|
|
strcat(result, text);
|
|
return result;
|
|
}
|
|
|
|
return strdup(text);
|
|
}
|
|
|
|
static char *normalize_whitespace_and_operators(const char *src) {
|
|
if (!src) return NULL;
|
|
size_t src_len = strlen(src);
|
|
char *result = malloc(src_len * 2 + 1);
|
|
if (!result) return NULL;
|
|
|
|
char *dst = result;
|
|
const char *curr = src;
|
|
|
|
while (*curr) {
|
|
if (*curr == '\r') {
|
|
curr++;
|
|
continue;
|
|
}
|
|
|
|
// Detect operators to normalize spaces around them
|
|
const char *ops[] = {"||", "&&", ">>", "|&", "|", ";", ">", "<", NULL};
|
|
bool matched_op = false;
|
|
for (int i = 0; ops[i]; i++) {
|
|
size_t op_len = strlen(ops[i]);
|
|
if (strncmp(curr, ops[i], op_len) == 0) {
|
|
// Remove preceding spaces in dst if any
|
|
while (dst > result && isspace((unsigned char)*(dst - 1)) && *(dst - 1) != '\n') dst--;
|
|
|
|
if (dst > result && *(dst - 1) != '\n') *dst++ = ' ';
|
|
memcpy(dst, ops[i], op_len);
|
|
dst += op_len;
|
|
curr += op_len;
|
|
|
|
// Skip following spaces in curr
|
|
while (*curr && isspace((unsigned char)*curr) && *curr != '\n') curr++;
|
|
if (*curr && *curr != '\n') *dst++ = ' ';
|
|
|
|
matched_op = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!matched_op) {
|
|
*dst++ = *curr++;
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
|
|
// Second pass to strip trailing spaces on each line
|
|
char *s2 = strdup(result);
|
|
free(result);
|
|
if (!s2) return NULL;
|
|
|
|
char *final = malloc(strlen(s2) + 1);
|
|
char *f_ptr = final;
|
|
char *line = s2;
|
|
while (line && *line) {
|
|
char *next_line = strchr(line, '\n');
|
|
size_t line_len = next_line ? (size_t)(next_line - line) : strlen(line);
|
|
|
|
const char *end = line + line_len - 1;
|
|
while (end >= line && isspace((unsigned char)*end)) end--;
|
|
|
|
size_t new_len = (size_t)(end - line + 1);
|
|
memcpy(f_ptr, line, new_len);
|
|
f_ptr += new_len;
|
|
if (next_line) {
|
|
*f_ptr++ = '\n';
|
|
line = next_line + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
*f_ptr = '\0';
|
|
free(s2);
|
|
return final;
|
|
}
|
|
|
|
static char *fix_line_issues(const char *src) {
|
|
if (!src) return NULL;
|
|
size_t src_len = strlen(src);
|
|
char *result = malloc(src_len * 2 + 1024);
|
|
if (!result) return NULL;
|
|
|
|
char *dst = result;
|
|
const char *line = src;
|
|
|
|
while (line && *line) {
|
|
const char *next_line = strchr(line, '\n');
|
|
size_t line_len = next_line ? (size_t)(next_line - line) : strlen(line);
|
|
|
|
char line_buf[4096];
|
|
if (line_len >= sizeof(line_buf)) line_len = sizeof(line_buf) - 1;
|
|
memcpy(line_buf, line, line_len);
|
|
line_buf[line_len] = '\0';
|
|
|
|
// 1. Tiny Shell Lint (else if -> elif)
|
|
char *else_if = strstr(line_buf, "else if ");
|
|
if (else_if) {
|
|
// Very basic replacement
|
|
memmove(else_if + 4, else_if + 8, strlen(else_if + 8) + 1);
|
|
memcpy(else_if, "elif", 4);
|
|
}
|
|
|
|
// 2. Fix unbalanced quotes
|
|
int single = 0, double_q = 0;
|
|
bool escaped = false;
|
|
for (size_t i = 0; i < strlen(line_buf); i++) {
|
|
if (escaped) { escaped = false; continue; }
|
|
if (line_buf[i] == '\\') escaped = true;
|
|
else if (line_buf[i] == '\'') single++;
|
|
else if (line_buf[i] == '"') double_q++;
|
|
}
|
|
if (single % 2 == 1 && double_q == 0) strcat(line_buf, "'");
|
|
else if (double_q % 2 == 1 && single == 0) strcat(line_buf, "\"");
|
|
|
|
// 3. Fix trailing operators
|
|
size_t cur_len = strlen(line_buf);
|
|
const char *ops[] = {"||", "&&", ">>", "|&", "|", ">", "<", NULL};
|
|
for (int i = 0; ops[i]; i++) {
|
|
size_t op_len = strlen(ops[i]);
|
|
if (cur_len >= op_len) {
|
|
if (strcmp(line_buf + cur_len - op_len, ops[i]) == 0) {
|
|
// Comment it
|
|
char temp[4096];
|
|
strcpy(temp, line_buf);
|
|
temp[cur_len - op_len] = '\0';
|
|
strcat(temp, "# ");
|
|
strcat(temp, ops[i]);
|
|
strcpy(line_buf, temp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Dangerous rm -rf check
|
|
if (strstr(line_buf, "sudo rm -rf /") || strstr(line_buf, "rm -rf / ")) {
|
|
strcpy(dst, "# WARNING: potentially destructive command detected\n");
|
|
dst += strlen(dst);
|
|
}
|
|
|
|
strcpy(dst, line_buf);
|
|
dst += strlen(dst);
|
|
if (next_line) *dst++ = '\n';
|
|
|
|
if (next_line) line = next_line + 1;
|
|
else break;
|
|
}
|
|
*dst = '\0';
|
|
return result;
|
|
}
|
|
|
|
static char *collapse_nested_bash_c(const char *src) {
|
|
if (!src) return NULL;
|
|
// Pattern: bash -c "bash -c '...'")
|
|
// We'll just do a very basic string replacement for common variants
|
|
char *s1 = strdup(src);
|
|
char *patterns[] = {
|
|
"bash -c \"bash -c '",
|
|
"bash -c 'bash -c \"",
|
|
NULL
|
|
};
|
|
|
|
for (int i = 0; patterns[i]; i++) {
|
|
char *p;
|
|
while ((p = strstr(s1, patterns[i]))) {
|
|
// Find closing quotes
|
|
char outer = patterns[i][8]; // " or '
|
|
char inner = patterns[i][18]; // ' or "
|
|
|
|
char *inner_end = strchr(p + 19, inner);
|
|
if (inner_end && *(inner_end + 1) == outer) {
|
|
// We can collapse.
|
|
// Original: [p]bash -c "bash -c 'cmd'"[end]
|
|
// New: [p]bash -c 'cmd'[end]
|
|
size_t cmd_len = (size_t)(inner_end - (p + 19));
|
|
char *new_s = malloc(strlen(s1) + 1);
|
|
size_t prefix_len = (size_t)(p - s1);
|
|
memcpy(new_s, s1, prefix_len);
|
|
char *d = new_s + prefix_len;
|
|
strcpy(d, "bash -c ");
|
|
d += 8;
|
|
*d++ = inner;
|
|
memcpy(d, p + 19, cmd_len);
|
|
d += cmd_len;
|
|
*d++ = inner;
|
|
strcpy(d, inner_end + 2);
|
|
|
|
free(s1);
|
|
s1 = new_s;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return s1;
|
|
}
|
|
|
|
char *bash_repair_command(const char *src) {
|
|
if (!src) return NULL;
|
|
|
|
char *s1 = normalize_whitespace_and_operators(src);
|
|
char *s2 = fix_line_issues(s1);
|
|
free(s1);
|
|
char *s3 = collapse_nested_bash_c(s2);
|
|
free(s2);
|
|
char *s4 = ensure_shebang(s3);
|
|
free(s3);
|
|
|
|
return s4;
|
|
}
|