Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix extremely long parsing time on hostile PROJ strings #2968

Merged
merged 2 commits into from
Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,31 @@ argument string, args, and count its number of elements.
}


static void unquote_string(char* param_str) {

size_t len = strlen(param_str);
// Remove leading and terminating spaces after equal sign
const char* equal = strstr(param_str, "=\"");
if( equal && equal - param_str + 1 >= 2 && param_str[len-1] == '"' ) {
size_t dst = equal + 1 - param_str;
size_t src = dst + 1;
for( ; param_str[src]; dst++, src++)
{
if( param_str[src] == '"' ) {
if( param_str[src+1] == '"' ) {
src++;
} else {
break;
}
}
param_str[dst] = param_str[src];
}
param_str[dst] = '\0';
}

}



/*****************************************************************************/
char **pj_trim_argv (size_t argc, char *args) {
Expand All @@ -349,7 +374,6 @@ It is the duty of the caller to free this array.
if (0==argc)
return nullptr;


/* turn the input string into an array of strings */
char** argv = (char **) calloc (argc, sizeof (char *));
if (nullptr==argv)
Expand All @@ -359,6 +383,7 @@ It is the duty of the caller to free this array.
char* str = argv[j];
size_t nLen = strlen(str);
i += nLen + 1;
unquote_string(str);
}
return argv;
}
Expand All @@ -370,7 +395,11 @@ std::string pj_double_quote_string_param_if_needed(const std::string& str) {
if( str.find(' ') == std::string::npos ) {
return str;
}
return '"' + replaceAll(str, "\"", "\"\"") + '"';
std::string ret;
ret += '"';
ret += replaceAll(str, "\"", "\"\"");
ret += '"';
return ret;
}

/*****************************************************************************/
Expand All @@ -383,7 +412,6 @@ Allocates, and returns, an array of char, large enough to hold a whitespace
separated copy of the args in argv. It is the duty of the caller to free this
array.
******************************************************************************/

try
{
std::string s;
Expand Down
107 changes: 38 additions & 69 deletions src/iso19111/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8248,75 +8248,52 @@ static void
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
std::vector<Step::KeyValue> &globalParamValues,
std::string &title) {
const char *c_str = projString.c_str();
std::vector<std::string> tokens;

bool hasProj = false;
bool hasInit = false;
bool hasPipeline = false;
{
size_t i = 0;
while (true) {
for (; isspace(static_cast<unsigned char>(c_str[i])); i++) {
}
std::string token;
bool in_string = false;
for (; c_str[i]; i++) {
if (in_string) {
if (c_str[i] == '"' && c_str[i + 1] == '"') {
i++;
} else if (c_str[i] == '"') {
in_string = false;
continue;
}
} else if (c_str[i] == '=' && c_str[i + 1] == '"') {
in_string = true;
token += c_str[i];
i++;
continue;
} else if (isspace(static_cast<unsigned char>(c_str[i]))) {
break;
}
token += c_str[i];
}
if (in_string) {
throw ParsingException("Unbalanced double quote");
}
if (token.empty()) {
break;
}
if (!hasPipeline &&
(token == "proj=pipeline" || token == "+proj=pipeline")) {
hasPipeline = true;
} else if (!hasProj && (starts_with(token, "proj=") ||
starts_with(token, "+proj="))) {
hasProj = true;
} else if (!hasInit && (starts_with(token, "init=") ||
starts_with(token, "+init="))) {
hasInit = true;
}
tokens.emplace_back(token);

std::string projStringModified(projString);

// Special case for "+title=several words +foo=bar"
if (starts_with(projStringModified, "+title=") &&
projStringModified.size() > 7 && projStringModified[7] != '"') {
const auto plusPos = projStringModified.find(" +", 1);
const auto spacePos = projStringModified.find(' ');
if (plusPos != std::string::npos && spacePos != std::string::npos &&
spacePos < plusPos) {
std::string tmp("+title=");
tmp += pj_double_quote_string_param_if_needed(
projStringModified.substr(7, plusPos - 7));
tmp += projStringModified.substr(plusPos);
projStringModified = std::move(tmp);
}
}

bool prevWasTitle = false;
size_t argc = pj_trim_argc(&projStringModified[0]);
char **argv = pj_trim_argv(argc, &projStringModified[0]);
for (size_t i = 0; i < argc; i++) {
std::string token(argv[i]);
if (!hasPipeline && token == "proj=pipeline") {
hasPipeline = true;
} else if (!hasProj && starts_with(token, "proj=")) {
hasProj = true;
} else if (!hasInit && starts_with(token, "init=")) {
hasInit = true;
}
tokens.emplace_back(token);
}
free(argv);

if (!hasPipeline) {
if (hasProj || hasInit) {
steps.push_back(Step());
}

for (auto &word : tokens) {
if (word[0] == '+') {
word = word.substr(1);
} else if (prevWasTitle && word.find('=') == std::string::npos) {
title += " ";
title += word;
continue;
}

prevWasTitle = false;
if (starts_with(word, "proj=") && !hasInit) {
if (starts_with(word, "proj=") && !hasInit &&
steps.back().name.empty()) {
assert(hasProj);
auto stepName = word.substr(strlen("proj="));
steps.back().name = stepName;
Expand All @@ -8331,7 +8308,6 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
}
} else if (starts_with(word, "title=")) {
title = word.substr(strlen("title="));
prevWasTitle = true;
} else if (word != "step") {
const auto pos = word.find('=');
auto key = word.substr(0, pos);
Expand All @@ -8352,15 +8328,6 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
bool inPipeline = false;
bool invGlobal = false;
for (auto &word : tokens) {
if (word[0] == '+') {
word = word.substr(1);
} else if (prevWasTitle && word.find('=') == std::string::npos) {
title += " ";
title += word;
continue;
}

prevWasTitle = false;
if (word == "proj=pipeline") {
if (inPipeline) {
throw ParsingException("nested pipeline not supported");
Expand Down Expand Up @@ -8388,7 +8355,6 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
steps.back().isInit = true;
} else if (!inPipeline && starts_with(word, "title=")) {
title = word.substr(strlen("title="));
prevWasTitle = true;
} else {
const auto pos = word.find('=');
auto key = word.substr(0, pos);
Expand Down Expand Up @@ -10518,7 +10484,8 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
std::string expanded;
if (!d->title_.empty()) {
expanded = "title=";
expanded += d->title_;
expanded +=
pj_double_quote_string_param_if_needed(d->title_);
}
for (const auto &pair : d->steps_[0].paramValues) {
if (!expanded.empty())
Expand All @@ -10527,7 +10494,8 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
expanded += pair.key;
if (!pair.value.empty()) {
expanded += '=';
expanded += pair.value;
expanded += pj_double_quote_string_param_if_needed(
pair.value);
}
}
expanded += ' ';
Expand Down Expand Up @@ -10557,7 +10525,8 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
}
std::string expanded;
if (!d->title_.empty()) {
expanded = "title=" + d->title_;
expanded =
"title=" + pj_double_quote_string_param_if_needed(d->title_);
}
bool first = true;
bool has_init_term = false;
Expand All @@ -10583,7 +10552,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
expanded += pair.key;
if (!pair.value.empty()) {
expanded += '=';
expanded += pair.value;
expanded += pj_double_quote_string_param_if_needed(pair.value);
}
}

Expand Down
27 changes: 0 additions & 27 deletions src/param.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,6 @@
#include "proj.h"
#include "proj_internal.h"

static void unquote_string(char* param_str) {

size_t len = strlen(param_str);
// Remove leading and terminating spaces after equal sign
const char* equal = strstr(param_str, "=\"");
if( equal && equal - param_str + 1 > 2 && param_str[len-1] == '"' ) {
size_t dst = equal + 1 - param_str;
size_t src = dst + 1;
for( ; param_str[src]; dst++, src++)
{
if( param_str[src] == '"' ) {
if( param_str[src+1] == '"' ) {
src++;
} else {
break;
}
}
param_str[dst] = param_str[src];
}
param_str[dst] = '\0';
}

}


/* create parameter list entry */
paralist *pj_mkparam(const char *str) {
paralist *newitem;
Expand All @@ -44,7 +19,6 @@ paralist *pj_mkparam(const char *str) {
if (*str == '+')
++str;
(void)strcpy(newitem->param, str);
unquote_string(newitem->param);
}
return newitem;
}
Expand Down Expand Up @@ -86,7 +60,6 @@ paralist *pj_mkparam_ws (const char *str, const char **next_str) {
if (nullptr==newitem)
return nullptr;
memcpy(newitem->param, str, len);
unquote_string(newitem->param);

newitem->used = 0;
newitem->next = nullptr;
Expand Down