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

refs #4688 fixed a bug in parse_datasource() parsing option values wi… #4689

Merged
merged 2 commits into from
Jan 25, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions doxygen/lang/900_release_notes.dox.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- <a href="../../modules/ConnectionProvider/html/index.html">ConnectionProvider</a> module
- set connection status to \c OK after a successful manual ping operation
(<a href="https://github.com/qorelanguage/qore/issues/4685">issue 4685</a>)
- fixed a bug in @ref Qore::parse_datasource() "parse_datasource()" parsing option values with special characters
(<a href="https://github.com/qorelanguage/qore/issues/4688">issue 4688</a>)

@section qore_1_13 Qore 1.13

Expand Down
21 changes: 19 additions & 2 deletions examples/test/qore/functions/parse_datasource.qtest
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,29 @@ public class ParseDatasourceTest inherits QUnit::Test {
"host": "c:x",
}, parse_datasource("a@b%c:x"));

assertEq({
"user": "a",
"db": "b",
"host": "c:x",
"options": {
"abc@xyz:1234": "123",
},
}, parse_datasource("a@b%c:x{abc@xyz:1234=123}"));

assertEq({
"type": "d",
"user": "a",
"pass": "b",
"db": "c",
"charset": "utf8",
"host": "localhost",
"port": 5432,
}, parse_datasource("d:a/b@c(utf8)%localhost:5432"));

assertThrows("DATASOURCE-PARSE-ERROR", "empty text", sub() { parse_datasource(""); });
assertThrows("DATASOURCE-PARSE-ERROR", "missing closing parenthesis", sub() { parse_datasource("a@b("); });
assertThrows("DATASOURCE-PARSE-ERROR", "missing hostname", sub() { parse_datasource("a@b%"); });
assertThrows("DATASOURCE-PARSE-ERROR", "missing hostname", sub() { parse_datasource("a@b%{}"); });
assertThrows("DATASOURCE-PARSE-ERROR", "invalid characters present", sub() { parse_datasource("a@b%c:12x2"); });
assertThrows("DATASOURCE-PARSE-ERROR", "missing closing curly bracket", sub() { parse_datasource("a@b{"); });
assertThrows("DATASOURCE-PARSE-ERROR", "unrecognized characters at end", sub() { parse_datasource("a@b{x=y}dfgf"); });
}
}
170 changes: 73 additions & 97 deletions lib/DBI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,13 +637,80 @@ QoreHashNode* parseDatasource(const char* ds, ExceptionSink* xsink) {
return nullptr;
}

QoreStringNode* driver = nullptr;
ReferenceHolder<QoreHashNode> h(new QoreHashNode(autoTypeInfo), xsink);

// use a QoreString to create a temporary buffer
QoreString tmp(ds);
tmp.trim();
char* str = const_cast<char*>(tmp.c_str());

QoreStringNode* driver = nullptr;
ReferenceHolder<QoreHashNode> h(new QoreHashNode(autoTypeInfo), xsink);
// first remove the option substring from the string, if any
if (tmp.size() > 2 && tmp[tmp.size() - 1] == '}') {
char* p = strrchr(str, '{');
if (p) {
// remove } char from final string
tmp.terminate(tmp.size() - 1);
// terminate the string before the option string
tmp.terminate(p - str);
++p;
// parse option hash
ReferenceHolder<QoreHashNode> opt(new QoreHashNode(autoTypeInfo), xsink);

while (true) {
if (!*p) {
break;
}
char* eq = strchr(p, '=');
char* oend = strchr(p, ',');
size_t len = 0;
// if there is only an option left with no more options and no value
if (!eq && !oend) {
opt->setKeyValue(p, true, xsink);
p += strlen(p);
} else {
// if there is more than one option and the next option to be parsed has no value
if (oend && (!eq || oend < eq)) {
len = oend - p;
QoreString tmp(p, len);
opt->setKeyValue(tmp.c_str(), true, nullptr);
p += len;
} else {
// here we must have an equals sign
assert(eq);
if (eq == p) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "missing value after '=' in option "
"specification in '%s'", ds);
return nullptr;
}
*eq = '\0';
++eq;
len = oend ? oend - eq : strlen(eq);
if (opt->existsKey(p)) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "option '%s' repeated in '%s'", p, ds);
return nullptr;
}

QoreString key(p);
key.trim();

QoreString value(eq, len);
value.trim();

opt->setKeyValue(key.c_str(), new QoreStringNode(value), nullptr);

p = eq + len;
}
}
if (oend) {
++p;
}
}

