Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Version 24.05.52
Fixes:
- [events] Fixed event descriptions (and custom names / count-sum-dur labels) not showing on the Events page for events whose key contains special characters (`.`, `$`, `\`, `&`, `<`, `>`, `"`, `'`)

Enterprise Fixes:
- [data-manager] Fixed editing an event whose key contains `&` creating undeletable duplicate rows in the events table

## Version 24.05.51
Security Fixes:
- [compliance-hub] The consents table now returns a fixed set of fields; a projection supplied on the request is no longer used to widen the response beyond the consent columns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/<=/g, "&le;").replace(/>=/g, "&ge;");
},
getEventLongName: function(eventKey, eventMap) {
var mapKey = eventKey.replace(/\\/g, "\\\\").replace(/\$/g, "\\u0024").replace(/\./g, "\\u002e");
//events.map is keyed by the raw event key, exactly as it appears in events.list, so do not escape it here
var mapKey = eventKey;
if (eventMap && eventMap[mapKey] && eventMap[mapKey].name) {
return eventMap[mapKey].name;
}
Expand All @@ -249,7 +250,8 @@
var allEvents = context.state.allEventsData;
var groupData = context.state.groupData.displayMap;
var eventMap = allEvents.map;
var mapKey = context.state.selectedEventName.replace(/\\/g, "\\\\").replace(/\$/g, "\\u0024").replace(/\./g, '\\u002e');
//events.map is keyed by the raw event key, exactly as it appears in events.list, so do not escape it here
var mapKey = context.state.selectedEventName;
var countString = (mapKey.startsWith('[CLY]_group') && groupData.c) ? groupData.c : (eventMap && eventMap[mapKey] && eventMap[mapKey].count) ? eventMap[mapKey].count : jQuery.i18n.map["events.table.count"];
var sumString = (mapKey.startsWith('[CLY]_group') && groupData.s) ? groupData.s : (eventMap && eventMap[mapKey] && eventMap[mapKey].sum) ? eventMap[mapKey].sum : jQuery.i18n.map["events.table.sum"];
var durString = (mapKey.startsWith('[CLY]_group') && groupData.d) ? groupData.d : (eventMap && eventMap[mapKey] && eventMap[mapKey].dur) ? eventMap[mapKey].dur : jQuery.i18n.map["events.table.dur"];
Expand Down Expand Up @@ -886,9 +888,7 @@
return countlyAllEvents.service.fetchAllEventsData(context, period)
.then(function(res) {
if (res) {
if (Array.isArray(res.list)) {
res.list = res.list.map(eventName => countlyCommon.unescapeHtml(eventName));
}
// setAllEventsData decodes res.list and res.map keys in place
context.commit("setAllEventsData", res);
if ((!context.state.selectedEventName) || (res.map && res.map[context.state.selectedEventName] && !res.map[context.state.selectedEventName].is_visible) || (res.list && res.list.indexOf(context.state.selectedEventName) === -1)) {
var appId = countlyCommon.ACTIVE_APP_ID;
Expand Down Expand Up @@ -1090,6 +1090,29 @@

var allEventsMutations = {
setAllEventsData: function(state, value) {
// The API HTML-escapes every key and value on the way out (common.returnOutput),
// but the rest of this store works with raw event keys - selectedEventName is
// taken from `list`. Normalise `list` and the `map` keys together here, the one
// point both fetch paths commit through, so lookups like map[selectedEventName]
// don't silently miss for keys containing & < > " '. Keys without those chars
// (dots, dashes) were unaffected, which is why only some events lost their
// description on the Events page.
if (value && Array.isArray(value.list)) {
value.list = value.list.map(function(eventName) {
return countlyCommon.unescapeHtml(eventName);
});
}
if (value && value.map && typeof value.map === "object") {
//null prototype: event keys are arbitrary strings, and assigning a
//"__proto__" key on a plain object reparents the map instead of adding
//an own property, which would let unrelated key lookups resolve through
//the assigned value
var decodedMap = Object.create(null);
Object.keys(value.map).forEach(function(eventKey) {
decodedMap[countlyCommon.unescapeHtml(eventKey)] = value.map[eventKey];
});
value.map = decodedMap;
}
state.allEventsData = value;
},
setAllEventsList: function(state, value) {
Expand Down
20 changes: 18 additions & 2 deletions plugins/data-manager/frontend/public/javascripts/countly.views.js
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,9 @@
var isVisible = command === 'visible';
var events = [];
rows.forEach(function(row) {
events.push(row.key);
// row.key is still API-escaped; change_visibility writes events.map[key]
// directly, so submitting it raw would create a ghost map entry
events.push(countlyCommon.unescapeHtml(row.key));
});
this.$store.dispatch('countlyDataManager/changeVisibility', { events: events, isVisible: isVisible }).then(function() {
countlyEvent.refreshEvents();
Expand Down Expand Up @@ -1324,8 +1326,22 @@
},
handleEdit: function() {
var event = JSON.parse(JSON.stringify(this.event));
event.segments = this.segments;
event.segments = JSON.parse(JSON.stringify(this.segments));
event.isEditMode = true;
// The store holds values exactly as the API returned them, and common.returnOutput
// HTML-escapes every string on the way out. The events table decodes these before
// opening the same drawer (see the 'dm-open-edit-event-drawer' handler) - do the
// same here, otherwise a key like "a & b" is submitted back as "a &amp; b" and
// forks a second drill_meta doc / events.map entry that shows up as a ghost row.
event.key = countlyCommon.unescapeHtml(event.key);
event.e = countlyCommon.unescapeHtml(event.e);
event.name = countlyCommon.unescapeHtml(event.name);
event.description = countlyCommon.unescapeHtml(event.description);
event.categoryName = countlyCommon.unescapeHtml(event.categoryName);
event.segments = event.segments.map(function(seg) {
seg.name = countlyCommon.unescapeHtml(seg.name);
return seg;
});
this.openDrawer("events", event);
},
handleEditSegment: function(seg) {
Expand Down
Loading