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