@@ -9,10 +9,37 @@ const URL_FIELDS = new Set([
99 "privacyChoicesUrl" ,
1010] ) ;
1111
12+ /** Multi-line fields where newlines (\n) are valid content. */
13+ const MULTILINE_FIELDS = new Set ( [
14+ "description" ,
15+ "whatsNew" ,
16+ "promotionalText" ,
17+ ] ) ;
18+
19+ /**
20+ * Strip control characters that ASC rejects (null, carriage return, tab,
21+ * escape, zero-width chars, etc.). For multi-line fields, preserve \n.
22+ */
23+ function stripControlChars ( value : string , allowNewlines : boolean ) : string {
24+ // \x00-\x1f covers null, tab, newline, carriage return, escape, etc.
25+ // \x7f is DEL. \u200b-\u200f and \ufeff are zero-width/invisible chars.
26+ if ( allowNewlines ) {
27+ // Keep \n, strip everything else
28+ return value . replace ( / [ \x00 - \x09 \x0b - \x1f \x7f \u200b - \u200f \ufeff ] / g, "" ) ;
29+ }
30+ return value . replace ( / [ \x00 - \x1f \x7f \u200b - \u200f \ufeff ] / g, "" ) ;
31+ }
32+
1233function cleanAttributes ( attrs : Record < string , unknown > ) : Record < string , unknown > {
1334 const cleaned : Record < string , unknown > = { } ;
1435 for ( const [ k , v ] of Object . entries ( attrs ) ) {
15- cleaned [ k ] = URL_FIELDS . has ( k ) && v === "" ? null : v ;
36+ if ( URL_FIELDS . has ( k ) && v === "" ) {
37+ cleaned [ k ] = null ;
38+ } else if ( typeof v === "string" ) {
39+ cleaned [ k ] = stripControlChars ( v , MULTILINE_FIELDS . has ( k ) ) ;
40+ } else {
41+ cleaned [ k ] = v ;
42+ }
1643 }
1744 return cleaned ;
1845}
0 commit comments