1 | /* -*- mode: C; c-file-style: "gnu" -*- */ |
2 | /* xdgmimeglob.c: Private file. Datastructure for storing the globs. |
3 | * |
4 | * More info can be found at http://www.freedesktop.org/standards/ |
5 | * |
6 | * Copyright (C) 2003 Red Hat, Inc. |
7 | * Copyright (C) 2003 Jonathan Blandford <[email protected]> |
8 | * |
9 | * Licensed under the Academic Free License version 2.0 |
10 | * Or under the following terms: |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Lesser General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Lesser General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Lesser General Public |
23 | * License along with this library; if not, write to the |
24 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
25 | * Boston, MA 02111-1307, USA. |
26 | */ |
27 | |
28 | #ifdef HAVE_CONFIG_H |
29 | #include "cmakeconfig.h" |
30 | #endif |
31 | |
32 | #include "xdgmimeglob.h" |
33 | #include "xdgmimeint.h" |
34 | #include <stdlib.h> |
35 | #include <stdio.h> |
36 | #include <assert.h> |
37 | #include <string.h> |
38 | #include <fnmatch.h> |
39 | |
40 | #ifndef FALSE |
41 | #define FALSE (0) |
42 | #endif |
43 | |
44 | #ifndef TRUE |
45 | #define TRUE (!FALSE) |
46 | #endif |
47 | |
48 | typedef struct XdgGlobHashNode XdgGlobHashNode; |
49 | typedef struct XdgGlobList XdgGlobList; |
50 | |
51 | struct XdgGlobHashNode |
52 | { |
53 | xdg_unichar_t character; |
54 | const char *mime_type; |
55 | int weight; |
56 | int case_sensitive; |
57 | XdgGlobHashNode *next; |
58 | XdgGlobHashNode *child; |
59 | }; |
60 | struct XdgGlobList |
61 | { |
62 | const char *data; |
63 | const char *mime_type; |
64 | int weight; |
65 | int case_sensitive; |
66 | XdgGlobList *next; |
67 | }; |
68 | |
69 | struct XdgGlobHash |
70 | { |
71 | XdgGlobList *literal_list; |
72 | XdgGlobHashNode *simple_node; |
73 | XdgGlobList *full_list; |
74 | }; |
75 | |
76 | |
77 | /* XdgGlobList |
78 | */ |
79 | static XdgGlobList * |
80 | _xdg_glob_list_new (void) |
81 | { |
82 | XdgGlobList *new_element; |
83 | |
84 | new_element = calloc (1, sizeof (XdgGlobList)); |
85 | |
86 | return new_element; |
87 | } |
88 | |
89 | /* Frees glob_list and all of it's children */ |
90 | static void |
91 | _xdg_glob_list_free (XdgGlobList *glob_list) |
92 | { |
93 | XdgGlobList *ptr, *next; |
94 | |
95 | ptr = glob_list; |
96 | |
97 | while (ptr != NULL) |
98 | { |
99 | next = ptr->next; |
100 | |
101 | if (ptr->data) |
102 | free ((void *) ptr->data); |
103 | if (ptr->mime_type) |
104 | free ((void *) ptr->mime_type); |
105 | free (ptr); |
106 | |
107 | ptr = next; |
108 | } |
109 | } |
110 | |
111 | static XdgGlobList * |
112 | _xdg_glob_list_append (XdgGlobList *glob_list, |
113 | void *data, |
114 | const char *mime_type, |
115 | int weight, |
116 | int case_sensitive) |
117 | { |
118 | XdgGlobList *new_element; |
119 | XdgGlobList *tmp_element; |
120 | |
121 | tmp_element = glob_list; |
122 | while (tmp_element != NULL) |
123 | { |
124 | if (strcmp (tmp_element->data, data) == 0 && |
125 | strcmp (tmp_element->mime_type, mime_type) == 0) |
126 | return glob_list; |
127 | |
128 | tmp_element = tmp_element->next; |
129 | } |
130 | |
131 | new_element = _xdg_glob_list_new (); |
132 | new_element->data = data; |
133 | new_element->mime_type = mime_type; |
134 | new_element->weight = weight; |
135 | new_element->case_sensitive = case_sensitive; |
136 | if (glob_list == NULL) |
137 | return new_element; |
138 | |
139 | tmp_element = glob_list; |
140 | while (tmp_element->next != NULL) |
141 | tmp_element = tmp_element->next; |
142 | |
143 | tmp_element->next = new_element; |
144 | |
145 | return glob_list; |
146 | } |
147 | |
148 | /* XdgGlobHashNode |
149 | */ |
150 | |
151 | static XdgGlobHashNode * |
152 | _xdg_glob_hash_node_new (void) |
153 | { |
154 | XdgGlobHashNode *glob_hash_node; |
155 | |
156 | glob_hash_node = calloc (1, sizeof (XdgGlobHashNode)); |
157 | |
158 | return glob_hash_node; |
159 | } |
160 | |
161 | static void |
162 | _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, |
163 | int depth) |
164 | { |
165 | int i; |
166 | for (i = 0; i < depth; i++) |
167 | printf (" " ); |
168 | |
169 | printf ("%c" , (char)glob_hash_node->character); |
170 | if (glob_hash_node->mime_type) |
171 | printf (" - %s %d\n" , glob_hash_node->mime_type, glob_hash_node->weight); |
172 | else |
173 | printf ("\n" ); |
174 | if (glob_hash_node->child) |
175 | _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1); |
176 | if (glob_hash_node->next) |
177 | _xdg_glob_hash_node_dump (glob_hash_node->next, depth); |
178 | } |
179 | |
180 | static XdgGlobHashNode * |
181 | _xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node, |
182 | xdg_unichar_t *text, |
183 | const char *mime_type, |
184 | int weight, |
185 | int case_sensitive) |
186 | { |
187 | XdgGlobHashNode *node; |
188 | xdg_unichar_t character; |
189 | |
190 | character = text[0]; |
191 | |
192 | if ((glob_hash_node == NULL) || |
193 | (character < glob_hash_node->character)) |
194 | { |
195 | node = _xdg_glob_hash_node_new (); |
196 | node->character = character; |
197 | node->next = glob_hash_node; |
198 | glob_hash_node = node; |
199 | } |
200 | else if (character == glob_hash_node->character) |
201 | { |
202 | node = glob_hash_node; |
203 | } |
204 | else |
205 | { |
206 | XdgGlobHashNode *prev_node; |
207 | int found_node = FALSE; |
208 | |
209 | /* Look for the first character of text in glob_hash_node, and insert it if we |
210 | * have to.*/ |
211 | prev_node = glob_hash_node; |
212 | node = prev_node->next; |
213 | |
214 | while (node != NULL) |
215 | { |
216 | if (character < node->character) |
217 | { |
218 | node = _xdg_glob_hash_node_new (); |
219 | node->character = character; |
220 | node->next = prev_node->next; |
221 | prev_node->next = node; |
222 | |
223 | found_node = TRUE; |
224 | break; |
225 | } |
226 | else if (character == node->character) |
227 | { |
228 | found_node = TRUE; |
229 | break; |
230 | } |
231 | prev_node = node; |
232 | node = node->next; |
233 | } |
234 | |
235 | if (! found_node) |
236 | { |
237 | node = _xdg_glob_hash_node_new (); |
238 | node->character = character; |
239 | node->next = prev_node->next; |
240 | prev_node->next = node; |
241 | } |
242 | } |
243 | |
244 | text++; |
245 | if (*text == 0) |
246 | { |
247 | if (node->mime_type) |
248 | { |
249 | if (strcmp (node->mime_type, mime_type) != 0) |
250 | { |
251 | XdgGlobHashNode *child; |
252 | int found_node = FALSE; |
253 | |
254 | child = node->child; |
255 | while (child && child->character == 0) |
256 | { |
257 | if (strcmp (child->mime_type, mime_type) == 0) |
258 | { |
259 | found_node = TRUE; |
260 | break; |
261 | } |
262 | child = child->next; |
263 | } |
264 | |
265 | if (!found_node) |
266 | { |
267 | child = _xdg_glob_hash_node_new (); |
268 | child->character = 0; |
269 | child->mime_type = strdup (mime_type); |
270 | child->weight = weight; |
271 | child->case_sensitive = case_sensitive; |
272 | child->child = NULL; |
273 | child->next = node->child; |
274 | node->child = child; |
275 | } |
276 | } |
277 | } |
278 | else |
279 | { |
280 | node->mime_type = strdup (mime_type); |
281 | node->weight = weight; |
282 | node->case_sensitive = case_sensitive; |
283 | } |
284 | } |
285 | else |
286 | { |
287 | node->child = _xdg_glob_hash_insert_ucs4 (node->child, text, mime_type, weight, case_sensitive); |
288 | } |
289 | return glob_hash_node; |
290 | } |
291 | |
292 | /* glob must be valid UTF-8 */ |
293 | static XdgGlobHashNode * |
294 | _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node, |
295 | const char *text, |
296 | const char *mime_type, |
297 | int weight, |
298 | int case_sensitive) |
299 | { |
300 | XdgGlobHashNode *node; |
301 | xdg_unichar_t *unitext; |
302 | int len; |
303 | |
304 | unitext = _xdg_convert_to_ucs4 (text, &len); |
305 | _xdg_reverse_ucs4 (unitext, len); |
306 | node = _xdg_glob_hash_insert_ucs4 (glob_hash_node, unitext, mime_type, weight, case_sensitive); |
307 | free (unitext); |
308 | return node; |
309 | } |
310 | |
311 | typedef struct { |
312 | const char *mime; |
313 | int weight; |
314 | } MimeWeight; |
315 | |
316 | static int |
317 | _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node, |
318 | const char *file_name, |
319 | int len, |
320 | int case_sensitive_check, |
321 | MimeWeight mime_types[], |
322 | int n_mime_types) |
323 | { |
324 | int n; |
325 | XdgGlobHashNode *node; |
326 | xdg_unichar_t character; |
327 | |
328 | if (glob_hash_node == NULL) |
329 | return 0; |
330 | |
331 | character = file_name[len - 1]; |
332 | |
333 | for (node = glob_hash_node; node && character >= node->character; node = node->next) |
334 | { |
335 | if (character == node->character) |
336 | { |
337 | len--; |
338 | n = 0; |
339 | if (len > 0) |
340 | { |
341 | n = _xdg_glob_hash_node_lookup_file_name (node->child, |
342 | file_name, |
343 | len, |
344 | case_sensitive_check, |
345 | mime_types, |
346 | n_mime_types); |
347 | } |
348 | if (n == 0) |
349 | { |
350 | if (node->mime_type && |
351 | (case_sensitive_check || |
352 | !node->case_sensitive)) |
353 | { |
354 | mime_types[n].mime = node->mime_type; |
355 | mime_types[n].weight = node->weight; |
356 | n++; |
357 | } |
358 | node = node->child; |
359 | while (n < n_mime_types && node && node->character == 0) |
360 | { |
361 | if (node->mime_type && |
362 | (case_sensitive_check || |
363 | !node->case_sensitive)) |
364 | { |
365 | mime_types[n].mime = node->mime_type; |
366 | mime_types[n].weight = node->weight; |
367 | n++; |
368 | } |
369 | node = node->next; |
370 | } |
371 | } |
372 | return n; |
373 | } |
374 | } |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static int compare_mime_weight (const void *a, const void *b) |
380 | { |
381 | const MimeWeight *aa = (const MimeWeight *)a; |
382 | const MimeWeight *bb = (const MimeWeight *)b; |
383 | |
384 | return bb->weight - aa->weight; |
385 | } |
386 | |
387 | #define ISUPPER(c) ((c) >= 'A' && (c) <= 'Z') |
388 | static char * |
389 | ascii_tolower (const char *str) |
390 | { |
391 | char *p, *lower; |
392 | |
393 | lower = strdup (str); |
394 | p = lower; |
395 | while (*p != 0) |
396 | { |
397 | char c = *p; |
398 | *p++ = ISUPPER (c) ? c - 'A' + 'a' : c; |
399 | } |
400 | return lower; |
401 | } |
402 | |
403 | int |
404 | _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, |
405 | const char *file_name, |
406 | const char *mime_types[], |
407 | int n_mime_types) |
408 | { |
409 | XdgGlobList *list; |
410 | int i, n; |
411 | MimeWeight mimes[10]; |
412 | int n_mimes = 10; |
413 | int len; |
414 | char *lower_case; |
415 | |
416 | /* First, check the literals */ |
417 | |
418 | assert (file_name != NULL && n_mime_types > 0); |
419 | |
420 | n = 0; |
421 | |
422 | lower_case = ascii_tolower (file_name); |
423 | |
424 | for (list = glob_hash->literal_list; list; list = list->next) |
425 | { |
426 | if (strcmp ((const char *)list->data, file_name) == 0) |
427 | { |
428 | mime_types[0] = list->mime_type; |
429 | free (lower_case); |
430 | return 1; |
431 | } |
432 | } |
433 | |
434 | for (list = glob_hash->literal_list; list; list = list->next) |
435 | { |
436 | if (!list->case_sensitive && |
437 | strcmp ((const char *)list->data, lower_case) == 0) |
438 | { |
439 | mime_types[0] = list->mime_type; |
440 | free (lower_case); |
441 | return 1; |
442 | } |
443 | } |
444 | |
445 | |
446 | len = strlen (file_name); |
447 | n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, lower_case, len, FALSE, |
448 | mimes, n_mimes); |
449 | if (n == 0) |
450 | n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, file_name, len, TRUE, |
451 | mimes, n_mimes); |
452 | |
453 | if (n == 0) |
454 | { |
455 | for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next) |
456 | { |
457 | if (fnmatch ((const char *)list->data, file_name, 0) == 0) |
458 | { |
459 | mimes[n].mime = list->mime_type; |
460 | mimes[n].weight = list->weight; |
461 | n++; |
462 | } |
463 | } |
464 | } |
465 | free (lower_case); |
466 | |
467 | qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight); |
468 | |
469 | if (n_mime_types < n) |
470 | n = n_mime_types; |
471 | |
472 | for (i = 0; i < n; i++) |
473 | mime_types[i] = mimes[i].mime; |
474 | |
475 | return n; |
476 | } |
477 | |
478 | static int |
479 | get_simple_globs (XdgGlobHashNode *glob_hash_node, |
480 | const char *mime, |
481 | char *globs[], |
482 | int n_globs, |
483 | int *n, |
484 | xdg_unichar_t *prefix, |
485 | int depth) |
486 | { |
487 | assert (*n >= 0); |
488 | assert (depth >= 0); |
489 | |
490 | if (*n >= n_globs) |
491 | return FALSE; |
492 | |
493 | prefix[depth] = glob_hash_node->character; |
494 | |
495 | if (glob_hash_node->mime_type) |
496 | { |
497 | if (strcasecmp (glob_hash_node->mime_type, mime) == 0) |
498 | { |
499 | int i; |
500 | |
501 | globs[*n] = malloc ((depth + 1) * sizeof (char)); |
502 | for (i = 0; i < depth; i++) |
503 | globs[*n][depth - i - 1] = prefix[i]; |
504 | globs[*n][depth] = '\0'; |
505 | |
506 | (*n)++; |
507 | } |
508 | } |
509 | |
510 | if (glob_hash_node->child) |
511 | { |
512 | if (!get_simple_globs (glob_hash_node->child, mime, globs, n_globs, n, prefix, depth + 1)) |
513 | return FALSE; |
514 | } |
515 | |
516 | if (glob_hash_node->next) |
517 | { |
518 | if (!get_simple_globs (glob_hash_node->next, mime, globs, n_globs, n, prefix, depth)) |
519 | return FALSE; |
520 | } |
521 | |
522 | return *n < n_globs; |
523 | } |
524 | |
525 | int |
526 | _xdg_glob_hash_get_simple_globs (XdgGlobHash *glob_hash, |
527 | const char *mime, |
528 | char *globs[], |
529 | int n_globs) |
530 | { |
531 | xdg_unichar_t prefix[25]; |
532 | int n; |
533 | |
534 | if (!glob_hash || glob_hash->simple_node == NULL) |
535 | return 0; |
536 | |
537 | n = 0; |
538 | get_simple_globs (glob_hash->simple_node, xdg_mime_unalias_mime_type (mime), globs, n_globs, &n, prefix, 0); |
539 | |
540 | return n; |
541 | } |
542 | |
543 | |
544 | /* XdgGlobHash |
545 | */ |
546 | |
547 | XdgGlobHash * |
548 | _xdg_glob_hash_new (void) |
549 | { |
550 | XdgGlobHash *glob_hash; |
551 | |
552 | glob_hash = calloc (1, sizeof (XdgGlobHash)); |
553 | |
554 | return glob_hash; |
555 | } |
556 | |
557 | |
558 | static void |
559 | _xdg_glob_hash_free_nodes (XdgGlobHashNode *node) |
560 | { |
561 | if (node) |
562 | { |
563 | if (node->child) |
564 | _xdg_glob_hash_free_nodes (node->child); |
565 | if (node->next) |
566 | _xdg_glob_hash_free_nodes (node->next); |
567 | if (node->mime_type) |
568 | free ((void *) node->mime_type); |
569 | free (node); |
570 | } |
571 | } |
572 | |
573 | void |
574 | _xdg_glob_hash_free (XdgGlobHash *glob_hash) |
575 | { |
576 | _xdg_glob_list_free (glob_hash->literal_list); |
577 | _xdg_glob_list_free (glob_hash->full_list); |
578 | _xdg_glob_hash_free_nodes (glob_hash->simple_node); |
579 | free (glob_hash); |
580 | } |
581 | |
582 | XdgGlobType |
583 | _xdg_glob_determine_type (const char *glob) |
584 | { |
585 | const char *ptr; |
586 | int maybe_in_simple_glob = FALSE; |
587 | int first_char = TRUE; |
588 | |
589 | ptr = glob; |
590 | |
591 | while (*ptr != '\0') |
592 | { |
593 | if (*ptr == '*' && first_char) |
594 | maybe_in_simple_glob = TRUE; |
595 | else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*') |
596 | return XDG_GLOB_FULL; |
597 | |
598 | first_char = FALSE; |
599 | ptr = _xdg_utf8_next_char (ptr); |
600 | } |
601 | if (maybe_in_simple_glob) |
602 | return XDG_GLOB_SIMPLE; |
603 | else |
604 | return XDG_GLOB_LITERAL; |
605 | } |
606 | |
607 | /* glob must be valid UTF-8 */ |
608 | void |
609 | _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, |
610 | const char *glob, |
611 | const char *mime_type, |
612 | int weight, |
613 | int case_sensitive) |
614 | { |
615 | XdgGlobType type; |
616 | |
617 | assert (glob_hash != NULL); |
618 | assert (glob != NULL); |
619 | |
620 | type = _xdg_glob_determine_type (glob); |
621 | |
622 | switch (type) |
623 | { |
624 | case XDG_GLOB_LITERAL: |
625 | glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type), weight, case_sensitive); |
626 | break; |
627 | case XDG_GLOB_SIMPLE: |
628 | glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type, weight, case_sensitive); |
629 | break; |
630 | case XDG_GLOB_FULL: |
631 | glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type), weight, case_sensitive); |
632 | break; |
633 | } |
634 | } |
635 | |
636 | void |
637 | _xdg_glob_hash_dump (XdgGlobHash *glob_hash) |
638 | { |
639 | XdgGlobList *list; |
640 | printf ("LITERAL STRINGS\n" ); |
641 | if (!glob_hash || glob_hash->literal_list == NULL) |
642 | { |
643 | printf (" None\n" ); |
644 | } |
645 | else |
646 | { |
647 | for (list = glob_hash->literal_list; list; list = list->next) |
648 | printf (" %s - %s %d\n" , (char *)list->data, list->mime_type, list->weight); |
649 | } |
650 | printf ("\nSIMPLE GLOBS\n" ); |
651 | if (!glob_hash || glob_hash->simple_node == NULL) |
652 | { |
653 | printf (" None\n" ); |
654 | } |
655 | else |
656 | { |
657 | _xdg_glob_hash_node_dump (glob_hash->simple_node, 4); |
658 | } |
659 | |
660 | printf ("\nFULL GLOBS\n" ); |
661 | if (!glob_hash || glob_hash->full_list == NULL) |
662 | { |
663 | printf (" None\n" ); |
664 | } |
665 | else |
666 | { |
667 | for (list = glob_hash->full_list; list; list = list->next) |
668 | printf (" %s - %s %d\n" , (char *)list->data, list->mime_type, list->weight); |
669 | } |
670 | } |
671 | |
672 | |
673 | void |
674 | _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, |
675 | const char *file_name, |
676 | int version_two) |
677 | { |
678 | FILE *glob_file; |
679 | char line[255]; |
680 | char *p; |
681 | |
682 | glob_file = fopen (file_name, "r" ); |
683 | |
684 | if (glob_file == NULL) |
685 | return; |
686 | |
687 | /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. |
688 | * Blah */ |
689 | while (fgets (line, 255, glob_file) != NULL) |
690 | { |
691 | char *colon; |
692 | char *mimetype, *glob, *end; |
693 | int weight; |
694 | int case_sensitive; |
695 | |
696 | if (line[0] == '#' || line[0] == 0) |
697 | continue; |
698 | |
699 | end = line + strlen(line) - 1; |
700 | if (*end == '\n') |
701 | *end = 0; |
702 | |
703 | p = line; |
704 | if (version_two) |
705 | { |
706 | colon = strchr (p, ':'); |
707 | if (colon == NULL) |
708 | continue; |
709 | *colon = 0; |
710 | weight = atoi (p); |
711 | p = colon + 1; |
712 | } |
713 | else |
714 | weight = 50; |
715 | |
716 | colon = strchr (p, ':'); |
717 | if (colon == NULL) |
718 | continue; |
719 | *colon = 0; |
720 | |
721 | mimetype = p; |
722 | p = colon + 1; |
723 | glob = p; |
724 | case_sensitive = FALSE; |
725 | |
726 | colon = strchr (p, ':'); |
727 | if (version_two && colon != NULL) |
728 | { |
729 | char *flag; |
730 | |
731 | /* We got flags */ |
732 | *colon = 0; |
733 | p = colon + 1; |
734 | |
735 | /* Flags end at next colon */ |
736 | colon = strchr (p, ':'); |
737 | if (colon != NULL) |
738 | *colon = 0; |
739 | |
740 | flag = strstr (p, "cs" ); |
741 | if (flag != NULL && |
742 | /* Start or after comma */ |
743 | (flag == p || |
744 | flag[-1] == ',') && |
745 | /* ends with comma or end of string */ |
746 | (flag[2] == 0 || |
747 | flag[2] == ',')) |
748 | case_sensitive = TRUE; |
749 | } |
750 | |
751 | _xdg_glob_hash_append_glob (glob_hash, glob, mimetype, weight, case_sensitive); |
752 | } |
753 | |
754 | fclose (glob_file); |
755 | } |
756 | |