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