suffix.c revision 223935
1207753Smm///////////////////////////////////////////////////////////////////////////////
2207753Smm//
3207753Smm/// \file       suffix.c
4207753Smm/// \brief      Checks filename suffix and creates the destination filename
5207753Smm//
6207753Smm//  Author:     Lasse Collin
7207753Smm//
8207753Smm//  This file has been put into the public domain.
9207753Smm//  You can do whatever you want with this file.
10207753Smm//
11207753Smm///////////////////////////////////////////////////////////////////////////////
12207753Smm
13207753Smm#include "private.h"
14207753Smm
15207753Smm// For case-insensitive filename suffix on case-insensitive systems
16207753Smm#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
17207753Smm#	define strcmp strcasecmp
18207753Smm#endif
19207753Smm
20207753Smm
21207753Smmstatic char *custom_suffix = NULL;
22207753Smm
23207753Smm
24219001Smm/// \brief      Test if the char is a directory separator
25219001Smmstatic bool
26219001Smmis_dir_sep(char c)
27219001Smm{
28219001Smm#ifdef TUKLIB_DOSLIKE
29219001Smm	return c == '/' || c == '\\' || c == ':';
30219001Smm#else
31219001Smm	return c == '/';
32219001Smm#endif
33219001Smm}
34219001Smm
35219001Smm
36219001Smm/// \brief      Test if the string contains a directory separator
37219001Smmstatic bool
38219001Smmhas_dir_sep(const char *str)
39219001Smm{
40219001Smm#ifdef TUKLIB_DOSLIKE
41219001Smm	return strpbrk(str, "/\\:") != NULL;
42219001Smm#else
43219001Smm	return strchr(str, '/') != NULL;
44219001Smm#endif
45219001Smm}
46219001Smm
47219001Smm
48207753Smm/// \brief      Checks if src_name has given compressed_suffix
49207753Smm///
50207753Smm/// \param      suffix      Filename suffix to look for
51207753Smm/// \param      src_name    Input filename
52207753Smm/// \param      src_len     strlen(src_name)
53207753Smm///
54207753Smm/// \return     If src_name has the suffix, src_len - strlen(suffix) is
55207753Smm///             returned. It's always a positive integer. Otherwise zero
56207753Smm///             is returned.
57207753Smmstatic size_t
58207753Smmtest_suffix(const char *suffix, const char *src_name, size_t src_len)
59207753Smm{
60207753Smm	const size_t suffix_len = strlen(suffix);
61207753Smm
62207753Smm	// The filename must have at least one character in addition to
63207753Smm	// the suffix. src_name may contain path to the filename, so we
64207753Smm	// need to check for directory separator too.
65219001Smm	if (src_len <= suffix_len
66219001Smm			|| is_dir_sep(src_name[src_len - suffix_len - 1]))
67207753Smm		return 0;
68207753Smm
69207753Smm	if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
70207753Smm		return src_len - suffix_len;
71207753Smm
72207753Smm	return 0;
73207753Smm}
74207753Smm
75207753Smm
76207753Smm/// \brief      Removes the filename suffix of the compressed file
77207753Smm///
78207753Smm/// \return     Name of the uncompressed file, or NULL if file has unknown
79207753Smm///             suffix.
80207753Smmstatic char *
81207753Smmuncompressed_name(const char *src_name, const size_t src_len)
82207753Smm{
83223935Smm	static const struct {
84223935Smm		const char *compressed;
85223935Smm		const char *uncompressed;
86223935Smm	} suffixes[] = {
87207753Smm		{ ".xz",    "" },
88207753Smm		{ ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
89207753Smm		{ ".lzma",  "" },
90207753Smm		{ ".tlz",   ".tar" },
91207753Smm		// { ".gz",    "" },
92207753Smm		// { ".tgz",   ".tar" },
93207753Smm	};
94207753Smm
95207753Smm	const char *new_suffix = "";
96207753Smm	size_t new_len = 0;
97207753Smm
98207753Smm	if (opt_format == FORMAT_RAW) {
99207753Smm		// Don't check for known suffixes when --format=raw was used.
100207753Smm		if (custom_suffix == NULL) {
101207753Smm			message_error(_("%s: With --format=raw, "
102207753Smm					"--suffix=.SUF is required unless "
103207753Smm					"writing to stdout"), src_name);
104207753Smm			return NULL;
105207753Smm		}
106207753Smm	} else {
107207753Smm		for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
108207753Smm			new_len = test_suffix(suffixes[i].compressed,
109207753Smm					src_name, src_len);
110207753Smm			if (new_len != 0) {
111207753Smm				new_suffix = suffixes[i].uncompressed;
112207753Smm				break;
113207753Smm			}
114207753Smm		}
115207753Smm	}
116207753Smm
117207753Smm	if (new_len == 0 && custom_suffix != NULL)
118207753Smm		new_len = test_suffix(custom_suffix, src_name, src_len);
119207753Smm
120207753Smm	if (new_len == 0) {
121207753Smm		message_warning(_("%s: Filename has an unknown suffix, "
122207753Smm				"skipping"), src_name);
123207753Smm		return NULL;
124207753Smm	}
125207753Smm
126207753Smm	const size_t new_suffix_len = strlen(new_suffix);
127207753Smm	char *dest_name = xmalloc(new_len + new_suffix_len + 1);
128207753Smm
129207753Smm	memcpy(dest_name, src_name, new_len);
130207753Smm	memcpy(dest_name + new_len, new_suffix, new_suffix_len);
131207753Smm	dest_name[new_len + new_suffix_len] = '\0';
132207753Smm
133207753Smm	return dest_name;
134207753Smm}
135207753Smm
136207753Smm
137207753Smm/// \brief      Appends suffix to src_name
138207753Smm///
139207753Smm/// In contrast to uncompressed_name(), we check only suffixes that are valid
140207753Smm/// for the specified file format.
141207753Smmstatic char *
142207753Smmcompressed_name(const char *src_name, const size_t src_len)
143207753Smm{
144207753Smm	// The order of these must match the order in args.h.
145223935Smm	static const char *const all_suffixes[][3] = {
146207753Smm		{
147223935Smm			".xz",
148223935Smm			".txz",
149223935Smm			NULL
150207753Smm		}, {
151223935Smm			".lzma",
152223935Smm			".tlz",
153223935Smm			NULL
154207753Smm/*
155207753Smm		}, {
156223935Smm			".gz",
157223935Smm			".tgz",
158223935Smm			NULL
159207753Smm*/
160207753Smm		}, {
161207753Smm			// --format=raw requires specifying the suffix
162207753Smm			// manually or using stdout.
163223935Smm			NULL
164207753Smm		}
165207753Smm	};
166207753Smm
167207753Smm	// args.c ensures this.
168207753Smm	assert(opt_format != FORMAT_AUTO);
169207753Smm
170207753Smm	const size_t format = opt_format - 1;
171223935Smm	const char *const *suffixes = all_suffixes[format];
172207753Smm
173223935Smm	for (size_t i = 0; suffixes[i] != NULL; ++i) {
174223935Smm		if (test_suffix(suffixes[i], src_name, src_len) != 0) {
175207753Smm			message_warning(_("%s: File already has `%s' "
176207753Smm					"suffix, skipping"), src_name,
177223935Smm					suffixes[i]);
178207753Smm			return NULL;
179207753Smm		}
180207753Smm	}
181207753Smm
182223935Smm	if (custom_suffix != NULL) {
183223935Smm		if (test_suffix(custom_suffix, src_name, src_len) != 0) {
184223935Smm			message_warning(_("%s: File already has `%s' "
185223935Smm					"suffix, skipping"), src_name,
186223935Smm					custom_suffix);
187223935Smm			return NULL;
188223935Smm		}
189223935Smm	}
190223935Smm
191207753Smm	// TODO: Hmm, maybe it would be better to validate this in args.c,
192207753Smm	// since the suffix handling when decoding is weird now.
193207753Smm	if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
194207753Smm		message_error(_("%s: With --format=raw, "
195207753Smm				"--suffix=.SUF is required unless "
196207753Smm				"writing to stdout"), src_name);
197207753Smm		return NULL;
198207753Smm	}
199207753Smm
200207753Smm	const char *suffix = custom_suffix != NULL
201223935Smm			? custom_suffix : suffixes[0];
202207753Smm	const size_t suffix_len = strlen(suffix);
203207753Smm
204207753Smm	char *dest_name = xmalloc(src_len + suffix_len + 1);
205207753Smm
206207753Smm	memcpy(dest_name, src_name, src_len);
207207753Smm	memcpy(dest_name + src_len, suffix, suffix_len);
208207753Smm	dest_name[src_len + suffix_len] = '\0';
209207753Smm
210207753Smm	return dest_name;
211207753Smm}
212207753Smm
213207753Smm
214207753Smmextern char *
215207753Smmsuffix_get_dest_name(const char *src_name)
216207753Smm{
217207753Smm	assert(src_name != NULL);
218207753Smm
219207753Smm	// Length of the name is needed in all cases to locate the end of
220207753Smm	// the string to compare the suffix, so calculate the length here.
221207753Smm	const size_t src_len = strlen(src_name);
222207753Smm
223207753Smm	return opt_mode == MODE_COMPRESS
224207753Smm			? compressed_name(src_name, src_len)
225207753Smm			: uncompressed_name(src_name, src_len);
226207753Smm}
227207753Smm
228207753Smm
229207753Smmextern void
230207753Smmsuffix_set(const char *suffix)
231207753Smm{
232219001Smm	// Empty suffix and suffixes having a directory separator are
233219001Smm	// rejected. Such suffixes would break things later.
234219001Smm	if (suffix[0] == '\0' || has_dir_sep(suffix))
235207753Smm		message_fatal(_("%s: Invalid filename suffix"), optarg);
236207753Smm
237207753Smm	// Replace the old custom_suffix (if any) with the new suffix.
238207753Smm	free(custom_suffix);
239207753Smm	custom_suffix = xstrdup(suffix);
240207753Smm	return;
241207753Smm}
242