Skip to content

Commit 6686727

Browse files
committed
Fixed secondary link parsing after first link was clicked
1 parent 1fde992 commit 6686727

File tree

3 files changed

+88
-32
lines changed

3 files changed

+88
-32
lines changed

crates/common/src/integrations/nextjs.rs

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,8 +1476,8 @@ mod tests {
14761476
#[test]
14771477
fn html_processor_rewrites_rsc_stream_payload_with_length_preservation() {
14781478
// RSC payloads (self.__next_f.push) are rewritten via post-processing.
1479-
// The streaming phase skips RSC push scripts, and the post-processor handles them
1480-
// to correctly handle cross-script T-chunks.
1479+
// The streaming phase skips RSC push scripts, and the HTML post-processor handles them
1480+
// at end-of-document to correctly handle cross-script T-chunks.
14811481
let html = r#"<html><body>
14821482
<script>self.__next_f.push([1,"prefix {\"inner\":\"value\"} \\\"href\\\":\\\"http://origin.example.com/dashboard\\\", \\\"link\\\":\\\"https://origin.example.com/api-test\\\" suffix"])</script>
14831483
</body></html>"#;
@@ -1508,16 +1508,9 @@ mod tests {
15081508
.process(Cursor::new(html.as_bytes()), &mut output)
15091509
.unwrap();
15101510

1511-
// Apply post-processing (this is what handles RSC push scripts)
1512-
let processed_str = String::from_utf8_lossy(&output);
1513-
let final_html = post_process_rsc_html(
1514-
&processed_str,
1515-
"origin.example.com",
1516-
"test.example.com",
1517-
"https",
1518-
);
1511+
let final_html = String::from_utf8_lossy(&output);
15191512

1520-
// RSC payloads should be rewritten via post-processing
1513+
// RSC payloads should be rewritten via end-of-document post-processing
15211514
assert!(
15221515
final_html.contains("test.example.com"),
15231516
"RSC stream payloads should be rewritten to proxy host via post-processing. Output: {}",
@@ -1558,16 +1551,9 @@ mod tests {
15581551
.process(Cursor::new(html.as_bytes()), &mut output)
15591552
.unwrap();
15601553

1561-
// Apply post-processing (this is what handles RSC push scripts)
1562-
let processed_str = String::from_utf8_lossy(&output);
1563-
let final_html = post_process_rsc_html(
1564-
&processed_str,
1565-
"origin.example.com",
1566-
"test.example.com",
1567-
"https",
1568-
);
1554+
let final_html = String::from_utf8_lossy(&output);
15691555

1570-
// RSC payloads should be rewritten via post-processing
1556+
// RSC payloads should be rewritten via end-of-document post-processing
15711557
assert!(
15721558
final_html.contains("test.example.com"),
15731559
"RSC stream payloads should be rewritten to proxy host with chunked input. Output: {}",
@@ -1621,18 +1607,7 @@ mod tests {
16211607
pipeline
16221608
.process(Cursor::new(html.as_bytes()), &mut output)
16231609
.unwrap();
1624-
1625-
// Apply post-processing (this is what handles RSC push scripts)
1626-
let processed_str = String::from_utf8_lossy(&output);
1627-
let final_html = post_process_rsc_html(
1628-
&processed_str,
1629-
"origin.example.com",
1630-
"test.example.com",
1631-
"https",
1632-
);
1633-
1634-
println!("=== Final HTML ===");
1635-
println!("{}", final_html);
1610+
let final_html = String::from_utf8_lossy(&output);
16361611

16371612
// RSC payloads should be rewritten via post-processing
16381613
assert!(

crates/common/src/rsc_flight.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ impl RscFlightUrlRewriter {
4747
request_host: &str,
4848
request_scheme: &str,
4949
) -> Self {
50+
// Normalize because some configs include a trailing slash (e.g. `https://origin/`).
51+
// If we keep the trailing slash, replacing `origin_url` inside `origin_url + "/path"`
52+
// would drop the delimiter and yield `https://proxyhostpath`.
53+
let origin_url = origin_url.trim_end_matches('/');
54+
5055
let request_url = format!("{request_scheme}://{request_host}");
5156
let origin_protocol_relative = format!("//{origin_host}");
5257
let request_protocol_relative = format!("//{request_host}");
@@ -302,6 +307,25 @@ mod tests {
302307
);
303308
}
304309

310+
#[test]
311+
fn rewrites_newline_rows_with_trailing_slash_origin_url() {
312+
let input = b"0:[\"https://origin.example.com/page\"]\n";
313+
314+
let mut rewriter = RscFlightUrlRewriter::new(
315+
"origin.example.com",
316+
"https://origin.example.com/",
317+
"proxy.example.com",
318+
"https",
319+
);
320+
321+
let output = run_rewriter(&mut rewriter, input, 8);
322+
let output_str = String::from_utf8(output).expect("should be valid UTF-8");
323+
assert_eq!(
324+
output_str, "0:[\"https://proxy.example.com/page\"]\n",
325+
"Output should rewrite URLs without dropping the path slash"
326+
);
327+
}
328+
305329
#[test]
306330
fn rewrites_t_rows_and_updates_length() {
307331
let t_content = r#"{"url":"https://origin.example.com/page"}"#;
@@ -332,6 +356,36 @@ mod tests {
332356
);
333357
}
334358

359+
#[test]
360+
fn rewrites_t_rows_with_trailing_slash_origin_url() {
361+
let t_content = r#"{"url":"https://origin.example.com/page"}"#;
362+
let json_row = "2:[\"ok\"]\n";
363+
let input = format!("1:T{:x},{}{}", t_content.len(), t_content, json_row);
364+
365+
let mut rewriter = RscFlightUrlRewriter::new(
366+
"origin.example.com",
367+
"https://origin.example.com/",
368+
"proxy.example.com",
369+
"https",
370+
);
371+
372+
let output = run_rewriter(&mut rewriter, input.as_bytes(), 7);
373+
let output_str = String::from_utf8(output).expect("should be valid UTF-8");
374+
375+
let rewritten_t_content = r#"{"url":"https://proxy.example.com/page"}"#;
376+
let expected = format!(
377+
"1:T{:x},{}{}",
378+
rewritten_t_content.len(),
379+
rewritten_t_content,
380+
json_row
381+
);
382+
383+
assert_eq!(
384+
output_str, expected,
385+
"Output should update T row lengths after rewriting without dropping the path slash"
386+
);
387+
}
388+
335389
#[test]
336390
fn handles_t_row_header_and_body_split_across_chunks() {
337391
let t_content = r#"{"url":"https://origin.example.com/page"}"#;

crates/common/src/streaming_replacer.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ pub fn create_url_replacer(
156156
request_host: &str,
157157
request_scheme: &str,
158158
) -> StreamingReplacer {
159+
// Normalize because some configs include a trailing slash (e.g. `https://origin/`).
160+
// If we keep the trailing slash, replacing `origin_url` inside `origin_url + "/path"`
161+
// would drop the delimiter and yield `https://proxyhostpath`.
162+
let origin_url = origin_url.trim_end_matches('/');
159163
let request_url = format!("{}://{}", request_scheme, request_host);
160164

161165
let mut replacements = vec![
@@ -364,6 +368,29 @@ mod tests {
364368
assert!(result.contains("//test.example.com/script.js"));
365369
}
366370

371+
#[test]
372+
fn test_url_replacer_handles_trailing_slash_origin_url() {
373+
let mut replacer = create_url_replacer(
374+
"origin.example.com",
375+
"https://origin.example.com/",
376+
"test.example.com",
377+
"https",
378+
);
379+
380+
let content = r#"Visit https://origin.example.com/news for more info"#;
381+
let processed = replacer.process_chunk(content.as_bytes(), true);
382+
let result = String::from_utf8(processed).expect("should be valid UTF-8");
383+
384+
assert!(
385+
result.contains("https://test.example.com/news"),
386+
"URL should keep the slash between host and path. Got: {result}"
387+
);
388+
assert!(
389+
!result.contains("https://test.example.comnews"),
390+
"URL should not lose the slash between host and path. Got: {result}"
391+
);
392+
}
393+
367394
#[test]
368395
fn test_process_chunk_utf8_boundary() {
369396
let mut replacer =

0 commit comments

Comments
 (0)