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