MOBILE-2471 sqlite: Escape multi-byte characters
parent
ab3da0f38c
commit
e3db4afe22
|
@ -960,9 +960,10 @@ export class CoreSite {
|
||||||
|
|
||||||
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
||||||
|
|
||||||
const sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?';
|
const sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ? ESCAPE ?';
|
||||||
|
const params = [this.db.encodeValue(key).replace(/%/g, '\\%') + '%', '\\'];
|
||||||
|
|
||||||
return this.db.execute(sql, [key + '%']);
|
return this.db.execute(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -259,6 +259,24 @@ export class SQLiteDB {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a value returned from the database if it's a string.
|
||||||
|
*
|
||||||
|
* @param {any} value The value.
|
||||||
|
* @return {any} The decoded string or the original value if it's not a string.
|
||||||
|
*/
|
||||||
|
decodeValue(value: any): any {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
value = decodeURI(value);
|
||||||
|
} catch (ex) {
|
||||||
|
// Error, use the original value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the records from a table where all the given conditions met.
|
* Delete the records from a table where all the given conditions met.
|
||||||
* If conditions not specified, table is truncated.
|
* If conditions not specified, table is truncated.
|
||||||
|
@ -308,6 +326,16 @@ export class SQLiteDB {
|
||||||
return this.execute(`DELETE FROM ${table} ${select}`, params);
|
return this.execute(`DELETE FROM ${table} ${select}`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a value that will be inserted into the database or compared to values in the database.
|
||||||
|
*
|
||||||
|
* @param {any} value The value.
|
||||||
|
* @return {any} The encoded string or the original value if it's not a string.
|
||||||
|
*/
|
||||||
|
encodeValue(value: any): any {
|
||||||
|
return (typeof value === 'string') ? encodeURI(value) : value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a SQL query.
|
* Execute a SQL query.
|
||||||
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
|
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
|
||||||
|
@ -352,6 +380,8 @@ export class SQLiteDB {
|
||||||
const value = data[name];
|
const value = data[name];
|
||||||
if (typeof value == 'undefined') {
|
if (typeof value == 'undefined') {
|
||||||
delete data[name];
|
delete data[name];
|
||||||
|
} else {
|
||||||
|
data[name] = this.encodeValue(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,7 +486,7 @@ export class SQLiteDB {
|
||||||
params = items;
|
params = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [sql, params];
|
return [sql, params.map(this.encodeValue)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -607,7 +637,11 @@ export class SQLiteDB {
|
||||||
// Retrieve the records.
|
// Retrieve the records.
|
||||||
const records = [];
|
const records = [];
|
||||||
for (let i = 0; i < result.rows.length; i++) {
|
for (let i = 0; i < result.rows.length; i++) {
|
||||||
records.push(result.rows.item(i));
|
const record = result.rows.item(i);
|
||||||
|
for (const key in record) {
|
||||||
|
record[key] = this.decodeValue(record[key]);
|
||||||
|
}
|
||||||
|
records.push(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
|
@ -819,6 +853,8 @@ export class SQLiteDB {
|
||||||
* @return {Promise<any>} Promise resolved when updated.
|
* @return {Promise<any>} Promise resolved when updated.
|
||||||
*/
|
*/
|
||||||
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise<any> {
|
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise<any> {
|
||||||
|
this.formatDataToInsert(data);
|
||||||
|
|
||||||
if (!data || !Object.keys(data).length) {
|
if (!data || !Object.keys(data).length) {
|
||||||
// No fields to update, consider it's done.
|
// No fields to update, consider it's done.
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -850,9 +886,10 @@ export class SQLiteDB {
|
||||||
* Returns the SQL WHERE conditions.
|
* Returns the SQL WHERE conditions.
|
||||||
*
|
*
|
||||||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||||
|
* @param {boolean} [encodeValues=true] Encode condiiton values. True by default.
|
||||||
* @return {any[]} An array list containing sql 'where' part and 'params'.
|
* @return {any[]} An array list containing sql 'where' part and 'params'.
|
||||||
*/
|
*/
|
||||||
whereClause(conditions: any = {}): any[] {
|
whereClause(conditions: any = {}, encodeValues: boolean = true): any[] {
|
||||||
if (!conditions || !Object.keys(conditions).length) {
|
if (!conditions || !Object.keys(conditions).length) {
|
||||||
return ['1 = 1', []];
|
return ['1 = 1', []];
|
||||||
}
|
}
|
||||||
|
@ -861,7 +898,11 @@ export class SQLiteDB {
|
||||||
params = [];
|
params = [];
|
||||||
|
|
||||||
for (const key in conditions) {
|
for (const key in conditions) {
|
||||||
const value = conditions[key];
|
let value = conditions[key];
|
||||||
|
|
||||||
|
if (encodeValues) {
|
||||||
|
value = this.encodeValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof value == 'undefined' || value === null) {
|
if (typeof value == 'undefined' || value === null) {
|
||||||
where.push(key + ' IS NULL');
|
where.push(key + ' IS NULL');
|
||||||
|
@ -897,7 +938,7 @@ export class SQLiteDB {
|
||||||
if (typeof value == 'undefined' || value === null) {
|
if (typeof value == 'undefined' || value === null) {
|
||||||
select = field + ' IS NULL';
|
select = field + ' IS NULL';
|
||||||
} else {
|
} else {
|
||||||
params.push(value);
|
params.push(this.encodeValue(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"app_id" : "com.moodle.moodlemobile",
|
"app_id" : "com.moodle.moodlemobile",
|
||||||
"appname": "Moodle Mobile",
|
"appname": "Moodle Mobile",
|
||||||
"desktopappname": "Moodle Desktop",
|
"desktopappname": "Moodle Desktop",
|
||||||
"versioncode" : 3510,
|
"versioncode" : 3520,
|
||||||
"versionname" : "3.5.1",
|
"versionname" : "3.5.1",
|
||||||
"cache_expiration_time" : 300000,
|
"cache_expiration_time" : 300000,
|
||||||
"default_lang" : "en",
|
"default_lang" : "en",
|
||||||
|
|
|
@ -346,6 +346,13 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
// DBs migrated, get the version applied again.
|
// DBs migrated, get the version applied again.
|
||||||
return this.configProvider.get(this.VERSION_APPLIED, 0);
|
return this.configProvider.get(this.VERSION_APPLIED, 0);
|
||||||
});
|
});
|
||||||
|
} else if (versionCode >= 3520 && versionApplied < 3520 && versionApplied > 0) {
|
||||||
|
// Encode special characters in the contents of all DBs to work around Unicode bugs in the Cordova plugin.
|
||||||
|
// This is not needed if the DBs are created from scratch, because all content is already encoded.
|
||||||
|
// We do this before any other update because SQLiteDB methods expect the content to be encoded.
|
||||||
|
return this.encodeAllDBs().then(() => {
|
||||||
|
return versionApplied;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return versionApplied;
|
return versionApplied;
|
||||||
}
|
}
|
||||||
|
@ -706,4 +713,68 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
return this.utils.allPromises(promises);
|
return this.utils.allPromises(promises);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode all DBs to escape special characters.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected encodeAllDBs(): Promise<any> {
|
||||||
|
// First encode the app DB.
|
||||||
|
return this.encodeDB(this.appProvider.getDB()).then(() => {
|
||||||
|
// Now encode all site DBs.
|
||||||
|
return this.sitesProvider.getSitesIds();
|
||||||
|
}).then((ids) => {
|
||||||
|
return this.utils.allPromises(ids.map((siteId) => {
|
||||||
|
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||||
|
return this.encodeDB(db);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode content of a certain DB to escape special characters.
|
||||||
|
*
|
||||||
|
* @param {SQLiteDB} db The DB.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected encodeDB(db: SQLiteDB): Promise<any> {
|
||||||
|
const sql = 'SELECT tbl_name FROM sqlite_master WHERE type = ?';
|
||||||
|
const params = ['table'];
|
||||||
|
|
||||||
|
return db.execute(sql, params).then((result) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < result.rows.length; i++) {
|
||||||
|
const table = result.rows.item(i).tbl_name;
|
||||||
|
promises.push(this.encodeTable(db, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode content of a certain table to escape special characters.
|
||||||
|
*
|
||||||
|
* @param {SQLiteDB} db The DB.
|
||||||
|
* @param {string} table Name of the table.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected encodeTable(db: SQLiteDB, table: string): Promise<any> {
|
||||||
|
const sql = 'SELECT * FROM ' + table;
|
||||||
|
|
||||||
|
return db.execute(sql).then((result) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < result.rows.length; i++) {
|
||||||
|
const record = result.rows.item(i);
|
||||||
|
const selectAndParams = db.whereClause(record, false);
|
||||||
|
promises.push(db.updateRecordsWhere(table, record, selectAndParams[0], selectAndParams[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue