1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-15 01:04:37 +02:00

Allow [, #, and space as the first character in database names.

iniLoad() was trimming lines which meant that a leading space would not pass checksum validation when a manifest was reloaded. Remove the trims since files we write should never contain extraneous spaces. This further diverges the format for the functions that read conf files (e.g. pgbackrest.conf) and those that read info (e.g. manifest) files.

While we are at it also allow [ and # as initial characters. # was reserved for comments but we never put comments into info files. [ denotes a section but we can get around this by never allowing arrays as values in info files, so if a line ends in ] it must be a section. This is currently the case but enforce it by adding an assert to info/info.c.
This commit is contained in:
David Steele
2020-10-24 11:07:07 -04:00
committed by GitHub
parent c573ef2814
commit 76cfd8ca70
7 changed files with 46 additions and 34 deletions

View File

@ -14,6 +14,18 @@
<release-list> <release-list>
<release date="XXXX-XX-XX" version="2.31dev" title="UNDER DEVELOPMENT"> <release date="XXXX-XX-XX" version="2.31dev" title="UNDER DEVELOPMENT">
<release-core-list> <release-core-list>
<release-bug-list>
<release-item>
<release-item-contributor-list>
<release-item-ideator id="jefferson.alexandre"/>
<release-item-reviewer id="stefan.fercot"/>
<release-item-reviewer id="cynthia.shang"/>
</release-item-contributor-list>
<p>Allow <code>[</code>, <code>#</code>, and <code>space</code> as the first character in database names.</p>
</release-item>
</release-bug-list>
<release-feature-list> <release-feature-list>
<release-item> <release-item>
<release-item-contributor-list> <release-item-contributor-list>
@ -9028,6 +9040,11 @@
<contributor-id type="github">jmccormick2001</contributor-id> <contributor-id type="github">jmccormick2001</contributor-id>
</contributor> </contributor>
<contributor id="jefferson.alexandre">
<contributor-name-display>Jefferson Alexandre</contributor-name-display>
<contributor-id type="github">jalexandre0</contributor-id>
</contributor>
<contributor id="jens.wilke"> <contributor id="jens.wilke">
<contributor-name-display>Jens Wilke</contributor-name-display> <contributor-name-display>Jens Wilke</contributor-name-display>
<contributor-id type="github">jwpit</contributor-id> <contributor-id type="github">jwpit</contributor-id>

View File

@ -358,19 +358,15 @@ iniLoad(
do do
{ {
const String *line = strTrim(ioReadLineParam(read, true)); const String *line = ioReadLineParam(read, true);
const char *linePtr = strZ(line); const char *linePtr = strZ(line);
// Only interested in lines that are not blank or comments // Only interested in lines that are not blank
if (strSize(line) > 0 && linePtr[0] != '#') if (strSize(line) > 0)
{ {
// Looks like this line is a section // The line is a section. Since the value must be valid JSON this means that the value must never be an array.
if (linePtr[0] == '[') if (linePtr[0] == '[' && linePtr[strSize(line) - 1] == ']')
{ {
// Make sure the section ends with ]
if (linePtr[strSize(line) - 1] != ']')
THROW_FMT(FormatError, "ini section should end with ] at line %u: %s", lineIdx + 1, linePtr);
// Assign section // Assign section
MEM_CONTEXT_PRIOR_BEGIN() MEM_CONTEXT_PRIOR_BEGIN()
{ {
@ -378,7 +374,7 @@ iniLoad(
} }
MEM_CONTEXT_PRIOR_END(); MEM_CONTEXT_PRIOR_END();
} }
// Else it should be a key/value // Else it is a key/value
else else
{ {
if (section == NULL) if (section == NULL)
@ -405,8 +401,8 @@ iniLoad(
retry = false; retry = false;
// Get key/value // Get key/value
key = strTrim(strNewN(linePtr, (size_t)(lineEqual - linePtr))); key = strNewN(linePtr, (size_t)(lineEqual - linePtr));
value = strTrim(strNew(lineEqual + 1)); value = strNew(lineEqual + 1);
// Check that the value is valid JSON // Check that the value is valid JSON
TRY_BEGIN() TRY_BEGIN()

View File

@ -27,7 +27,8 @@ Functions
// Move to a new parent mem context // Move to a new parent mem context
Ini *iniMove(Ini *this, MemContext *parentNew); Ini *iniMove(Ini *this, MemContext *parentNew);
// Parse ini from a string // Parse ini from a string. Comments are ignored and additional whitespace around sections, keys, and values is trimmed. Should be
// used *only* to read user-generated config files, for code-generated info files see iniLoad().
void iniParse(Ini *this, const String *content); void iniParse(Ini *this, const String *content);
// Set an ini value // Set an ini value
@ -62,7 +63,9 @@ void iniFree(Ini *this);
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Helper Functions Helper Functions
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
// Load an ini file and return data to a callback // Load an ini file and return data to a callback. Intended to read info files that were generated by code so do not have comments
// or extraneous spaces, and where all values are valid JSON. This allows syntax characters such as [, =, #, and whitespace to be
// used in keys.
void iniLoad( void iniLoad(
IoRead *read, IoRead *read,
void (*callbackFunction)(void *data, const String *section, const String *key, const String *value, const Variant *valueVar), void (*callbackFunction)(void *data, const String *section, const String *key, const String *value, const Variant *valueVar),

View File

@ -351,6 +351,9 @@ infoSaveValue(InfoSave *infoSaveData, const String *section, const String *key,
ASSERT(section != NULL); ASSERT(section != NULL);
ASSERT(key != NULL); ASSERT(key != NULL);
ASSERT(jsonValue != NULL); ASSERT(jsonValue != NULL);
ASSERT(strSize(jsonValue) != 0);
// The JSON value must not be an array because this may be confused with a section in the ini file
ASSERT(strZ(jsonValue)[0] != '[');
// Save section // Save section
if (infoSaveData->sectionLast == NULL || !strEq(section, infoSaveData->sectionLast)) if (infoSaveData->sectionLast == NULL || !strEq(section, infoSaveData->sectionLast))

View File

@ -419,7 +419,7 @@ testBackupPqScript(unsigned int pgVersion, time_t backupTimeStart, TestBackupPqS
// Start backup // Start backup
HRNPQ_MACRO_ADVISORY_LOCK(1, true), HRNPQ_MACRO_ADVISORY_LOCK(1, true),
HRNPQ_MACRO_START_BACKUP_GE_10(1, param.startFast, lsnStartStr, walSegmentStart), HRNPQ_MACRO_START_BACKUP_GE_10(1, param.startFast, lsnStartStr, walSegmentStart),
HRNPQ_MACRO_DATABASE_LIST_1(1, "test1"), HRNPQ_MACRO_DATABASE_LIST_1(1, " test1"),
HRNPQ_MACRO_TABLESPACE_LIST_1(1, 32768, "tblspc32768"), HRNPQ_MACRO_TABLESPACE_LIST_1(1, 32768, "tblspc32768"),
// Get copy start time // Get copy start time

View File

@ -34,15 +34,6 @@ testRun(void)
TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini"); TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini");
TEST_RESULT_STR_Z(result, "", " check ini"); TEST_RESULT_STR_Z(result, "", " check ini");
// Invalid section
// -------------------------------------------------------------------------------------------------------------------------
iniBuf = BUFSTRZ(
" [section ");
TEST_ERROR(
iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), FormatError,
"ini section should end with ] at line 1: [section");
// Key outside of section // Key outside of section
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
iniBuf = BUFSTRZ( iniBuf = BUFSTRZ(
@ -84,9 +75,8 @@ testRun(void)
TEST_TITLE("one section"); TEST_TITLE("one section");
iniBuf = BUFSTRZ( iniBuf = BUFSTRZ(
"# comment\n"
"[section1]\n" "[section1]\n"
"key1=\"value1\"\n" " key1 =\"value1\"\n"
"key2=\"value2\"\n" "key2=\"value2\"\n"
"key=3==\"value3\"\n" "key=3==\"value3\"\n"
"==\"=\""); "==\"=\"");
@ -95,7 +85,7 @@ testRun(void)
TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini"); TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini");
TEST_RESULT_STR_Z( TEST_RESULT_STR_Z(
result, result,
"section1:key1:\"value1\"\n" "section1: key1 :\"value1\"\n"
"section1:key2:\"value2\"\n" "section1:key2:\"value2\"\n"
"section1:key=3=:\"value3\"\n" "section1:key=3=:\"value3\"\n"
"section1:=:\"=\"\n", "section1:=:\"=\"\n",
@ -104,22 +94,21 @@ testRun(void)
// Two sections // Two sections
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
iniBuf = BUFSTRZ( iniBuf = BUFSTRZ(
"# comment\n"
"[section1]\n" "[section1]\n"
"key1=\"value1\"\n" "[key1=\"value1\"\n"
"key2=\"value2\"\n" "key2=\"value2\"\n"
"\n" "\n"
"[section2]\n" "[section2]\n"
"\n" "\n"
"key2=\"value2\""); "#key2=\"value2\"");
result = strNew(""); result = strNew("");
TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini"); TEST_RESULT_VOID(iniLoad(ioBufferReadNew(iniBuf), testIniLoadCallback, result), "load ini");
TEST_RESULT_STR_Z( TEST_RESULT_STR_Z(
result, result,
"section1:key1:\"value1\"\n" "section1:[key1:\"value1\"\n"
"section1:key2:\"value2\"\n" "section1:key2:\"value2\"\n"
"section2:key2:\"value2\"\n", "section2:#key2:\"value2\"\n",
" check ini"); " check ini");
} }

View File

@ -1714,8 +1714,10 @@ testRun(void)
#define TEST_MANIFEST_DB \ #define TEST_MANIFEST_DB \
"\n" \ "\n" \
"[db]\n" \ "[db]\n" \
" mail\t={\"db-id\":16456,\"db-last-system-id\":12168}\n" \
"#={\"db-id\":16453,\"db-last-system-id\":12168}\n" \
"=={\"db-id\":16455,\"db-last-system-id\":12168}\n" \ "=={\"db-id\":16455,\"db-last-system-id\":12168}\n" \
"mail={\"db-id\":16456,\"db-last-system-id\":12168}\n" \ "[={\"db-id\":16454,\"db-last-system-id\":12168}\n" \
"postgres={\"db-id\":12173,\"db-last-system-id\":12168}\n" \ "postgres={\"db-id\":12173,\"db-last-system-id\":12168}\n" \
"template0={\"db-id\":12168,\"db-last-system-id\":12168}\n" \ "template0={\"db-id\":12168,\"db-last-system-id\":12168}\n" \
"template1={\"db-id\":1,\"db-last-system-id\":12168}\n" \ "template1={\"db-id\":1,\"db-last-system-id\":12168}\n" \
@ -1813,8 +1815,10 @@ testRun(void)
TEST_MANIFEST_TARGET TEST_MANIFEST_TARGET
"\n" "\n"
"[db]\n" "[db]\n"
" mail\t={\"db-id\":16456,\"db-last-system-id\":12168}\n"
"#={\"db-id\":16453,\"db-last-system-id\":12168}\n"
"=={\"db-id\":16455,\"db-last-system-id\":12168}\n" "=={\"db-id\":16455,\"db-last-system-id\":12168}\n"
"mail={\"db-id\":16456,\"db-last-system-id\":12168}\n" "[={\"db-id\":16454,\"db-last-system-id\":12168}\n"
"postgres={\"db-id\":12173,\"db-last-system-id\":12168}\n" "postgres={\"db-id\":12173,\"db-last-system-id\":12168}\n"
TEST_MANIFEST_FILE TEST_MANIFEST_FILE
TEST_MANIFEST_FILE_DEFAULT TEST_MANIFEST_FILE_DEFAULT