]> icculus.org git repositories - icculus/xz.git/blob - src/xz/suffix.c
aff4d6d6c24a60376950734f531bcd2577aff171
[icculus/xz.git] / src / xz / suffix.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       suffix.c
4 /// \brief      Checks filename suffix and creates the destination filename
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU Lesser General Public
10 //  License as published by the Free Software Foundation; either
11 //  version 2.1 of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 //  Lesser General Public License for more details.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "private.h"
21
22 // For case-insensitive filename suffix on case-insensitive systems
23 #ifdef DOSLIKE
24 #       define strcmp strcasecmp
25 #endif
26
27
28 static char *custom_suffix = NULL;
29
30
31 struct suffix_pair {
32         const char *compressed;
33         const char *uncompressed;
34 };
35
36
37 /// \brief      Checks if src_name has given compressed_suffix
38 ///
39 /// \param      suffix      Filename suffix to look for
40 /// \param      src_name    Input filename
41 /// \param      src_len     strlen(src_name)
42 ///
43 /// \return     If src_name has the suffix, src_len - strlen(suffix) is
44 ///             returned. It's always a positive integer. Otherwise zero
45 ///             is returned.
46 static size_t
47 test_suffix(const char *suffix, const char *src_name, size_t src_len)
48 {
49         const size_t suffix_len = strlen(suffix);
50
51         // The filename must have at least one character in addition to
52         // the suffix. src_name may contain path to the filename, so we
53         // need to check for directory separator too.
54         if (src_len <= suffix_len || src_name[src_len - suffix_len - 1] == '/')
55                 return 0;
56
57         if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
58                 return src_len - suffix_len;
59
60         return 0;
61 }
62
63
64 /// \brief      Removes the filename suffix of the compressed file
65 ///
66 /// \return     Name of the uncompressed file, or NULL if file has unknown
67 ///             suffix.
68 static char *
69 uncompressed_name(const char *src_name, const size_t src_len)
70 {
71         static const struct suffix_pair suffixes[] = {
72                 { ".xz",    "" },
73                 { ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
74                 { ".lzma",  "" },
75                 { ".tlz",   ".tar" },
76                 // { ".gz",    "" },
77                 // { ".tgz",   ".tar" },
78         };
79
80         const char *new_suffix = "";
81         size_t new_len = 0;
82
83         if (opt_format == FORMAT_RAW) {
84                 // Don't check for known suffixes when --format=raw was used.
85                 if (custom_suffix == NULL) {
86                         message_error(_("%s: With --format=raw, "
87                                         "--suffix=.SUF is required unless "
88                                         "writing to stdout"), src_name);
89                         return NULL;
90                 }
91         } else {
92                 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
93                         new_len = test_suffix(suffixes[i].compressed,
94                                         src_name, src_len);
95                         if (new_len != 0) {
96                                 new_suffix = suffixes[i].uncompressed;
97                                 break;
98                         }
99                 }
100         }
101
102         if (new_len == 0 && custom_suffix != NULL)
103                 new_len = test_suffix(custom_suffix, src_name, src_len);
104
105         if (new_len == 0) {
106                 message_warning(_("%s: Filename has an unknown suffix, "
107                                 "skipping"), src_name);
108                 return NULL;
109         }
110
111         const size_t new_suffix_len = strlen(new_suffix);
112         char *dest_name = xmalloc(new_len + new_suffix_len + 1);
113
114         memcpy(dest_name, src_name, new_len);
115         memcpy(dest_name + new_len, new_suffix, new_suffix_len);
116         dest_name[new_len + new_suffix_len] = '\0';
117
118         return dest_name;
119 }
120
121
122 /// \brief      Appends suffix to src_name
123 ///
124 /// In contrast to uncompressed_name(), we check only suffixes that are valid
125 /// for the specified file format.
126 static char *
127 compressed_name(const char *src_name, const size_t src_len)
128 {
129         // The order of these must match the order in args.h.
130         static const struct suffix_pair all_suffixes[][3] = {
131                 {
132                         { ".xz",    "" },
133                         { ".txz",   ".tar" },
134                         { NULL, NULL }
135                 }, {
136                         { ".lzma",  "" },
137                         { ".tlz",   ".tar" },
138                         { NULL,     NULL }
139 /*
140                 }, {
141                         { ".gz",    "" },
142                         { ".tgz",   ".tar" },
143                         { NULL,     NULL }
144 */
145                 }, {
146                         // --format=raw requires specifying the suffix
147                         // manually or using stdout.
148                         { NULL,     NULL }
149                 }
150         };
151
152         // args.c ensures this.
153         assert(opt_format != FORMAT_AUTO);
154
155         const size_t format = opt_format - 1;
156         const struct suffix_pair *const suffixes = all_suffixes[format];
157
158         for (size_t i = 0; suffixes[i].compressed != NULL; ++i) {
159                 if (test_suffix(suffixes[i].compressed, src_name, src_len)
160                                 != 0) {
161                         message_warning(_("%s: File already has `%s' "
162                                         "suffix, skipping"), src_name,
163                                         suffixes[i].compressed);
164                         return NULL;
165                 }
166         }
167
168         // TODO: Hmm, maybe it would be better to validate this in args.c,
169         // since the suffix handling when decoding is weird now.
170         if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
171                 message_error(_("%s: With --format=raw, "
172                                 "--suffix=.SUF is required unless "
173                                 "writing to stdout"), src_name);
174                 return NULL;
175         }
176
177         const char *suffix = custom_suffix != NULL
178                         ? custom_suffix : suffixes[0].compressed;
179         const size_t suffix_len = strlen(suffix);
180
181         char *dest_name = xmalloc(src_len + suffix_len + 1);
182
183         memcpy(dest_name, src_name, src_len);
184         memcpy(dest_name + src_len, suffix, suffix_len);
185         dest_name[src_len + suffix_len] = '\0';
186
187         return dest_name;
188 }
189
190
191 extern char *
192 suffix_get_dest_name(const char *src_name)
193 {
194         assert(src_name != NULL);
195
196         // Length of the name is needed in all cases to locate the end of
197         // the string to compare the suffix, so calculate the length here.
198         const size_t src_len = strlen(src_name);
199
200         return opt_mode == MODE_COMPRESS
201                         ? compressed_name(src_name, src_len)
202                         : uncompressed_name(src_name, src_len);
203 }
204
205
206 extern void
207 suffix_set(const char *suffix)
208 {
209         // Empty suffix and suffixes having a slash are rejected. Such
210         // suffixes would break things later.
211         if (suffix[0] == '\0' || strchr(suffix, '/') != NULL)
212                 message_fatal(_("%s: Invalid filename suffix"), optarg);
213
214         // Replace the old custom_suffix (if any) with the new suffix.
215         free(custom_suffix);
216         custom_suffix = xstrdup(suffix);
217         return;
218 }