Api: Fix favorites with non-ascii names could overlap

This commit is contained in:
nathan 2024-03-04 15:12:54 -07:00
parent 98cf3415ca
commit b1f20ddbb8
3 changed files with 38 additions and 5 deletions

View File

@ -20,7 +20,6 @@ import {et2_valueWidget} from "../etemplate/et2_core_valueWidget";
import {nm_action} from "../etemplate/et2_extension_nextmatch_actions";
import {Et2Dialog} from "../etemplate/Et2Dialog/Et2Dialog";
import {Et2Favorites} from "../etemplate/Et2Favorites/Et2Favorites";
import {EgwAction} from "../egw_action/EgwAction";
/**
* Type for push-message
@ -940,7 +939,7 @@ export abstract class EgwApp
{
// Get current state
// Make sure it's an object - deep copy to prevent references in sub-objects (col_filters)
state = jQuery.extend(true, {}, this.getState(), state || {});
state = {...this.getState(), ...(state || {})};
this._create_favorite_popup(state);
@ -1007,8 +1006,9 @@ export abstract class EgwApp
let filter_list = [];
let add_to_popup = function(arr, inset = "")
{
jQuery.each(arr, function(index, filter)
Object.keys(arr).forEach((index) =>
{
let filter = arr[index];
filter_list.push({
label: inset + index.toString(),
value: (typeof filter != "object" ? "" + filter : "")
@ -1022,7 +1022,7 @@ export abstract class EgwApp
add_to_popup(data.content.state);
data.content.current_filters = filter_list;
let save_callback = (button, value) =>
let save_callback = async(button, value) =>
{
if(button !== Et2Dialog.OK_BUTTON)
{
@ -1034,6 +1034,16 @@ export abstract class EgwApp
// Add to the list
value.name = (<string>value.name).replace(/(<([^>]+)>)/ig, "");
let safe_name = (<string>value.name).replace(/[^A-Za-z0-9-_]/g, "_");
if(safe_name != value.name)
{
// Check if the label matches an existing preference, consider it an update
let existing = this.egw.preference(favorite_prefix + safe_name, this.appname);
if(existing && existing.name !== value.name)
{
// Name mis-match, this is a new favorite with the same safe name
safe_name += "_" + await this.egw.hashString(value.name);
}
}
let favorite = {
name: value.name,
group: value.group || false,
@ -1066,7 +1076,7 @@ export abstract class EgwApp
if(this.sidebox)
{
// Remove any existing with that name
jQuery('[data-id="' + safe_name + '"]', this.sidebox).remove();
this.sidebox.get(0).querySelectorAll('[data-id="' + safe_name + '"]').forEach(e => e.remove());
// Create new item
var html = "<li data-id='" + safe_name + "' data-group='" + favorite.group + "' class='ui-menu-item' role='menuitem'>\n";

View File

@ -666,6 +666,15 @@ declare interface IegwGlobal
* @return {string}
*/
encodePathComponent(_comp : string) : string;
/**
* Hash a string
*
* @param string
*/
async
hashString(name : any) : Promise<string>;
/**
* Escape HTML special chars, just like PHP
*

View File

@ -338,6 +338,20 @@ egw.extend('utils', egw.MODULE_GLOBAL, function()
return _comp.replace(/%/g,'%25').replace(/#/g,'%23').replace(/\?/g,'%3F').replace(/\//g,'');
},
/**
* Hash a string
*
* @param string
*/
async hashString(string)
{
const data = (new TextEncoder()).encode(string);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
return hashHex;
},
/**
* Escape HTML special chars, just like PHP
*