1 ///////////////////////////////////////////////////////////////////////////////
4 /// \brief Checks filename suffix and creates the destination filename
6 // Author: Lasse Collin
8 // This file has been put into the public domain.
9 // You can do whatever you want with this file.
11 ///////////////////////////////////////////////////////////////////////////////
15 // For case-insensitive filename suffix on case-insensitive systems
16 #if defined(TUKLIB_DOSLIKE) || defined(__VMS)
17 # define strcmp strcasecmp
21 static char *custom_suffix = NULL;
24 /// \brief Test if the char is a directory separator
29 return c == '/' || c == '\\' || c == ':';
36 /// \brief Test if the string contains a directory separator
38 has_dir_sep(const char *str)
41 return strpbrk(str, "/\\:") != NULL;
43 return strchr(str, '/') != NULL;
48 /// \brief Checks if src_name has given compressed_suffix
50 /// \param suffix Filename suffix to look for
51 /// \param src_name Input filename
52 /// \param src_len strlen(src_name)
54 /// \return If src_name has the suffix, src_len - strlen(suffix) is
55 /// returned. It's always a positive integer. Otherwise zero
58 test_suffix(const char *suffix, const char *src_name, size_t src_len)
60 const size_t suffix_len = strlen(suffix);
62 // The filename must have at least one character in addition to
63 // the suffix. src_name may contain path to the filename, so we
64 // need to check for directory separator too.
65 if (src_len <= suffix_len
66 || is_dir_sep(src_name[src_len - suffix_len - 1]))
69 if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
70 return src_len - suffix_len;
76 /// \brief Removes the filename suffix of the compressed file
78 /// \return Name of the uncompressed file, or NULL if file has unknown
81 uncompressed_name(const char *src_name, const size_t src_len)
84 const char *compressed;
85 const char *uncompressed;
88 { ".txz", ".tar" }, // .txz abbreviation for .txt.gz is rare.
92 // { ".tgz", ".tar" },
95 const char *new_suffix = "";
98 if (opt_format == FORMAT_RAW) {
99 // Don't check for known suffixes when --format=raw was used.
100 if (custom_suffix == NULL) {
101 message_error(_("%s: With --format=raw, "
102 "--suffix=.SUF is required unless "
103 "writing to stdout"), src_name);
107 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
108 new_len = test_suffix(suffixes[i].compressed,
111 new_suffix = suffixes[i].uncompressed;
117 if (new_len == 0 && custom_suffix != NULL)
118 new_len = test_suffix(custom_suffix, src_name, src_len);
121 message_warning(_("%s: Filename has an unknown suffix, "
122 "skipping"), src_name);
126 const size_t new_suffix_len = strlen(new_suffix);
127 char *dest_name = xmalloc(new_len + new_suffix_len + 1);
129 memcpy(dest_name, src_name, new_len);
130 memcpy(dest_name + new_len, new_suffix, new_suffix_len);
131 dest_name[new_len + new_suffix_len] = '\0';
137 /// \brief Appends suffix to src_name
139 /// In contrast to uncompressed_name(), we check only suffixes that are valid
140 /// for the specified file format.
142 compressed_name(const char *src_name, const size_t src_len)
144 // The order of these must match the order in args.h.
145 static const char *const all_suffixes[][3] = {
161 // --format=raw requires specifying the suffix
162 // manually or using stdout.
167 // args.c ensures this.
168 assert(opt_format != FORMAT_AUTO);
170 const size_t format = opt_format - 1;
171 const char *const *suffixes = all_suffixes[format];
173 for (size_t i = 0; suffixes[i] != NULL; ++i) {
174 if (test_suffix(suffixes[i], src_name, src_len) != 0) {
175 message_warning(_("%s: File already has `%s' "
176 "suffix, skipping"), src_name,
182 if (custom_suffix != NULL) {
183 if (test_suffix(custom_suffix, src_name, src_len) != 0) {
184 message_warning(_("%s: File already has `%s' "
185 "suffix, skipping"), src_name,
191 // TODO: Hmm, maybe it would be better to validate this in args.c,
192 // since the suffix handling when decoding is weird now.
193 if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
194 message_error(_("%s: With --format=raw, "
195 "--suffix=.SUF is required unless "
196 "writing to stdout"), src_name);
200 const char *suffix = custom_suffix != NULL
201 ? custom_suffix : suffixes[0];
202 const size_t suffix_len = strlen(suffix);
204 char *dest_name = xmalloc(src_len + suffix_len + 1);
206 memcpy(dest_name, src_name, src_len);
207 memcpy(dest_name + src_len, suffix, suffix_len);
208 dest_name[src_len + suffix_len] = '\0';
215 suffix_get_dest_name(const char *src_name)
217 assert(src_name != NULL);
219 // Length of the name is needed in all cases to locate the end of
220 // the string to compare the suffix, so calculate the length here.
221 const size_t src_len = strlen(src_name);
223 return opt_mode == MODE_COMPRESS
224 ? compressed_name(src_name, src_len)
225 : uncompressed_name(src_name, src_len);
230 suffix_set(const char *suffix)
232 // Empty suffix and suffixes having a directory separator are
233 // rejected. Such suffixes would break things later.
234 if (suffix[0] == '\0' || has_dir_sep(suffix))
235 message_fatal(_("%s: Invalid filename suffix"), suffix);
237 // Replace the old custom_suffix (if any) with the new suffix.
239 custom_suffix = xstrdup(suffix);