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