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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* If conditions not specified, table is truncated.
|
||||
|
@ -308,6 +326,16 @@ export class SQLiteDB {
|
|||
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.
|
||||
* 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];
|
||||
if (typeof value == 'undefined') {
|
||||
delete data[name];
|
||||
} else {
|
||||
data[name] = this.encodeValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -456,7 +486,7 @@ export class SQLiteDB {
|
|||
params = items;
|
||||
}
|
||||
|
||||
return [sql, params];
|
||||
return [sql, params.map(this.encodeValue)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -607,7 +637,11 @@ export class SQLiteDB {
|
|||
// Retrieve the records.
|
||||
const records = [];
|
||||
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;
|
||||
|
@ -819,6 +853,8 @@ export class SQLiteDB {
|
|||
* @return {Promise<any>} Promise resolved when updated.
|
||||
*/
|
||||
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise<any> {
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
if (!data || !Object.keys(data).length) {
|
||||
// No fields to update, consider it's done.
|
||||
return Promise.resolve();
|
||||
|
@ -850,9 +886,10 @@ export class SQLiteDB {
|
|||
* Returns the SQL WHERE conditions.
|
||||
*
|
||||
* @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'.
|
||||
*/
|
||||
whereClause(conditions: any = {}): any[] {
|
||||
whereClause(conditions: any = {}, encodeValues: boolean = true): any[] {
|
||||
if (!conditions || !Object.keys(conditions).length) {
|
||||
return ['1 = 1', []];
|
||||
}
|
||||
|
@ -861,7 +898,11 @@ export class SQLiteDB {
|
|||
params = [];
|
||||
|
||||
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) {
|
||||
where.push(key + ' IS NULL');
|
||||
|
@ -897,7 +938,7 @@ export class SQLiteDB {
|
|||
if (typeof value == 'undefined' || value === null) {
|
||||
select = field + ' IS NULL';
|
||||
} else {
|
||||
params.push(value);
|
||||
params.push(this.encodeValue(value));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"app_id" : "com.moodle.moodlemobile",
|
||||
"appname": "Moodle Mobile",
|
||||
"desktopappname": "Moodle Desktop",
|
||||
"versioncode" : 3510,
|
||||
"versioncode" : 3520,
|
||||
"versionname" : "3.5.1",
|
||||
"cache_expiration_time" : 300000,
|
||||
"default_lang" : "en",
|
||||
|
|
|
@ -346,6 +346,13 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
|||
// DBs migrated, get the version applied again.
|
||||
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 {
|
||||
return versionApplied;
|
||||
}
|
||||
|
@ -706,4 +713,68 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
|||
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