Coverage Report

Created: 2026-01-22 07:12

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 alloc_size = 0;
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
    /* Allocate buffers with max size needed: path + symlink target + separator */
303
0
    alloc_size = path_len * 2 + 2;
304
305
0
    check_path = (char *)calloc(1, path_len + 1);
306
0
    symlink_target = (char *)calloc(1, path_len + 1);
307
0
    combined = (char *)calloc(1, alloc_size);
308
0
    resolved = (char *)calloc(1, alloc_size);
309
310
0
    if (!check_path || !symlink_target || !combined || !resolved) {
311
0
        err = MZ_MEM_ERROR;
312
0
    }
313
314
    /* Walk through each path component */
315
0
    while (err == MZ_OK && pos < path_len) {
316
        /* Copy separator if present */
317
0
        if (mz_os_is_dir_separator(path[pos])) {
318
0
            check_path[pos] = path[pos];
319
0
            pos++;
320
0
        }
321
322
        /* Copy next path component */
323
0
        while (pos < path_len && !mz_os_is_dir_separator(path[pos])) {
324
0
            check_path[pos] = path[pos];
325
0
            pos++;
326
0
        }
327
0
        check_path[pos] = 0;
328
329
        /* Check if this existing path component is a symlink */
330
0
        if (mz_os_is_symlink(check_path) != MZ_OK)
331
0
            continue;
332
0
        if (mz_os_read_symlink(check_path, symlink_target, (int32_t)(path_len + 1)) != MZ_OK)
333
0
            continue;
334
335
        /* Absolute symlink targets are not allowed */
336
0
        if (mz_os_is_dir_separator(symlink_target[0])) {
337
0
            err = MZ_EXIST_ERROR;
338
0
            break;
339
0
        }
340
341
        /* Find parent directory length by scanning backwards past filename and trailing slashes */
342
0
        parent_len = pos;
343
0
        while (parent_len > 0 && !mz_os_is_dir_separator(check_path[parent_len - 1]))
344
0
            parent_len--;
345
0
        while (parent_len > 0 && mz_os_is_dir_separator(check_path[parent_len - 1]))
346
0
            parent_len--;
347
348
        /* Combine parent + symlink_target */
349
0
        combined[0] = 0;
350
0
        if (parent_len > 0) {
351
0
            strncpy(combined, check_path, parent_len);
352
0
            combined[parent_len] = 0;
353
0
            mz_path_append_slash(combined, (int32_t)alloc_size, MZ_PATH_SLASH_PLATFORM);
354
0
        }
355
0
        strncat(combined, symlink_target, alloc_size - strlen(combined) - 1);
356
357
        /* Resolve the combined path to eliminate .. */
358
0
        if (mz_path_resolve(combined, resolved, (int32_t)alloc_size) != MZ_OK) {
359
0
            err = MZ_EXIST_ERROR;
360
0
            break;
361
0
        }
362
363
        /* Check that resolved path starts with base_path */
364
0
        if (strlen(resolved) < base_len ||
365
0
            strncmp(resolved, base_path, base_len) != 0 ||
366
0
            (resolved[base_len] != 0 && !mz_os_is_dir_separator(resolved[base_len]))) {
367
0
            err = MZ_EXIST_ERROR;
368
0
            break;
369
0
        }
370
0
    }
371
372
0
    free(check_path);
373
0
    free(symlink_target);
374
0
    free(combined);
375
0
    free(resolved);
376
377
0
    return err;
378
0
}
379
380
0
int32_t mz_dir_make(const char *path) {
381
0
    int32_t err = MZ_OK;
382
0
    char *current_dir = NULL;
383
0
    char *match = NULL;
384
0
    char hold = 0;
385
386
0
    if (!*path)
387
0
        return MZ_OK;
388
389
0
    current_dir = strdup(path);
390
0
    if (!current_dir)
391
0
        return MZ_MEM_ERROR;
392
393
0
    mz_path_remove_slash(current_dir);
394
395
0
    err = mz_os_make_dir(current_dir);
396
0
    if (err != MZ_OK) {
397
0
        match = current_dir + 1;
398
0
        while (1) {
399
0
            while (*match != 0 && !mz_os_is_dir_separator(*match))
400
0
                match += 1;
401
0
            hold = *match;
402
0
            *match = 0;
403
404
0
            err = mz_os_make_dir(current_dir);
405
0
            if (err != MZ_OK)
406
0
                break;
407
0
            if (hold == 0)
408
0
                break;
409
410
0
            *match = hold;
411
0
            match += 1;
412
0
        }
413
0
    }
414
415
0
    free(current_dir);
416
0
    return err;
417
0
}
418
419
0
int32_t mz_file_get_crc(const char *path, uint32_t *result_crc) {
420
0
    void *stream = NULL;
421
0
    uint32_t crc32 = 0;
422
0
    int32_t read = 0;
423
0
    int32_t err = MZ_OK;
424
0
    uint8_t buf[16384];
425
426
0
    stream = mz_stream_os_create();
427
0
    if (!stream)
428
0
        return MZ_MEM_ERROR;
429
430
0
    err = mz_stream_os_open(stream, path, MZ_OPEN_MODE_READ);
431
0
    if (err == MZ_OK) {
432
0
        do {
433
0
            read = mz_stream_os_read(stream, buf, sizeof(buf));
434
435
0
            if (read < 0) {
436
0
                err = read;
437
0
                break;
438
0
            }
439
440
0
            crc32 = mz_crypt_crc32_update(crc32, buf, read);
441
0
        } while ((err == MZ_OK) && (read > 0));
442
443
0
        mz_stream_os_close(stream);
444
0
    }
445
446
0
    *result_crc = crc32;
447
448
0
    mz_stream_os_delete(&stream);
449
450
0
    return err;
451
0
}
452
453
/***************************************************************************/