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