Merge pull request #2106 from AI0867/case-conflict-filenames
Case conflict filenames
This commit is contained in:
commit
3032c844e1
4 changed files with 80 additions and 20 deletions
|
@ -164,6 +164,13 @@ bool addons_client::upload_addon(const std::string& id, std::string& response_me
|
|||
this->last_error_data_ = font::escape_text(utils::join(badnames, "\n"));
|
||||
return false;
|
||||
}
|
||||
if(!check_case_insensitive_duplicates(addon_data, &badnames)){
|
||||
this->last_error_ =
|
||||
vgettext("The add-on <i>$addon_title</i> contains files or directories with case conflicts. "
|
||||
"File or directory names may not be differently-cased versions of the same string.", i18n_symbols);
|
||||
this->last_error_data_ = utils::join(badnames, "\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
config request_buf, response_buf;
|
||||
request_buf.add_child("upload", cfg).add_child("data", addon_data);
|
||||
|
|
|
@ -144,6 +144,55 @@ bool check_names_legal_internal(const config& dir, std::string current_prefix, s
|
|||
return badlist ? badlist->empty() : true;
|
||||
}
|
||||
|
||||
bool check_case_insensitive_duplicates_internal(const config& dir, std::string prefix, std::vector<std::string>* badlist){
|
||||
typedef std::pair<bool, std::string> printed_and_original;
|
||||
std::map<std::string, printed_and_original> filenames;
|
||||
bool inserted;
|
||||
bool printed;
|
||||
std::string original;
|
||||
for (const config &path : dir.child_range("file")) {
|
||||
const config::attribute_value &filename = path["name"];
|
||||
const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
|
||||
const std::string with_prefix = prefix + filename.str();
|
||||
std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
|
||||
if (!inserted){
|
||||
if(badlist){
|
||||
std::tie(printed, original) = filenames[lowercase];
|
||||
if(!printed){
|
||||
badlist->push_back(original);
|
||||
filenames[lowercase] = make_pair(true, std::string());
|
||||
}
|
||||
badlist->push_back(with_prefix);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const config &path : dir.child_range("dir")) {
|
||||
const config::attribute_value &filename = path["name"];
|
||||
const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
|
||||
const std::string with_prefix = prefix + filename.str();
|
||||
std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
|
||||
if (!inserted) {
|
||||
if(badlist){
|
||||
std::tie(printed, original) = filenames[lowercase];
|
||||
if(!printed){
|
||||
badlist->push_back(original);
|
||||
filenames[lowercase] = make_pair(true, std::string());
|
||||
}
|
||||
badlist->push_back(with_prefix);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!check_case_insensitive_duplicates_internal(path, prefix + filename + "/", badlist) && !badlist){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return badlist ? badlist->empty() : true;
|
||||
}
|
||||
|
||||
} // end unnamed namespace 3
|
||||
|
||||
bool check_names_legal(const config& dir, std::vector<std::string>* badlist)
|
||||
|
@ -155,21 +204,8 @@ bool check_names_legal(const config& dir, std::vector<std::string>* badlist)
|
|||
return check_names_legal_internal(dir, "", badlist);
|
||||
}
|
||||
|
||||
bool check_case_insensitive_duplicates(const config& dir){
|
||||
std::set<std::string> filenames;
|
||||
bool inserted;
|
||||
for (const config &path : dir.child_range("file")) {
|
||||
const config::attribute_value &filename = path["name"];
|
||||
std::tie(std::ignore, inserted) = filenames.insert(boost::algorithm::to_lower_copy(filename.str(), std::locale::classic()));
|
||||
if (!inserted) return false;
|
||||
}
|
||||
for (const config &path : dir.child_range("dir")) {
|
||||
const config::attribute_value &filename = path["name"];
|
||||
std::tie(std::ignore, inserted) = filenames.insert(boost::algorithm::to_lower_copy(filename.str(), std::locale::classic()));
|
||||
if (!inserted) return false;
|
||||
if (!check_case_insensitive_duplicates(path)) return false;
|
||||
}
|
||||
return true;
|
||||
bool check_case_insensitive_duplicates(const config& dir, std::vector<std::string>* badlist){
|
||||
return check_case_insensitive_duplicates_internal(dir, "", badlist);
|
||||
}
|
||||
|
||||
ADDON_TYPE get_addon_type(const std::string& str)
|
||||
|
|
|
@ -77,8 +77,21 @@ bool addon_filename_legal(const std::string& name);
|
|||
* @returns True if no illegal names were found.
|
||||
*/
|
||||
bool check_names_legal(const config& dir, std::vector<std::string>* badlist = nullptr);
|
||||
/** Probes an add-on archive for case-conflicts on case-insensitive filesystems. */
|
||||
bool check_case_insensitive_duplicates(const config& dir);
|
||||
/**
|
||||
* Scans an add-on archive for case-conflicts.
|
||||
*
|
||||
* Case conflicts may cause trouble on case-insensitive filesystems.
|
||||
*
|
||||
* @param dir The WML container for the root [dir] node where the search
|
||||
* should begin.
|
||||
* @param badlist If provided and not null, any case conflicts encountered will
|
||||
* be added to this list. This also makes the archive scan more
|
||||
* thorough instead of returning on the first conflict
|
||||
* encountered.
|
||||
*
|
||||
* @returns True if no conflicts were found.
|
||||
*/
|
||||
bool check_case_insensitive_duplicates(const config& dir, std::vector<std::string>* badlist = nullptr);
|
||||
|
||||
std::string encode_binary(const std::string& str);
|
||||
std::string unencode_binary(const std::string& str);
|
||||
|
|
|
@ -693,9 +693,13 @@ void server::handle_upload(const server::request& req)
|
|||
"File or directory names may not contain whitespace, control characters or any of the following characters: '\" * / : < > ? \\ | ~'. "
|
||||
"It also may not contain '..' end with '.' or be longer than 255 characters.",
|
||||
filelist, req.sock);
|
||||
} else if(!check_case_insensitive_duplicates(data)) {
|
||||
LOG_CS << "Upload aborted - case conflict in add-on data.\n";
|
||||
send_error("Add-on rejected: Two files have a case conflict", req.sock);
|
||||
} else if(!check_case_insensitive_duplicates(data, &badnames)) {
|
||||
const std::string& filelist = utils::join(badnames, "\n");
|
||||
LOG_CS << "Upload aborted - case conflict in add-on data (" << badnames.size() << " entries).\n";
|
||||
send_error(
|
||||
"Add-on rejected: The add-on contains files or directories with case conflicts. "
|
||||
"File or directory names may not be differently-cased versions of the same string.",
|
||||
filelist, req.sock);
|
||||
} else if(campaign && !authenticate(*campaign, upload["passphrase"])) {
|
||||
LOG_CS << "Upload aborted - incorrect passphrase.\n";
|
||||
send_error("Add-on rejected: The add-on already exists, and your passphrase was incorrect.", req.sock);
|
||||
|
|
Loading…
Add table
Reference in a new issue