Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/minizip-ng/mz_os.c
Line
Count
Source
1
/* mz_os.c -- System functions
2
   part of the minizip-ng project
3
4
   Copyright (C) Nathan Moinvaziri
5
     https://github.com/zlib-ng/minizip-ng
6
   Copyright (C) 1998-2010 Gilles Vollant
7
     https://www.winimage.com/zLibDll/minizip.html
8
9
   This program is distributed under the terms of the same license as zlib.
10
   See the accompanying LICENSE file for the full text of the license.
11
*/
12
13
#include "mz.h"
14
#include "mz_crypt.h"
15
#include "mz_os.h"
16
#include "mz_strm.h"
17
#include "mz_strm_os.h"
18
19
#include <ctype.h> /* tolower */
20
#include <string.h>
21
22
/***************************************************************************/
23
24
0
int32_t mz_path_combine(char *path, const char *join, int32_t max_path) {
25
0
    int32_t path_len = 0;
26
27
0
    if (!path || !join || !max_path)
28
0
        return MZ_PARAM_ERROR;
29
30
0
    path_len = (int32_t)strlen(path);
31
32
0
    if (path_len == 0) {
33
0
        strncpy(path, join, max_path - 1);
34
0
        path[max_path - 1] = 0;
35
0
    } else {
36
0
        mz_path_append_slash(path, max_path, MZ_PATH_SLASH_PLATFORM);
37
0
        path_len = (int32_t)strlen(path);
38
0
        if (max_path > path_len)
39
0
            strncat(path, join, max_path - path_len - 1);
40
0
    }
41
42
0
    return MZ_OK;
43
0
}
44
45
0
int32_t mz_path_append_slash(char *path, int32_t max_path, char slash) {
46
0
    int32_t path_len = (int32_t)strlen(path);
47
0
    if ((path_len + 2) >= max_path)
48
0
        return MZ_BUF_ERROR;
49
0
    if (!mz_os_is_dir_separator(path[path_len - 1])) {
50
0
        path[path_len] = slash;
51
0
        path[path_len + 1] = 0;
52
0
    }
53
0
    return MZ_OK;
54
0
}
55
56
0
int32_t mz_path_remove_slash(char *path) {
57
0
    int32_t path_len = (int32_t)strlen(path);
58
0
    while (path_len > 0) {
59
0
        if (mz_os_is_dir_separator(path[path_len - 1]))
60
0
            path[path_len - 1] = 0;
61
0
        else
62
0
            break;
63
64
0
        path_len -= 1;
65
0
    }
66
0
    return MZ_OK;
67
0
}
68
69
0
int32_t mz_path_has_slash(const char *path) {
70
0
    int32_t path_len = (int32_t)strlen(path);
71
0
    if (path_len > 0 && !mz_os_is_dir_separator(path[path_len - 1]))
72
0
        return MZ_EXIST_ERROR;
73
0
    return MZ_OK;
74
0
}
75
76
0
int32_t mz_path_convert_slashes(char *path, char slash) {
77
0
    int32_t i = 0;
78
79
0
    for (i = 0; i < (int32_t)strlen(path); i += 1) {
80
0
        if (mz_os_is_dir_separator(path[i]))
81
0
            path[i] = slash;
82
0
    }
83
0
    return MZ_OK;
84
0
}
85
86
0
int32_t mz_path_compare_wc(const char *path, const char *wildcard, uint8_t ignore_case) {
87
0
    while (*path != 0) {
88
0
        switch (*wildcard) {
89
0
        case '*':
90
91
0
            if (*(wildcard + 1) == 0)
92
0
                return MZ_OK;
93
94
0
            while (*path != 0) {
95
0
                if (mz_path_compare_wc(path, (wildcard + 1), ignore_case) == MZ_OK)
96
0
                    return MZ_OK;
97
98
0
                path += 1;
99
0
            }
100
101
0
            return MZ_EXIST_ERROR;
102
103
0
        default:
104
            /* Ignore differences in path slashes on platforms */
105
0
            if ((*path == '\\' && *wildcard == '/') || (*path == '/' && *wildcard == '\\'))
106
0
                break;
107
108
0
            if (ignore_case) {
109
0
                if (tolower(*path) != tolower(*wildcard))
110
0
                    return MZ_EXIST_ERROR;
111
0
            } else {
112
0
                if (*path != *wildcard)
113
0
                    return MZ_EXIST_ERROR;
114
0
            }
115
116
0
            break;
117
0
        }
118
119
0
        path += 1;
120
0
        wildcard += 1;
121
0
    }
122
123
0
    if ((*wildcard != 0) && (*wildcard != '*'))
124
0
        return MZ_EXIST_ERROR;
125
126
0
    return MZ_OK;
127
0
}
128
129
0
int32_t mz_path_resolve(const char *path, char *output, int32_t max_output) {
130
0
    const char *source = path;
131
0
    const char *check = output;
132
0
    char *target = output;
133
134
0
    if (max_output <= 0)
135
0
        return MZ_PARAM_ERROR;
136
137
0
    while (*source != 0 && max_output > 1) {
138
0
        check = source;
139
0
        if (mz_os_is_dir_separator(*check))
140
0
            check += 1;
141
142
0
        if (source == path || target == output || check != source) {
143
            /* Skip double paths */
144
0
            if (mz_os_is_dir_separator(*check)) {
145
0
                source += 1;
146
0
                continue;
147
0
            }
148
0
            if (*check == '.') {
149
0
                check += 1;
150
151
                /* Remove . if at end of string and not at the beginning */
152
0
                if (*check == 0 && source != path && target != output) {
153
                    /* Copy last slash */
154
0
                    *target = *source;
155
0
                    target += 1;
156
0
                    max_output -= 1;
157
0
                    source += (check - source);
158
0
                    continue;
159
0
                }
160
                /* Remove . if not at end of string */
161
0
                else if (mz_os_is_dir_separator(*check)) {
162
0
                    source += (check - source);
163
                    /* Skip slash if at beginning of string */
164
0
                    if (target == output && *source != 0)
165
0
                        source += 1;
166
0
                    continue;
167
0
                }
168
                /* Go to parent directory .. */
169
0
                else if (*check == '.') {
170
0
                    check += 1;
171
0
                    if (*check == 0 || mz_os_is_dir_separator(*check)) {
172
0
                        source += (check - source);
173
174
                        /* Search backwards for previous slash or the start of the output string */
175
0
                        if (target != output) {
176
0
                            target -= 1;
177
0
                            do {
178
0
                                if (target == output || mz_os_is_dir_separator(*target))
179
0
                                    break;
180
181
0
                                target -= 1;
182
0
                                max_output += 1;
183
0
                            } while (target > output);
184
0
                        }
185
186
0
                        if ((target == output) && *source != 0)
187
0
                            source += 1;
188
0
                        if (mz_os_is_dir_separator(*target) && *source == 0)
189
0
                            target += 1;
190
191
0
                        *target = 0;
192
0
                        continue;
193
0
                    }
194
0
                }
195
0
            }
196
0
        }
197
198
0
        *target = *source;
199
200
0
        source += 1;
201
0
        target += 1;
202
0
        max_output -= 1;
203
0
    }
204
205
0
    *target = 0;
206
207
0
    if (*path == 0)
208
0
        return MZ_INTERNAL_ERROR;
209
210
0
    return MZ_OK;
211
0
}
212
213
0
int32_t mz_path_remove_filename(char *path) {
214
0
    char *path_ptr = NULL;
215
216
0
    if (!path)
217
0
        return MZ_PARAM_ERROR;
218
219
0
    path_ptr = path + strlen(path) - 1;
220
221
0
    while (path_ptr > path) {
222
0
        if (mz_os_is_dir_separator(*path_ptr)) {
223
0
            *path_ptr = 0;
224
0
            break;
225
0
        }
226
227
0
        path_ptr -= 1;
228
0
    }
229
230
0
    if (path_ptr == path)
231
0
        *path_ptr = 0;
232
233
0
    return MZ_OK;
234
0
}
235
236
0
int32_t mz_path_remove_extension(char *path) {
237
0
    char *path_ptr = NULL;
238
239
0
    if (!path)
240
0
        return MZ_PARAM_ERROR;
241
242
0
    path_ptr = path + strlen(path) - 1;
243
244
0
    while (path_ptr > path) {
245
0
        if (mz_os_is_dir_separator(*path_ptr))
246
0
            break;
247
0
        if (*path_ptr == '.') {
248
0
            *path_ptr = 0;
249
0
            break;
250
0
        }
251
252
0
        path_ptr -= 1;
253
0
    }
254
255
0
    if (path_ptr == path)
256
0
        *path_ptr = 0;
257
258
0
    return MZ_OK;
259
0
}
260
261
0
int32_t mz_path_get_filename(const char *path, const char **filename) {
262
0
    const char *match = NULL;
263
264
0
    if (!path || !filename)
265
0
        return MZ_PARAM_ERROR;
266
267
0
    *filename = NULL;
268
269
0
    for (match = path; *match != 0; match += 1) {
270
0
        if (mz_os_is_dir_separator(*match))
271
0
            *filename = match + 1;
272
0
    }
273
274
0
    if (!*filename)
275
0
        return MZ_EXIST_ERROR;
276
277
0
    return MZ_OK;
278
0
}
279
280
0
int32_t mz_dir_has_unsafe_symlink(const char *path, const char *base_path) {
281
0
    char *check_path = NULL;
282
0
    char *symlink_target = NULL;
283
0
    char *combined = NULL;
284
0
    char *resolved = NULL;
285
0
    size_t path_len = 0;
286
0
    size_t base_len = 0;
287
0
    size_t max_path = 1024;
288
0
    size_t parent_len = 0;
289
0
    size_t pos = 0;
290
0
    int32_t err = MZ_OK;
291
292
0
    if (!path || *path == 0 || !base_path)
293
0
        return MZ_PARAM_ERROR;
294
295
0
    path_len = strlen(path);
296
0
    base_len = strlen(base_path);
297
298
    /* Remove trailing slash from base_path for comparison */
299
0
    while (base_len > 0 && mz_os_is_dir_separator(base_path[base_len - 1]))
300
0
        base_len--;
301
302
0
    check_path = (char *)calloc(1, path_len + 1);
303
0
    if (!check_path)
304
0
        return MZ_MEM_ERROR;
305
306
    /* Walk through each path component */
307
0
    while (err == MZ_OK && pos < path_len) {
308
        /* Copy separator if present */
309
0
        if (mz_os_is_dir_separator(path[pos])) {
310
0
            check_path[pos] = path[pos];
311
0
            pos++;
312
0
        }
313
314
        /* Copy next path component */
315
0
        while (pos < path_len && !mz_os_is_dir_separator(path[pos])) {
316
0
            check_path[pos] = path[pos];
317
0
            pos++;
318
0
        }
319
0
        check_path[pos] = 0;
320
321
        /* Check if this existing path component is a symlink */
322
0
        if (mz_os_is_symlink(check_path) != MZ_OK)
323
0
            continue;
324
325
        /* Allocate symlink buffers on first use */
326
0
        if (!symlink_target) {
327
0
            symlink_target = (char *)calloc(1, max_path);
328
0
            combined = (char *)calloc(1, max_path);
329
0
            resolved = (char *)calloc(1, max_path);
330
331
0
            if (!symlink_target || !combined || !resolved) {
332
0
                err = MZ_MEM_ERROR;
333
0
                break;
334
0
            }
335
0
        }
336
337
0
        if (mz_os_read_symlink(check_path, symlink_target, max_path) != MZ_OK) {
338
0
            err = MZ_EXIST_ERROR;
339
0
            break;
340
0
        }
341
342
        /* Absolute symlink targets are not allowed */
343
0
        if (mz_os_is_dir_separator(symlink_target[0])) {
344
0
            err = MZ_EXIST_ERROR;
345
0
            break;
346
0
        }
347
348
        /* Find parent directory length by scanning backwards past filename and trailing slashes */
349
0
        parent_len = pos;
350
0
        while (parent_len > 0 && !mz_os_is_dir_separator(check_path[parent_len - 1]))
351
0
            parent_len--;
352
0
        while (parent_len > 0 && mz_os_is_dir_separator(check_path[parent_len - 1]))
353
0
            parent_len--;
354
355
        /* Combine parent + symlink_target */
356
0
        combined[0] = 0;
357
0
        if (parent_len > 0) {
358
0
            strncpy(combined, check_path, parent_len);
359
0
            combined[parent_len] = 0;
360
0
            mz_path_append_slash(combined, (int32_t)max_path, MZ_PATH_SLASH_PLATFORM);
361
0
        }
362
0
        strncat(combined, symlink_target, max_path - strlen(combined) - 1);
363
364
        /* Resolve the combined path to eliminate .. */
365
0
        if (mz_path_resolve(combined, resolved, (int32_t)max_path) != MZ_OK) {
366
0
            err = MZ_EXIST_ERROR;
367
0
            break;
368
0
        }
369
370
        /* Check that resolved path starts with base_path */
371
0
        if (strlen(resolved) < base_len ||
372
0
            strncmp(resolved, base_path, base_len) != 0 ||
373
0
            (resolved[base_len] != 0 && !mz_os_is_dir_separator(resolved[base_len]))) {
374
0
            err = MZ_EXIST_ERROR;
375
0
            break;
376
0
        }
377
0
    }
378
379
0
    free(check_path);
380
0
    free(symlink_target);
381
0
    free(combined);
382
0
    free(resolved);
383
384
0
    return err;
385
0
}
386
387
0
int32_t mz_dir_make(const char *path) {
388
0
    int32_t err = MZ_OK;
389
0
    char *current_dir = NULL;
390
0
    char *match = NULL;
391
0
    char hold = 0;
392
393
0
    if (!*path)
394
0
        return MZ_OK;
395
396
0
    current_dir = strdup(path);
397
0
    if (!current_dir)
398
0
        return MZ_MEM_ERROR;
399
400
0
    mz_path_remove_slash(current_dir);
401
402
0
    err = mz_os_make_dir(current_dir);
403
0
    if (err != MZ_OK) {
404
0
        match = current_dir + 1;
405
0
        while (1) {
406
0
            while (*match != 0 && !mz_os_is_dir_separator(*match))
407
0
                match += 1;
408
0
            hold = *match;
409
0
            *match = 0;
410
411
0
            err = mz_os_make_dir(current_dir);
412
0
            if (err != MZ_OK)
413
0
                break;
414
0
            if (hold == 0)
415
0
                break;
416
417
0
            *match = hold;
418
0
            match += 1;
419
0
        }
420
0
    }
421
422
0
    free(current_dir);
423
0
    return err;
424
0
}
425
426
0
int32_t mz_file_get_crc(const char *path, uint32_t *result_crc) {
427
0
    void *stream = NULL;
428
0
    uint32_t crc32 = 0;
429
0
    int32_t read = 0;
430
0
    int32_t err = MZ_OK;
431
0
    uint8_t buf[16384];
432
433
0
    stream = mz_stream_os_create();
434
0
    if (!stream)
435
0
        return MZ_MEM_ERROR;
436
437
0
    err = mz_stream_os_open(stream, path, MZ_OPEN_MODE_READ);
438
0
    if (err == MZ_OK) {
439
0
        do {
440
0
            read = mz_stream_os_read(stream, buf, sizeof(buf));
441
442
0
            if (read < 0) {
443
0
                err = read;
444
0
                break;
445
0
            }
446
447
0
            crc32 = mz_crypt_crc32_update(crc32, buf, read);
448
0
        } while ((err == MZ_OK) && (read > 0));
449
450
0
        mz_stream_os_close(stream);
451
0
    }
452
453
0
    *result_crc = crc32;
454
455
0
    mz_stream_os_delete(&stream);
456
457
0
    return err;
458
0
}
459
460
/***************************************************************************/