solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
dotenv.c
1#include "../include/dotenv.h"
2
3#include "../include/str_utils.h"
4
5#include <ctype.h> // for isspace
6#include <errno.h> // for errno
7#include <stdio.h> // for fprintf, stderr, fopen, fgets, fclose
8#include <string.h> // for strlen, strchr, memset
9
15static char* remove_quotes(char* str) {
16 if (str == NULL) {
17 return NULL;
18 }
19
20 size_t len = strlen(str);
21 if (len < 2) {
22 return str;
23 }
24
25 // Check for matching quotes
26 if ((str[0] == '"' && str[len - 1] == '"') || (str[0] == '\'' && str[len - 1] == '\'')) {
27 str[len - 1] = '\0';
28 return str + 1;
29 }
30
31 return str;
32}
33
41static bool interpolate(const char* value, char* result, size_t result_size) {
42 if (value == NULL || result == NULL || result_size == 0) {
43 return false;
44 }
45
46 size_t result_len = 0;
47 const char* ptr = value;
48 memset(result, 0, result_size);
49
50 while (*ptr && result_len < result_size - 1) {
51 // Look for variable interpolation pattern: ${VAR_NAME}
52 if (*ptr == '$' && *(ptr + 1) == '{') {
53 const char* start = ptr + 2;
54 const char* end = strchr(start, '}');
55
56 if (end == NULL) {
57 // Malformed interpolation - copy literal characters
58 fprintf(stderr, "Warning: Unclosed variable reference starting at position %td\n", ptr - value);
59 result[result_len++] = *ptr++;
60 continue;
61 }
62
63 // Extract variable name
64 size_t var_name_len = (size_t)(end - start);
65 if (var_name_len == 0) {
66 // Empty variable name: ${}
67 fprintf(stderr, "Warning: Empty variable name\n");
68 ptr = end + 1;
69 continue;
70 }
71
72 if (var_name_len >= MAX_VAR_NAME_LEN) {
73 fprintf(stderr, "Error: Variable name too long (max %d)\n", MAX_VAR_NAME_LEN - 1);
74 return false;
75 }
76
77 char var_name[MAX_VAR_NAME_LEN] = {0};
78 snprintf(var_name, sizeof(var_name), "%.*s", (int)var_name_len, start);
79
80 // Get variable value from environment
81 const char* var_value = GETENV(var_name);
82 if (var_value != NULL) {
83 size_t var_value_len = strlen(var_value);
84 // Check if we have enough space (account for null terminator)
85 if (result_len + var_value_len >= result_size) {
86 fprintf(stderr, "Error: Result buffer too small for interpolation\n");
87 return false;
88 }
89
90 memcpy(result + result_len, var_value, var_value_len);
91 result_len += var_value_len;
92 } else {
93 fprintf(stderr, "Warning: Environment variable '%s' not found, skipping\n", var_name);
94 }
95
96 ptr = end + 1;
97 } else {
98 // Regular character - copy as-is
99 result[result_len++] = *ptr++;
100 }
101 }
102
103 // Check if we ran out of buffer space
104 if (*ptr != '\0') {
105 fprintf(stderr, "Error: Result buffer too small\n");
106 return false;
107 }
108
109 result[result_len] = '\0';
110 return true;
111}
112
119static bool process_env_pair(char* key, char* value) {
120 if (key == NULL || value == NULL) {
121 return false;
122 }
123
124 // Trim key
125 key = trim_string(key);
126 if (*key == '\0') {
127 fprintf(stderr, "Error: Empty key\n");
128 return false;
129 }
130
131 // Trim and unquote value
132 value = trim_string(value);
133 value = remove_quotes(value);
134
135 // Check if interpolation is needed
136 if (strchr(value, '$') != NULL && strchr(value, '{') != NULL && strchr(value, '}') != NULL) {
137 char interpolated_value[MAX_LINE_LENGTH] = {0};
138 if (!interpolate(value, interpolated_value, sizeof(interpolated_value))) {
139 fprintf(stderr, "Error: Failed to interpolate value for key '%s'\n", key);
140 return false;
141 }
142
143 if (SETENV(key, interpolated_value, 1) != 0) {
144 fprintf(stderr, "Error: Failed to set environment variable '%s': %s\n", key, strerror(errno));
145 return false;
146 }
147 } else {
148 if (SETENV(key, value, 1) != 0) {
149 fprintf(stderr, "Error: Failed to set environment variable '%s': %s\n", key, strerror(errno));
150 return false;
151 }
152 }
153
154 return true;
155}
156
157bool load_dotenv(const char* path) {
158 if (path == NULL) {
159 return false;
160 }
161
162 FILE* file = fopen(path, "r");
163 if (file == NULL) {
164 fprintf(stderr, "Error: Cannot open file '%s': %s\n", path, strerror(errno));
165 return false;
166 }
167
168 char line[MAX_LINE_LENGTH] = {0};
169 size_t line_number = 0;
170 bool had_errors = false;
171
172 while (fgets(line, sizeof(line), file) != NULL) {
173 line_number++;
174
175 // Remove trailing newline
176 size_t len = strlen(line);
177 if (len > 0 && line[len - 1] == '\n') {
178 line[len - 1] = '\0';
179 }
180
181 char* trimmed = trim_string(line);
182
183 // Skip empty lines and comments
184 if (*trimmed == '\0' || *trimmed == '#') {
185 continue;
186 }
187
188 // Find the '=' separator
189 char* equals = strchr(trimmed, '=');
190 if (equals == NULL) {
191 fprintf(stderr, "Warning: Invalid line %zu (no '=' found): %s\n", line_number, trimmed);
192 had_errors = true;
193 continue;
194 }
195
196 // Split into key and value
197 *equals = '\0';
198 char* key = trimmed;
199 char* value = equals + 1;
200
201 if (!process_env_pair(key, value)) {
202 fprintf(stderr, "Warning: Failed to process line %zu\n", line_number);
203 had_errors = true;
204 }
205 }
206
207 if (ferror(file)) {
208 fprintf(stderr, "Error: Failed to read from file '%s'\n", path);
209 fclose(file);
210 return false;
211 }
212
213 fclose(file);
214 return !had_errors;
215}
#define MAX_VAR_NAME_LEN
Definition dotenv.h:23
bool load_dotenv(const char *path)
Definition dotenv.c:157
#define SETENV(name, value, overwrite)
Definition env.h:66
#define GETENV(name)
Definition env.h:35