1use crate::data::export_v2::schema::{DDL_DIR, SCHEMA_DIR};
18
19pub(crate) fn ddl_path_for_schema(schema: &str) -> String {
20 format!(
21 "{}/{}/{}.sql",
22 SCHEMA_DIR,
23 DDL_DIR,
24 encode_path_segment(schema)
25 )
26}
27
28pub(crate) fn encode_path_segment(value: &str) -> String {
29 let mut encoded = String::with_capacity(value.len());
30 for byte in value.bytes() {
31 match byte {
32 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' => {
33 encoded.push(byte as char);
34 }
35 _ => {
36 encoded.push('%');
37 encoded.push(hex_char(byte >> 4));
38 encoded.push(hex_char(byte & 0x0F));
39 }
40 }
41 }
42 encoded
43}
44
45fn hex_char(nibble: u8) -> char {
46 match nibble {
47 0..=9 => (b'0' + nibble) as char,
48 10..=15 => (b'A' + (nibble - 10)) as char,
49 _ => unreachable!("nibble must be in 0..=15"),
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_encode_path_segment_preserves_safe_ascii() {
59 assert_eq!(encode_path_segment("test_db"), "test_db");
60 }
61
62 #[test]
63 fn test_encode_path_segment_escapes_path_traversal_chars() {
64 assert_eq!(encode_path_segment("../evil"), "%2E%2E%2Fevil");
65 assert_eq!(encode_path_segment(r"..\\evil"), "%2E%2E%5C%5Cevil");
66 }
67
68 #[test]
69 fn test_ddl_path_for_schema_encodes_schema_segment() {
70 assert_eq!(ddl_path_for_schema("public"), "schema/ddl/public.sql");
71 assert_eq!(
72 ddl_path_for_schema("../evil"),
73 "schema/ddl/%2E%2E%2Fevil.sql"
74 );
75 }
76}