1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_dialog_utils.h"
24
25char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters,
26 NameTransform ntf, const char *prefix,
27 const char *separator, const char *suffix,
28 const char *filt_prefix, const char *filt_separator,
29 const char *filt_suffix, const char *ext_prefix,
30 const char *ext_separator, const char *ext_suffix)
31{
32 char *combined;
33 char *new_combined;
34 char *converted;
35 const char *terminator;
36 size_t new_length;
37 int i;
38
39 if (!filters) {
40 SDL_SetError("Called convert_filters() with NULL filters (SDL bug)");
41 return NULL;
42 }
43
44 combined = SDL_strdup(prefix);
45
46 if (!combined) {
47 return NULL;
48 }
49
50 for (i = 0; i < nfilters; i++) {
51 const SDL_DialogFileFilter *f = &filters[i];
52
53 converted = convert_filter(*f, ntf, filt_prefix, filt_separator,
54 filt_suffix, ext_prefix, ext_separator,
55 ext_suffix);
56
57 if (!converted) {
58 SDL_free(combined);
59 return NULL;
60 }
61
62 terminator = ((i + 1) < nfilters) ? separator : suffix;
63 new_length = SDL_strlen(combined) + SDL_strlen(converted)
64 + SDL_strlen(terminator) + 1;
65
66 new_combined = (char *)SDL_realloc(combined, new_length);
67
68 if (!new_combined) {
69 SDL_free(converted);
70 SDL_free(combined);
71 return NULL;
72 }
73
74 combined = new_combined;
75
76 SDL_strlcat(combined, converted, new_length);
77 SDL_strlcat(combined, terminator, new_length);
78 SDL_free(converted);
79 }
80
81 new_length = SDL_strlen(combined) + SDL_strlen(suffix) + 1;
82
83 new_combined = (char *)SDL_realloc(combined, new_length);
84
85 if (!new_combined) {
86 SDL_free(combined);
87 return NULL;
88 }
89
90 combined = new_combined;
91
92 SDL_strlcat(combined, suffix, new_length);
93
94 return combined;
95}
96
97char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf,
98 const char *prefix, const char *separator,
99 const char *suffix, const char *ext_prefix,
100 const char *ext_separator, const char *ext_suffix)
101{
102 char *converted;
103 char *name_filtered;
104 size_t total_length;
105 char *list;
106
107 list = convert_ext_list(filter.pattern, ext_prefix, ext_separator,
108 ext_suffix);
109
110 if (!list) {
111 return NULL;
112 }
113
114 if (ntf) {
115 name_filtered = ntf(filter.name);
116 } else {
117 // Useless strdup, but easier to read and maintain code this way
118 name_filtered = SDL_strdup(filter.name);
119 }
120
121 if (!name_filtered) {
122 SDL_free(list);
123 return NULL;
124 }
125
126 total_length = SDL_strlen(prefix) + SDL_strlen(name_filtered)
127 + SDL_strlen(separator) + SDL_strlen(list)
128 + SDL_strlen(suffix) + 1;
129
130 converted = (char *) SDL_malloc(total_length);
131
132 if (!converted) {
133 SDL_free(list);
134 SDL_free(name_filtered);
135 return NULL;
136 }
137
138 SDL_snprintf(converted, total_length, "%s%s%s%s%s", prefix, name_filtered,
139 separator, list, suffix);
140
141 SDL_free(list);
142 SDL_free(name_filtered);
143
144 return converted;
145}
146
147char *convert_ext_list(const char *list, const char *prefix,
148 const char *separator, const char *suffix)
149{
150 char *converted;
151 int semicolons;
152 size_t total_length;
153
154 semicolons = 0;
155
156 for (const char *c = list; *c; c++) {
157 semicolons += (*c == ';');
158 }
159
160 total_length =
161 SDL_strlen(list) - semicolons // length of list contents
162 + semicolons * SDL_strlen(separator) // length of separators
163 + SDL_strlen(prefix) + SDL_strlen(suffix) // length of prefix/suffix
164 + 1; // terminating null byte
165
166 converted = (char *) SDL_malloc(total_length);
167
168 if (!converted) {
169 return NULL;
170 }
171
172 *converted = '\0';
173
174 SDL_strlcat(converted, prefix, total_length);
175
176 /* Some platforms may prefer to handle the asterisk manually, but this
177 function offers to handle it for ease of use. */
178 if (SDL_strcmp(list, "*") == 0) {
179 SDL_strlcat(converted, "*", total_length);
180 } else {
181 for (const char *c = list; *c; c++) {
182 if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z')
183 || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_'
184 || *c == '.') {
185 char str[2];
186 str[0] = *c;
187 str[1] = '\0';
188 SDL_strlcat(converted, str, total_length);
189 } else if (*c == ';') {
190 if (c == list || c[-1] == ';') {
191 SDL_SetError("Empty pattern not allowed");
192 SDL_free(converted);
193 return NULL;
194 }
195
196 SDL_strlcat(converted, separator, total_length);
197 } else {
198 SDL_SetError("Invalid character '%c' in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)", *c);
199 SDL_free(converted);
200 return NULL;
201 }
202 }
203 }
204
205 if (list[SDL_strlen(list) - 1] == ';') {
206 SDL_SetError("Empty pattern not allowed");
207 SDL_free(converted);
208 return NULL;
209 }
210
211 SDL_strlcat(converted, suffix, total_length);
212
213 return converted;
214}
215
216const char *validate_filters(const SDL_DialogFileFilter *filters, int nfilters)
217{
218 if (filters) {
219 for (int i = 0; i < nfilters; i++) {
220 const char *msg = validate_list(filters[i].pattern);
221
222 if (msg) {
223 return msg;
224 }
225 }
226 }
227
228 return NULL;
229}
230
231const char *validate_list(const char *list)
232{
233 if (SDL_strcmp(list, "*") == 0) {
234 return NULL;
235 } else {
236 for (const char *c = list; *c; c++) {
237 if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z')
238 || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_'
239 || *c == '.') {
240 continue;
241 } else if (*c == ';') {
242 if (c == list || c[-1] == ';') {
243 return "Empty pattern not allowed";
244 }
245 } else {
246 return "Invalid character in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)";
247 }
248 }
249 }
250
251 if (list[SDL_strlen(list) - 1] == ';') {
252 return "Empty pattern not allowed";
253 }
254
255 return NULL;
256}
257