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"), suffix); 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