@@ -17,6 +17,7 @@ pub struct HtmlProcessorConfig {
1717 pub request_host : String ,
1818 pub request_scheme : String ,
1919 pub enable_prebid : bool ,
20+ pub enable_permutive : bool ,
2021}
2122
2223impl HtmlProcessorConfig {
@@ -32,6 +33,7 @@ impl HtmlProcessorConfig {
3233 request_host : request_host. to_string ( ) ,
3334 request_scheme : request_scheme. to_string ( ) ,
3435 enable_prebid : settings. prebid . auto_configure ,
36+ enable_permutive : settings. permutive . auto_configure ,
3537 }
3638 }
3739}
@@ -85,6 +87,14 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso
8587 )
8688 }
8789
90+ fn is_permutive_sdk_url ( url : & str ) -> bool {
91+ let lower = url. to_ascii_lowercase ( ) ;
92+ // Match: https://*.edge.permutive.app/*-web.js
93+ // Match: https://cdn.permutive.com/*-web.js
94+ ( lower. contains ( ".edge.permutive.app" ) || lower. contains ( "cdn.permutive.com" ) )
95+ && lower. ends_with ( "-web.js" )
96+ }
97+
8898 let rewriter_settings = RewriterSettings {
8999 element_content_handlers : vec ! [
90100 // Inject tsjs once at the start of <head>
@@ -103,12 +113,16 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso
103113 element!( "[href]" , {
104114 let patterns = patterns. clone( ) ;
105115 let rewrite_prebid = config. enable_prebid;
116+ let rewrite_permutive = config. enable_permutive;
106117 move |el| {
107118 if let Some ( href) = el. get_attribute( "href" ) {
108119 // If Prebid auto-config is enabled and this looks like a Prebid script href, rewrite to our extension
109120 if rewrite_prebid && is_prebid_script_url( & href) {
110121 let ext_src = tsjs:: ext_script_src( ) ;
111122 el. set_attribute( "href" , & ext_src) ?;
123+ } else if rewrite_permutive && is_permutive_sdk_url( & href) {
124+ let permutive_src = tsjs:: permutive_script_src( ) ;
125+ el. set_attribute( "href" , & permutive_src) ?;
112126 } else {
113127 let new_href = href
114128 . replace( & patterns. https_origin( ) , & patterns. replacement_url( ) )
@@ -125,11 +139,15 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso
125139 element!( "[src]" , {
126140 let patterns = patterns. clone( ) ;
127141 let rewrite_prebid = config. enable_prebid;
142+ let rewrite_permutive = config. enable_permutive;
128143 move |el| {
129144 if let Some ( src) = el. get_attribute( "src" ) {
130145 if rewrite_prebid && is_prebid_script_url( & src) {
131146 let ext_src = tsjs:: ext_script_src( ) ;
132147 el. set_attribute( "src" , & ext_src) ?;
148+ } else if rewrite_permutive && is_permutive_sdk_url( & src) {
149+ let permutive_src = tsjs:: permutive_script_src( ) ;
150+ el. set_attribute( "src" , & permutive_src) ?;
133151 } else {
134152 let new_src = src
135153 . replace( & patterns. https_origin( ) , & patterns. replacement_url( ) )
@@ -238,6 +256,7 @@ mod tests {
238256 request_host : "test.example.com" . to_string ( ) ,
239257 request_scheme : "https" . to_string ( ) ,
240258 enable_prebid : false ,
259+ enable_permutive : false ,
241260 }
242261 }
243262
@@ -318,6 +337,81 @@ mod tests {
318337 assert ! ( processed. contains( "/static/tsjs=tsjs-core.min.js" ) ) ;
319338 }
320339
340+ #[ test]
341+ fn test_injects_tsjs_script_and_rewrites_permutive_refs ( ) {
342+ let html = r#"<html><head>
343+ <script async src="https://myorg.edge.permutive.app/workspace-12345-web.js"></script>
344+ </head><body></body></html>"# ;
345+
346+ let mut config = create_test_config ( ) ;
347+ config. enable_permutive = true ; // enable rewriting of Permutive URLs
348+ let processor = create_html_processor ( config) ;
349+ let pipeline_config = PipelineConfig {
350+ input_compression : Compression :: None ,
351+ output_compression : Compression :: None ,
352+ chunk_size : 8192 ,
353+ } ;
354+ let mut pipeline = StreamingPipeline :: new ( pipeline_config, processor) ;
355+
356+ let mut output = Vec :: new ( ) ;
357+ let result = pipeline. process ( Cursor :: new ( html. as_bytes ( ) ) , & mut output) ;
358+ assert ! ( result. is_ok( ) ) ;
359+ let processed = String :: from_utf8_lossy ( & output) ;
360+ assert ! ( processed. contains( "/static/tsjs=tsjs-core.min.js" ) ) ;
361+ // Permutive references are rewritten to our permutive bundle when auto-configure is on
362+ assert ! ( processed. contains( "/static/tsjs=tsjs-permutive.min.js" ) ) ;
363+ assert ! ( !processed. contains( "edge.permutive.app" ) ) ;
364+ }
365+
366+ #[ test]
367+ fn test_permutive_cdn_url_rewriting ( ) {
368+ let html = r#"<html><head>
369+ <script async src="https://cdn.permutive.com/autoblog-abc123-web.js"></script>
370+ </head><body></body></html>"# ;
371+
372+ let mut config = create_test_config ( ) ;
373+ config. enable_permutive = true ;
374+ let processor = create_html_processor ( config) ;
375+ let pipeline_config = PipelineConfig {
376+ input_compression : Compression :: None ,
377+ output_compression : Compression :: None ,
378+ chunk_size : 8192 ,
379+ } ;
380+ let mut pipeline = StreamingPipeline :: new ( pipeline_config, processor) ;
381+
382+ let mut output = Vec :: new ( ) ;
383+ let result = pipeline. process ( Cursor :: new ( html. as_bytes ( ) ) , & mut output) ;
384+ assert ! ( result. is_ok( ) ) ;
385+ let processed = String :: from_utf8_lossy ( & output) ;
386+ assert ! ( processed. contains( "/static/tsjs=tsjs-permutive.min.js" ) ) ;
387+ assert ! ( !processed. contains( "cdn.permutive.com" ) ) ;
388+ }
389+
390+ #[ test]
391+ fn test_permutive_disabled_does_not_rewrite ( ) {
392+ let html = r#"<html><head>
393+ <script async src="https://myorg.edge.permutive.app/workspace-12345-web.js"></script>
394+ </head><body></body></html>"# ;
395+
396+ let mut config = create_test_config ( ) ;
397+ config. enable_permutive = false ; // disabled
398+ let processor = create_html_processor ( config) ;
399+ let pipeline_config = PipelineConfig {
400+ input_compression : Compression :: None ,
401+ output_compression : Compression :: None ,
402+ chunk_size : 8192 ,
403+ } ;
404+ let mut pipeline = StreamingPipeline :: new ( pipeline_config, processor) ;
405+
406+ let mut output = Vec :: new ( ) ;
407+ let result = pipeline. process ( Cursor :: new ( html. as_bytes ( ) ) , & mut output) ;
408+ assert ! ( result. is_ok( ) ) ;
409+ let processed = String :: from_utf8_lossy ( & output) ;
410+ // When disabled, Permutive URL should remain unchanged
411+ assert ! ( processed. contains( "edge.permutive.app" ) ) ;
412+ assert ! ( !processed. contains( "tsjs-permutive" ) ) ;
413+ }
414+
321415 #[ test]
322416 fn test_create_html_processor_url_replacement ( ) {
323417 let config = create_test_config ( ) ;
0 commit comments