Skip to content

Commit c072b32

Browse files
jamwilKeats
authored andcommitted
Fix --base-url improper path and protocol handling using zola serve (#2311)
* Fix --base-url improper path and protocol handling. * Fix formatting.
1 parent aa81986 commit c072b32

File tree

1 file changed

+113
-27
lines changed

1 file changed

+113
-27
lines changed

src/cmd/serve.rs

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,34 @@ fn set_serve_error(msg: &'static str, e: errors::Error) {
9292
}
9393
}
9494

95-
async fn handle_request(req: Request<Body>, mut root: PathBuf) -> Result<Response<Body>> {
95+
async fn handle_request(
96+
req: Request<Body>,
97+
mut root: PathBuf,
98+
base_path: String,
99+
) -> Result<Response<Body>> {
100+
let path_str = req.uri().path();
101+
if !path_str.starts_with(&base_path) {
102+
return Ok(not_found());
103+
}
104+
105+
let trimmed_path = &path_str[base_path.len() - 1..];
106+
96107
let original_root = root.clone();
97108
let mut path = RelativePathBuf::new();
98109
// https://zola.discourse.group/t/percent-encoding-for-slugs/736
99-
let decoded = match percent_encoding::percent_decode_str(req.uri().path()).decode_utf8() {
110+
let decoded = match percent_encoding::percent_decode_str(trimmed_path).decode_utf8() {
100111
Ok(d) => d,
101112
Err(_) => return Ok(not_found()),
102113
};
103114

104-
for c in decoded.split('/') {
115+
let decoded_path = if base_path != "/" && decoded.starts_with(&base_path) {
116+
// Remove the base_path from the request path before processing
117+
decoded[base_path.len()..].to_string()
118+
} else {
119+
decoded.to_string()
120+
};
121+
122+
for c in decoded_path.split('/') {
105123
path.push(c);
106124
}
107125

@@ -318,6 +336,39 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st
318336
}
319337
}
320338

339+
fn construct_url(base_url: &str, no_port_append: bool, interface_port: u16) -> String {
340+
if base_url == "/" {
341+
return String::from("/");
342+
}
343+
344+
let (protocol, stripped_url) = match base_url {
345+
url if url.starts_with("http://") => ("http://", &url[7..]),
346+
url if url.starts_with("https://") => ("https://", &url[8..]),
347+
url => ("http://", url),
348+
};
349+
350+
let (domain, path) = {
351+
let parts: Vec<&str> = stripped_url.splitn(2, '/').collect();
352+
if parts.len() > 1 {
353+
(parts[0], format!("/{}", parts[1]))
354+
} else {
355+
(parts[0], String::new())
356+
}
357+
};
358+
359+
let full_address = if no_port_append {
360+
format!("{}{}{}", protocol, domain, path)
361+
} else {
362+
format!("{}{}:{}{}", protocol, domain, interface_port, path)
363+
};
364+
365+
if full_address.ends_with('/') {
366+
full_address
367+
} else {
368+
format!("{}/", full_address)
369+
}
370+
}
371+
321372
#[allow(clippy::too_many_arguments)]
322373
fn create_new_site(
323374
root_dir: &Path,
@@ -330,7 +381,7 @@ fn create_new_site(
330381
include_drafts: bool,
331382
mut no_port_append: bool,
332383
ws_port: Option<u16>,
333-
) -> Result<(Site, SocketAddr)> {
384+
) -> Result<(Site, SocketAddr, String)> {
334385
SITE_CONTENT.write().unwrap().clear();
335386

336387
let mut site = Site::new(root_dir, config_file)?;
@@ -345,24 +396,10 @@ fn create_new_site(
345396
|u| u.to_string(),
346397
);
347398

348-
let base_url = if base_url == "/" {
349-
String::from("/")
350-
} else {
351-
let base_address = if no_port_append {
352-
base_url.to_string()
353-
} else {
354-
format!("{}:{}", base_url, interface_port)
355-
};
356-
357-
if site.config.base_url.ends_with('/') {
358-
format!("http://{}/", base_address)
359-
} else {
360-
format!("http://{}", base_address)
361-
}
362-
};
399+
let constructed_base_url = construct_url(&base_url, no_port_append, interface_port);
363400

364401
site.enable_serve_mode();
365-
site.set_base_url(base_url);
402+
site.set_base_url(constructed_base_url.clone());
366403
if let Some(output_dir) = output_dir {
367404
if !force && output_dir.exists() {
368405
return Err(Error::msg(format!(
@@ -384,7 +421,7 @@ fn create_new_site(
384421
messages::notify_site_size(&site);
385422
messages::warn_about_ignored_pages(&site);
386423
site.build()?;
387-
Ok((site, address))
424+
Ok((site, address, constructed_base_url))
388425
}
389426

390427
#[allow(clippy::too_many_arguments)]
@@ -403,7 +440,7 @@ pub fn serve(
403440
utc_offset: UtcOffset,
404441
) -> Result<()> {
405442
let start = Instant::now();
406-
let (mut site, bind_address) = create_new_site(
443+
let (mut site, bind_address, constructed_base_url) = create_new_site(
407444
root_dir,
408445
interface,
409446
interface_port,
@@ -415,6 +452,11 @@ pub fn serve(
415452
no_port_append,
416453
None,
417454
)?;
455+
let base_path = match constructed_base_url.splitn(4, '/').nth(3) {
456+
Some(path) => format!("/{}", path),
457+
None => "/".to_string(),
458+
};
459+
418460
messages::report_elapsed_time(start);
419461

420462
// Stop right there if we can't bind to the address
@@ -479,19 +521,27 @@ pub fn serve(
479521
rt.block_on(async {
480522
let make_service = make_service_fn(move |_| {
481523
let static_root = static_root.clone();
524+
let base_path = base_path.clone();
482525

483526
async {
484527
Ok::<_, hyper::Error>(service_fn(move |req| {
485-
response_error_injector(handle_request(req, static_root.clone()))
528+
response_error_injector(handle_request(
529+
req,
530+
static_root.clone(),
531+
base_path.clone(),
532+
))
486533
}))
487534
}
488535
});
489536

490537
let server = Server::bind(&bind_address).serve(make_service);
491538

492-
println!("Web server is available at http://{}\n", bind_address);
539+
println!(
540+
"Web server is available at {} (bound to {})\n",
541+
&constructed_base_url, &bind_address
542+
);
493543
if open {
494-
if let Err(err) = open::that(format!("http://{}", bind_address)) {
544+
if let Err(err) = open::that(format!("{}", &constructed_base_url)) {
495545
eprintln!("Failed to open URL in your browser: {}", err);
496546
}
497547
}
@@ -618,7 +668,7 @@ pub fn serve(
618668
no_port_append,
619669
ws_port,
620670
) {
621-
Ok((s, _)) => {
671+
Ok((s, _, _)) => {
622672
clear_serve_error();
623673
rebuild_done_handling(&broadcaster, Ok(()), "/x.js");
624674

@@ -801,7 +851,7 @@ fn is_folder_empty(dir: &Path) -> bool {
801851
mod tests {
802852
use std::path::{Path, PathBuf};
803853

804-
use super::{detect_change_kind, is_temp_file, ChangeKind};
854+
use super::{construct_url, detect_change_kind, is_temp_file, ChangeKind};
805855

806856
#[test]
807857
fn can_recognize_temp_files() {
@@ -893,4 +943,40 @@ mod tests {
893943
let config_filename = Path::new("config.toml");
894944
assert_eq!(expected, detect_change_kind(pwd, path, config_filename));
895945
}
946+
947+
#[test]
948+
fn test_construct_url_base_url_is_slash() {
949+
let result = construct_url("/", false, 8080);
950+
assert_eq!(result, "/");
951+
}
952+
953+
#[test]
954+
fn test_construct_url_http_protocol() {
955+
let result = construct_url("http://example.com", false, 8080);
956+
assert_eq!(result, "http://example.com:8080/");
957+
}
958+
959+
#[test]
960+
fn test_construct_url_https_protocol() {
961+
let result = construct_url("https://example.com", false, 8080);
962+
assert_eq!(result, "https://example.com:8080/");
963+
}
964+
965+
#[test]
966+
fn test_construct_url_no_protocol() {
967+
let result = construct_url("example.com", false, 8080);
968+
assert_eq!(result, "http://example.com:8080/");
969+
}
970+
971+
#[test]
972+
fn test_construct_url_no_port_append() {
973+
let result = construct_url("https://example.com", true, 8080);
974+
assert_eq!(result, "https://example.com/");
975+
}
976+
977+
#[test]
978+
fn test_construct_url_trailing_slash() {
979+
let result = construct_url("http://example.com/", false, 8080);
980+
assert_eq!(result, "http://example.com:8080/");
981+
}
896982
}

0 commit comments

Comments
 (0)