h->setKeyValue("options", opt.release(), nullptr);
}
}

char* p = strchr(str, ':');
// make sure this is the driver name and not the port at the end
if (p) {
Expand Down Expand Up @@ -717,7 +784,7 @@ QoreHashNode* parseDatasource(const char* ds, ExceptionSink* xsink) {
}

// get db name
p = check_datasource_chars(str, "%:{");
p = check_datasource_chars(str, "%:");
if (!p) {
h->setKeyValue("db", new QoreStringNode(db), nullptr);
} else {
Expand All @@ -728,7 +795,7 @@ QoreHashNode* parseDatasource(const char* ds, ExceptionSink* xsink) {
str = p + 1;

if (tok == '%') {
p = check_datasource_chars(str, ":{");
p = check_datasource_chars(str, ":");
if ((p == str) || !*str) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "missing hostname string after '%%' delimeter in '%s'",
ds);
Expand All @@ -753,16 +820,7 @@ QoreHashNode* parseDatasource(const char* ds, ExceptionSink* xsink) {
assert(port);
h->setKeyValue("port", port, nullptr);

const char* pstr = str;
p = strchr(str, '{');
if (p) {
tok = *p;
*p = '\0';
str = p + 1;
} else {
str += strlen(str);
}

char* pstr = str;
while (isdigit(*pstr)) {
++pstr;
}
Expand All @@ -772,89 +830,7 @@ QoreHashNode* parseDatasource(const char* ds, ExceptionSink* xsink) {
ds);
return nullptr;
}
}

if (tok == '{') {
p = str;
{
char* str0 = str;
// open bracket count
int obc = 0;
while (true) {
if (!*p) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "missing closing curly bracket '}' in option "
"specification in '%s'", ds, str);
return nullptr;
}
if (*p == '{') {
++obc;
} else if (*p == '}') {
if (!obc) {
*p = '\0';
str = p + 1;
break;
}
--obc;
}
++p;
}
p = str0;
}

// parse option hash
ReferenceHolder<QoreHashNode> opt(new QoreHashNode(autoTypeInfo), xsink);

while (true) {
if (!*p) {
break;
}
char* eq = strchr(p, '=');
char* oend = strchr(p, ',');
size_t len = 0;
// if there is only an option left with no more options and no value
if (!eq && !oend) {
opt->setKeyValue(p, true, nullptr);
p += strlen(p);
} else {
// if there is more than one option and the next option to be parsed has no value
if (oend && (!eq || oend < eq)) {
len = oend - p;
QoreString tmp(p, len);
opt->setKeyValue(tmp.c_str(), true, nullptr);
p += len;
} else {
// here we must have an equals sign
assert(eq);
if (eq == p) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "missing value after '=' in option "
"specification in '%s'", ds);
return nullptr;
}
*eq = '\0';
++eq;
len = oend ? oend - eq : strlen(eq);
if (opt->existsKey(p)) {
xsink->raiseException(DATASOURCE_PARSE_ERROR, "option '%s' repeated in '%s'", p, ds);
return nullptr;
}

QoreString key(p);
key.trim();

QoreString value(eq, len);
value.trim();

opt->setKeyValue(key.c_str(), new QoreStringNode(value), nullptr);

p = eq + len;
}
}
if (oend) {
++p;
}
}

h->setKeyValue("options", opt.release(), nullptr);
str = pstr;
}

if (*str) {
Expand Down