xref: /dragonfly/contrib/xz/src/xz/suffix.c (revision 650094e1)
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 /// \brief      Test if the char is a directory separator
25 static bool
26 is_dir_sep(char c)
27 {
28 #ifdef TUKLIB_DOSLIKE
29 	return c == '/' || c == '\\' || c == ':';
30 #else
31 	return c == '/';
32 #endif
33 }
34 
35 
36 /// \brief      Test if the string contains a directory separator
37 static bool
38 has_dir_sep(const char *str)
39 {
40 #ifdef TUKLIB_DOSLIKE
41 	return strpbrk(str, "/\\:") != NULL;
42 #else
43 	return strchr(str, '/') != NULL;
44 #endif
45 }
46 
47 
48 /// \brief      Checks if src_name has given compressed_suffix
49 ///
50 /// \param      suffix      Filename suffix to look for
51 /// \param      src_name    Input filename
52 /// \param      src_len     strlen(src_name)
53 ///
54 /// \return     If src_name has the suffix, src_len - strlen(suffix) is
55 ///             returned. It's always a positive integer. Otherwise zero
56 ///             is returned.
57 static size_t
58 test_suffix(const char *suffix, const char *src_name, size_t src_len)
59 {
60 	const size_t suffix_len = strlen(suffix);
61 
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]))
67 		return 0;
68 
69 	if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
70 		return src_len - suffix_len;
71 
72 	return 0;
73 }
74 
75 
76 /// \brief      Removes the filename suffix of the compressed file
77 ///
78 /// \return     Name of the uncompressed file, or NULL if file has unknown
79 ///             suffix.
80 static char *
81 uncompressed_name(const char *src_name, const size_t src_len)
82 {
83 	static const struct {
84 		const char *compressed;
85 		const char *uncompressed;
86 	} suffixes[] = {
87 		{ ".xz",    "" },
88 		{ ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
89 		{ ".lzma",  "" },
90 		{ ".tlz",   ".tar" },
91 		// { ".gz",    "" },
92 		// { ".tgz",   ".tar" },
93 	};
94 
95 	const char *new_suffix = "";
96 	size_t new_len = 0;
97 
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);
104 			return NULL;
105 		}
106 	} else {
107 		for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
108 			new_len = test_suffix(suffixes[i].compressed,
109 					src_name, src_len);
110 			if (new_len != 0) {
111 				new_suffix = suffixes[i].uncompressed;
112 				break;
113 			}
114 		}
115 	}
116 
117 	if (new_len == 0 && custom_suffix != NULL)
118 		new_len = test_suffix(custom_suffix, src_name, src_len);
119 
120 	if (new_len == 0) {
121 		message_warning(_("%s: Filename has an unknown suffix, "
122 				"skipping"), src_name);
123 		return NULL;
124 	}
125 
126 	const size_t new_suffix_len = strlen(new_suffix);
127 	char *dest_name = xmalloc(new_len + new_suffix_len + 1);
128 
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';
132 
133 	return dest_name;
134 }
135 
136 
137 /// \brief      Appends suffix to src_name
138 ///
139 /// In contrast to uncompressed_name(), we check only suffixes that are valid
140 /// for the specified file format.
141 static char *
142 compressed_name(const char *src_name, const size_t src_len)
143 {
144 	// The order of these must match the order in args.h.
145 	static const char *const all_suffixes[][3] = {
146 		{
147 			".xz",
148 			".txz",
149 			NULL
150 		}, {
151 			".lzma",
152 			".tlz",
153 			NULL
154 /*
155 		}, {
156 			".gz",
157 			".tgz",
158 			NULL
159 */
160 		}, {
161 			// --format=raw requires specifying the suffix
162 			// manually or using stdout.
163 			NULL
164 		}
165 	};
166 
167 	// args.c ensures this.
168 	assert(opt_format != FORMAT_AUTO);
169 
170 	const size_t format = opt_format - 1;
171 	const char *const *suffixes = all_suffixes[format];
172 
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,
177 					suffixes[i]);
178 			return NULL;
179 		}
180 	}
181 
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,
186 					custom_suffix);
187 			return NULL;
188 		}
189 	}
190 
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);
197 		return NULL;
198 	}
199 
200 	const char *suffix = custom_suffix != NULL
201 			? custom_suffix : suffixes[0];
202 	const size_t suffix_len = strlen(suffix);
203 
204 	char *dest_name = xmalloc(src_len + suffix_len + 1);
205 
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';
209 
210 	return dest_name;
211 }
212 
213 
214 extern char *
215 suffix_get_dest_name(const char *src_name)
216 {
217 	assert(src_name != NULL);
218 
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);
222 
223 	return opt_mode == MODE_COMPRESS
224 			? compressed_name(src_name, src_len)
225 			: uncompressed_name(src_name, src_len);
226 }
227 
228 
229 extern void
230 suffix_set(const char *suffix)
231 {
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"), optarg);
236 
237 	// Replace the old custom_suffix (if any) with the new suffix.
238 	free(custom_suffix);
239 	custom_suffix = xstrdup(suffix);
240 	return;
241 }
242