1use super::{Error, Result};
16use bytesize::ByteSize;
17use http::{HeaderName, HeaderValue};
18use pingap_discovery::{DNS_DISCOVERY, is_static_discovery};
19use pingap_util::{is_pem, resolve_path};
20use regex::Regex;
21use rustls_pki_types::pem::PemObject;
22use serde::{Deserialize, Serialize, Serializer};
23use std::collections::HashSet;
24use std::fs::File;
25use std::hash::{DefaultHasher, Hash, Hasher};
26use std::io::{BufReader, Read};
27use std::net::{IpAddr, ToSocketAddrs};
28use std::path::Path;
29use std::time::Duration;
30use std::{collections::HashMap, str::FromStr};
31use strum::EnumString;
32use tempfile::tempfile_in;
33use toml::Table;
34use toml::{Value, map::Map};
35use url::Url;
36
37pub const CATEGORY_BASIC: &str = "basic";
38pub const CATEGORY_SERVER: &str = "server";
39pub const CATEGORY_LOCATION: &str = "location";
40pub const CATEGORY_UPSTREAM: &str = "upstream";
41pub const CATEGORY_PLUGIN: &str = "plugin";
42pub const CATEGORY_CERTIFICATE: &str = "certificate";
43pub const CATEGORY_STORAGE: &str = "storage";
44
45pub trait Validate {
46 fn validate(&self) -> Result<()>;
47}
48
49#[derive(PartialEq, Debug, Default, Clone, EnumString, strum::Display)]
50#[strum(serialize_all = "snake_case")]
51pub enum PluginCategory {
52 #[default]
54 Stats,
55 Limit,
57 Compression,
59 Admin,
61 Directory,
63 Mock,
65 RequestId,
67 IpRestriction,
69 KeyAuth,
71 BasicAuth,
73 CombinedAuth,
75 Jwt,
77 Cache,
79 Redirect,
81 Ping,
83 ResponseHeaders,
85 SubFilter,
87 RefererRestriction,
89 UaRestriction,
91 Csrf,
93 Cors,
95 AcceptEncoding,
97 TrafficSplitting,
99}
100impl Serialize for PluginCategory {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 serializer.serialize_str(self.to_string().as_ref())
106 }
107}
108
109impl<'de> Deserialize<'de> for PluginCategory {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: serde::Deserializer<'de>,
113 {
114 let value: String = serde::Deserialize::deserialize(deserializer)?;
115 PluginCategory::from_str(&value).map_err(|_| {
116 serde::de::Error::custom(format!(
117 "invalid plugin category: {value}"
118 ))
119 })
120 }
121}
122
123#[derive(Debug, Default, Deserialize, Clone, Serialize, Hash)]
125pub struct CertificateConf {
126 pub domains: Option<String>,
128 pub tls_cert: Option<String>,
130 pub tls_key: Option<String>,
132 pub is_default: Option<bool>,
134 pub is_ca: Option<bool>,
136 pub acme: Option<String>,
138 pub dns_challenge: Option<bool>,
140 pub dns_provider: Option<String>,
142 pub dns_service_url: Option<String>,
144 pub buffer_days: Option<u16>,
146 pub remark: Option<String>,
148}
149
150fn validate_cert(value: &str) -> Result<()> {
152 let buf_list =
154 pingap_util::convert_pem(value).map_err(|e| Error::Invalid {
155 message: e.to_string(),
156 })?;
157 for buf in buf_list {
158 let certs = rustls_pki_types::CertificateDer::pem_slice_iter(&buf)
160 .collect::<std::result::Result<Vec<_>, _>>()
161 .map_err(|_| Error::Invalid {
162 message: "Failed to parse certificate".to_string(),
163 })?;
164
165 if certs.is_empty() {
167 return Err(Error::Invalid {
168 message: "No valid certificates found in input".to_string(),
169 });
170 }
171 }
172
173 Ok(())
174}
175
176impl Hashable for CertificateConf {
179 fn hash_key(&self) -> String {
180 let mut hasher = DefaultHasher::new();
181
182 self.hash(&mut hasher);
185
186 for value in [&self.tls_cert, &self.tls_key].into_iter().flatten() {
188 if is_pem(value) {
189 continue;
190 }
191 let file_path = resolve_path(value);
192 let path = Path::new(&file_path);
193 if !path.is_file() {
194 continue;
195 }
196
197 match File::open(path) {
198 Ok(file) => {
199 let mut reader = BufReader::new(file);
200 let mut buffer = [0; 8192];
201
202 loop {
203 match reader.read(&mut buffer) {
204 Ok(0) => break, Ok(bytes_read) => {
206 hasher.write(&buffer[..bytes_read]);
208 },
209 Err(e) => {
210 hasher.write(b"Error reading file content:");
211 hasher.write(e.to_string().as_bytes());
212 break;
213 },
214 }
215 }
216 },
217 Err(e) => {
218 hasher.write(b"Error opening file:");
219 hasher.write(e.to_string().as_bytes());
220 },
221 }
222 }
223
224 format!("{:x}", hasher.finish())
225 }
226}
227
228impl Validate for CertificateConf {
229 fn validate(&self) -> Result<()> {
234 let tls_key = self.tls_key.clone().unwrap_or_default();
236 if !tls_key.is_empty() {
237 let buf_list = pingap_util::convert_pem(&tls_key).map_err(|e| {
238 Error::Invalid {
239 message: e.to_string(),
240 }
241 })?;
242 let buf = &buf_list[0];
243 let _ = rustls_pki_types::PrivateKeyDer::from_pem_slice(buf)
244 .map_err(|_| Error::Invalid {
245 message: "Failed to parse private key".to_string(),
246 })?;
247 }
248
249 let tls_cert = self.tls_cert.clone().unwrap_or_default();
251 if !tls_cert.is_empty() {
252 validate_cert(&tls_cert)?;
253 }
254
255 Ok(())
256 }
257}
258
259#[derive(Debug, Default, Deserialize, Clone, Serialize, Hash)]
261pub struct UpstreamConf {
262 pub addrs: Vec<String>,
264
265 pub discovery: Option<String>,
267
268 pub dns_server: Option<String>,
270
271 pub dns_domain: Option<String>,
273
274 pub dns_search: Option<String>,
276
277 #[serde(default)]
279 #[serde(with = "humantime_serde")]
280 pub update_frequency: Option<Duration>,
281
282 pub algo: Option<String>,
284
285 pub sni: Option<String>,
287
288 pub verify_cert: Option<bool>,
290
291 pub health_check: Option<String>,
293
294 pub ipv4_only: Option<bool>,
296
297 pub enable_tracer: Option<bool>,
299
300 pub enable_backend_stats: Option<bool>,
302
303 pub backend_failure_status_code: Option<String>,
306
307 pub circuit_break_max_consecutive_failures: Option<u32>,
309
310 pub circuit_break_max_failure_percent: Option<u16>,
312
313 pub circuit_break_min_requests_threshold: Option<u64>,
317
318 pub circuit_break_half_open_consecutive_success_threshold: Option<u32>,
320
321 #[serde(default)]
323 #[serde(with = "humantime_serde")]
324 pub circuit_break_open_duration: Option<Duration>,
325
326 #[serde(default)]
328 #[serde(with = "humantime_serde")]
329 pub backend_stats_interval: Option<Duration>,
330
331 pub alpn: Option<String>,
333
334 #[serde(default)]
336 #[serde(with = "humantime_serde")]
337 pub connection_timeout: Option<Duration>,
338
339 #[serde(default)]
341 #[serde(with = "humantime_serde")]
342 pub total_connection_timeout: Option<Duration>,
343
344 #[serde(default)]
346 #[serde(with = "humantime_serde")]
347 pub read_timeout: Option<Duration>,
348
349 #[serde(default)]
351 #[serde(with = "humantime_serde")]
352 pub idle_timeout: Option<Duration>,
353
354 #[serde(default)]
356 #[serde(with = "humantime_serde")]
357 pub write_timeout: Option<Duration>,
358
359 #[serde(default)]
361 #[serde(with = "humantime_serde")]
362 pub tcp_idle: Option<Duration>,
363
364 #[serde(default)]
366 #[serde(with = "humantime_serde")]
367 pub tcp_interval: Option<Duration>,
368
369 #[serde(default)]
371 #[serde(with = "humantime_serde")]
372 pub tcp_user_timeout: Option<Duration>,
373
374 pub tcp_probe_count: Option<usize>,
376
377 pub tcp_recv_buf: Option<ByteSize>,
379
380 pub tcp_fast_open: Option<bool>,
382
383 pub includes: Option<Vec<String>>,
385
386 pub remark: Option<String>,
388}
389
390fn is_valid_upstream_ip(ip: IpAddr) -> bool {
391 match ip {
392 IpAddr::V4(ipv4) => {
393 !ipv4.is_unspecified()
394 && !ipv4.is_broadcast()
395 && !ipv4.is_multicast()
396 && !ipv4.is_link_local()
397 },
398 IpAddr::V6(ipv6) => {
399 !ipv6.is_unspecified()
400 && !ipv6.is_multicast()
401 && (ipv6.segments()[0] & 0xffc0) != 0xfe80
402 },
403 }
404}
405
406impl Validate for UpstreamConf {
407 fn validate(&self) -> Result<()> {
413 self.validate_addresses()?;
415
416 self.validate_health_check()?;
418
419 self.validate_tcp_probe_count()?;
421
422 Ok(())
423 }
424}
425
426impl UpstreamConf {
427 pub fn guess_discovery(&self) -> String {
432 if let Some(discovery) = &self.discovery {
434 return discovery.clone();
435 }
436
437 let has_hostname = self.addrs.iter().any(|addr| {
439 let host =
441 addr.split_once(':').map_or(addr.as_str(), |(host, _)| host);
442
443 host.parse::<std::net::IpAddr>().is_err()
445 });
446
447 if has_hostname {
448 DNS_DISCOVERY.to_string()
449 } else {
450 String::new()
451 }
452 }
453
454 fn validate_addresses(&self) -> Result<()> {
455 if self.addrs.is_empty() {
456 return Err(Error::Invalid {
457 message: "upstream addrs is empty".to_string(),
458 });
459 }
460
461 if !is_static_discovery(&self.guess_discovery()) {
463 return Ok(());
464 }
465
466 for addr in &self.addrs {
467 let parts: Vec<_> = addr.split_whitespace().collect();
468 let host_port = parts[0].to_string();
469
470 let host = if host_port.starts_with('[') {
471 host_port
472 .find(']')
473 .map_or(host_port.as_str(), |i| &host_port[1..i])
474 } else {
475 host_port
476 .split_once(':')
477 .map_or(host_port.as_str(), |(h, _)| h)
478 };
479
480 if let Ok(ip) = host.parse::<IpAddr>()
481 && !is_valid_upstream_ip(ip)
482 {
483 return Err(Error::Invalid {
484 message: format!(
485 "upstream addr({host}) is an invalid IP \
486 (unspecified, broadcast, multicast, or link-local)"
487 ),
488 });
489 }
490
491 let addr_to_check = if !host_port.contains(':') {
493 format!("{host_port}:80")
494 } else {
495 host_port
496 };
497
498 addr_to_check.to_socket_addrs().map_err(|e| Error::Io {
500 source: e,
501 file: addr_to_check,
502 })?;
503 }
504
505 Ok(())
506 }
507
508 fn validate_health_check(&self) -> Result<()> {
509 let health_check = match &self.health_check {
510 Some(url) if !url.is_empty() => url,
511 _ => return Ok(()),
512 };
513
514 Url::parse(health_check).map_err(|e| Error::UrlParse {
515 source: e,
516 url: health_check.to_string(),
517 })?;
518
519 Ok(())
520 }
521
522 fn validate_tcp_probe_count(&self) -> Result<()> {
523 const MAX_TCP_PROBE_COUNT: usize = 16;
524
525 if let Some(count) = self.tcp_probe_count
526 && count > MAX_TCP_PROBE_COUNT
527 {
528 return Err(Error::Invalid {
529 message: format!(
530 "tcp probe count should be <= {MAX_TCP_PROBE_COUNT}"
531 ),
532 });
533 }
534
535 Ok(())
536 }
537}
538
539impl Validate for LocationConf {
540 fn validate(&self) -> Result<()> {
541 self.validate_with_upstream(None)?;
542 Ok(())
543 }
544}
545
546#[derive(Debug, Default, Deserialize, Clone, Serialize, Hash)]
548pub struct LocationConf {
549 pub upstream: Option<String>,
551
552 pub path: Option<String>,
558
559 pub host: Option<String>,
561
562 pub proxy_set_headers: Option<Vec<String>>,
564
565 pub proxy_add_headers: Option<Vec<String>>,
567
568 pub rewrite: Option<String>,
570
571 pub weight: Option<u16>,
574
575 pub plugins: Option<Vec<String>>,
577
578 pub client_max_body_size: Option<ByteSize>,
580
581 pub max_processing: Option<i32>,
583
584 pub includes: Option<Vec<String>>,
586
587 pub grpc_web: Option<bool>,
589
590 pub enable_reverse_proxy_headers: Option<bool>,
592
593 pub max_retries: Option<u8>,
595
596 #[serde(default)]
598 #[serde(with = "humantime_serde")]
599 pub max_retry_window: Option<Duration>,
600
601 pub remark: Option<String>,
603}
604
605impl LocationConf {
606 fn validate_with_upstream(
612 &self,
613 upstream_names: Option<&[String]>,
614 ) -> Result<()> {
615 let validate = |headers: &Option<Vec<String>>| -> Result<()> {
617 if let Some(headers) = headers {
618 for header in headers.iter() {
619 let arr = header
621 .split_once(':')
622 .map(|(k, v)| (k.trim(), v.trim()));
623 let Some((header_name, header_value)) = arr else {
624 return Err(Error::Invalid {
625 message: format!("header {header} is invalid"),
626 });
627 };
628
629 HeaderName::from_bytes(header_name.as_bytes()).map_err(|err| Error::Invalid {
631 message: format!("header name({header_name}) is invalid, error: {err}"),
632 })?;
633
634 HeaderValue::from_str(header_value).map_err(|err| Error::Invalid {
636 message: format!("header value({header_value}) is invalid, error: {err}"),
637 })?;
638 }
639 }
640 Ok(())
641 };
642
643 if let Some(upstream_names) = upstream_names {
645 let upstream = self.upstream.clone().unwrap_or_default();
646 if !upstream.is_empty()
647 && !upstream.starts_with("$")
648 && !upstream_names.contains(&upstream)
649 {
650 return Err(Error::Invalid {
651 message: format!("upstream({upstream}) is not found"),
652 });
653 }
654 }
655
656 validate(&self.proxy_add_headers)?;
658 validate(&self.proxy_set_headers)?;
659
660 if let Some(value) = &self.rewrite {
662 let arr: Vec<&str> = value.split(' ').collect();
663 let _ =
664 Regex::new(arr[0]).map_err(|e| Error::Regex { source: e })?;
665 }
666
667 Ok(())
668 }
669
670 pub fn get_weight(&self) -> u16 {
679 if let Some(weight) = self.weight {
681 return weight;
682 }
683
684 let mut weight: u16 = 0;
685 let path = self.path.clone().unwrap_or("".to_string());
686
687 if path.len() > 1 {
689 if path.starts_with('=') {
690 weight += 1024; } else if path.starts_with('~') {
692 weight += 256; } else {
694 weight += 512; }
696 weight += path.len().min(64) as u16;
697 };
698 if let Some(host) = &self.host {
700 let exist_regex = host.split(',').any(|item| item.starts_with("~"));
701 if !exist_regex && !host.is_empty() {
704 weight += 128;
705 } else {
706 weight += host.len() as u16;
707 }
708 }
709
710 weight
711 }
712}
713
714#[derive(Debug, Default, Deserialize, Clone, Serialize)]
716pub struct ServerConf {
717 pub addr: String,
719
720 pub access_log: Option<String>,
722
723 pub locations: Option<Vec<String>>,
725
726 pub threads: Option<usize>,
728
729 pub tls_cipher_list: Option<String>,
731
732 pub tls_ciphersuites: Option<String>,
734
735 pub tls_min_version: Option<String>,
737
738 pub tls_max_version: Option<String>,
740
741 pub global_certificates: Option<bool>,
743
744 pub enabled_h2: Option<bool>,
746
747 #[serde(default)]
749 #[serde(with = "humantime_serde")]
750 pub tcp_idle: Option<Duration>,
751
752 #[serde(default)]
754 #[serde(with = "humantime_serde")]
755 pub tcp_interval: Option<Duration>,
756
757 #[serde(default)]
759 #[serde(with = "humantime_serde")]
760 pub tcp_user_timeout: Option<Duration>,
761
762 #[serde(default)]
764 #[serde(with = "humantime_serde")]
765 pub downstream_read_timeout: Option<Duration>,
766
767 #[serde(default)]
769 #[serde(with = "humantime_serde")]
770 pub downstream_write_timeout: Option<Duration>,
771
772 pub tcp_probe_count: Option<usize>,
774
775 pub tcp_fastopen: Option<usize>,
777
778 pub reuse_port: Option<bool>,
782
783 pub prometheus_metrics: Option<String>,
785
786 pub otlp_exporter: Option<String>,
788
789 pub includes: Option<Vec<String>>,
791
792 pub modules: Option<Vec<String>>,
794
795 pub enable_server_timing: Option<bool>,
797
798 pub remark: Option<String>,
800}
801
802impl Validate for ServerConf {
803 fn validate(&self) -> Result<()> {
804 self.validate_with_locations(&[])?;
805 Ok(())
806 }
807}
808
809impl ServerConf {
810 fn validate_with_locations(&self, location_names: &[String]) -> Result<()> {
815 for addr in self.addr.split(',') {
816 let _ = addr.to_socket_addrs().map_err(|e| Error::Io {
817 source: e,
818 file: self.addr.clone(),
819 })?;
820 }
821 if !location_names.is_empty()
822 && let Some(locations) = &self.locations
823 {
824 for item in locations {
825 if !location_names.contains(item) {
826 return Err(Error::Invalid {
827 message: format!("location({item}) is not found"),
828 });
829 }
830 }
831 }
832 let access_log = self.access_log.clone().unwrap_or_default();
833 if !access_log.is_empty() {
834 }
842
843 Ok(())
844 }
845}
846
847#[derive(Debug, Default, Deserialize, Clone, Serialize)]
849pub struct BasicConf {
850 pub name: Option<String>,
852 pub error_template: Option<String>,
854 pub pid_file: Option<String>,
856 pub upgrade_sock: Option<String>,
858 pub user: Option<String>,
860 pub group: Option<String>,
862 pub threads: Option<usize>,
864 pub work_stealing: Option<bool>,
866 pub listener_tasks_per_fd: Option<usize>,
868 #[serde(default)]
870 #[serde(with = "humantime_serde")]
871 pub grace_period: Option<Duration>,
872 #[serde(default)]
874 #[serde(with = "humantime_serde")]
875 pub graceful_shutdown_timeout: Option<Duration>,
876 pub upstream_keepalive_pool_size: Option<usize>,
878 pub webhook: Option<String>,
880 pub webhook_type: Option<String>,
882 pub webhook_notifications: Option<Vec<String>>,
884 pub log_level: Option<String>,
886 pub log_buffered_size: Option<ByteSize>,
888 pub log_format_json: Option<bool>,
890 pub sentry: Option<String>,
892 pub pyroscope: Option<String>,
894 #[serde(default)]
896 #[serde(with = "humantime_serde")]
897 pub auto_restart_check_interval: Option<Duration>,
898
899 pub log_compress_algorithm: Option<String>,
901 pub log_compress_level: Option<u8>,
903 pub log_compress_days_ago: Option<u16>,
905 pub log_compress_time_point_hour: Option<u8>,
907}
908
909impl Validate for BasicConf {
910 fn validate(&self) -> Result<()> {
911 Ok(())
912 }
913}
914
915impl BasicConf {
916 pub fn get_pid_file(&self) -> String {
921 if let Some(pid_file) = &self.pid_file {
922 return pid_file.clone();
923 }
924 for dir in ["/run", "/var/run"] {
925 if tempfile_in(dir).is_ok() {
926 return format!("{dir}/pingap.pid");
927 }
928 }
929 "/tmp/pingap.pid".to_string()
930 }
931}
932
933#[derive(Debug, Default, Deserialize, Clone, Serialize)]
934pub struct StorageConf {
935 pub category: String,
936 pub value: String,
937 pub secret: Option<String>,
938 pub remark: Option<String>,
939}
940
941impl Validate for StorageConf {
942 fn validate(&self) -> Result<()> {
943 Ok(())
944 }
945}
946
947pub trait Hashable: Hash {
948 fn hash_key(&self) -> String {
949 let mut hasher = DefaultHasher::new();
950 self.hash(&mut hasher);
951 format!("{:x}", hasher.finish())
952 }
953}
954impl Hashable for UpstreamConf {}
955impl Hashable for LocationConf {}
956
957#[derive(Deserialize, Debug, Serialize)]
958struct TomlConfig {
959 basic: Option<BasicConf>,
960 servers: Option<Map<String, Value>>,
961 upstreams: Option<Map<String, Value>>,
962 locations: Option<Map<String, Value>>,
963 plugins: Option<Map<String, Value>>,
964 certificates: Option<Map<String, Value>>,
965 storages: Option<Map<String, Value>>,
966}
967
968fn format_toml(value: &Value) -> String {
969 if let Some(value) = value.as_table() {
970 value.to_string()
971 } else {
972 "".to_string()
973 }
974}
975
976pub type PluginConf = Map<String, Value>;
977
978impl Validate for PluginConf {
979 fn validate(&self) -> Result<()> {
980 Ok(())
981 }
982}
983
984#[derive(Debug, Default, Clone, Deserialize, Serialize)]
985pub struct PingapConfig {
986 pub basic: BasicConf,
987 pub upstreams: HashMap<String, UpstreamConf>,
988 pub locations: HashMap<String, LocationConf>,
989 pub servers: HashMap<String, ServerConf>,
990 pub plugins: HashMap<String, PluginConf>,
991 pub certificates: HashMap<String, CertificateConf>,
992 pub storages: HashMap<String, StorageConf>,
993}
994
995impl PingapConfig {
996 fn get_value_for_category<T: Serialize>(
998 &self,
999 map: &HashMap<String, T>,
1000 name: Option<&str>,
1001 ) -> Result<toml::Value> {
1002 match name {
1003 Some(name) => {
1005 if let Some(item) = map.get(name) {
1006 let mut table = Map::new();
1007 table.insert(
1008 name.to_string(),
1009 toml::Value::try_from(item)
1010 .map_err(|e| Error::Ser { source: e })?,
1011 );
1012 Ok(toml::Value::Table(table))
1013 } else {
1014 Ok(toml::Value::Table(Map::new())) }
1016 },
1017 None => Ok(toml::Value::try_from(map)
1019 .map_err(|e| Error::Ser { source: e })?),
1020 }
1021 }
1022 pub fn get_toml(
1023 &self,
1024 category: &str,
1025 name: Option<&str>,
1026 ) -> Result<(String, String)> {
1027 let (key, value_to_serialize) = match category {
1028 CATEGORY_SERVER => {
1029 ("servers", self.get_value_for_category(&self.servers, name)?)
1030 },
1031 CATEGORY_LOCATION => (
1032 "locations",
1033 self.get_value_for_category(&self.locations, name)?,
1034 ),
1035 CATEGORY_UPSTREAM => (
1036 "upstreams",
1037 self.get_value_for_category(&self.upstreams, name)?,
1038 ),
1039 CATEGORY_PLUGIN => {
1040 ("plugins", self.get_value_for_category(&self.plugins, name)?)
1041 },
1042 CATEGORY_CERTIFICATE => (
1043 "certificates",
1044 self.get_value_for_category(&self.certificates, name)?,
1045 ),
1046 CATEGORY_STORAGE => (
1047 "storages",
1048 self.get_value_for_category(&self.storages, name)?,
1049 ),
1050 _ => (
1051 CATEGORY_BASIC,
1052 toml::Value::try_from(&self.basic)
1053 .map_err(|e| Error::Ser { source: e })?,
1054 ),
1055 };
1056
1057 let path = {
1058 let name = name.unwrap_or_default();
1059 if key == CATEGORY_BASIC || name.is_empty() {
1060 format!("/{key}.toml")
1061 } else {
1062 format!("/{key}/{name}.toml")
1063 }
1064 };
1065
1066 if let Some(table) = value_to_serialize.as_table()
1067 && table.is_empty()
1068 {
1069 return Ok((path, "".to_string()));
1070 }
1071
1072 let mut wrapper = Map::new();
1073 wrapper.insert(key.to_string(), value_to_serialize);
1074
1075 let toml_string = toml::to_string_pretty(&wrapper)
1076 .map_err(|e| Error::Ser { source: e })?;
1077
1078 Ok((path, toml_string))
1079 }
1080 pub fn get_storage_value(&self, name: &str) -> Result<String> {
1081 for (key, item) in self.storages.iter() {
1082 if key != name {
1083 continue;
1084 }
1085
1086 if let Some(key) = &item.secret {
1087 return pingap_util::aes_decrypt(key, &item.value).map_err(
1088 |e| Error::Invalid {
1089 message: e.to_string(),
1090 },
1091 );
1092 }
1093 return Ok(item.value.clone());
1094 }
1095 Ok("".to_string())
1096 }
1097}
1098
1099fn convert_include_toml(
1100 data: &HashMap<String, String>,
1101 replace_includes: bool,
1102 mut value: Value,
1103) -> String {
1104 let Some(m) = value.as_table_mut() else {
1105 return "".to_string();
1106 };
1107 if !replace_includes {
1108 return m.to_string();
1109 }
1110 if let Some(includes) = m.remove("includes")
1111 && let Some(includes) = get_include_toml(data, includes)
1112 && let Ok(includes) = toml::from_str::<Table>(&includes)
1113 {
1114 for (key, value) in includes.iter() {
1115 m.insert(key.to_string(), value.clone());
1116 }
1117 }
1118 m.to_string()
1119}
1120
1121fn get_include_toml(
1122 data: &HashMap<String, String>,
1123 includes: Value,
1124) -> Option<String> {
1125 let values = includes.as_array()?;
1126 let arr: Vec<String> = values
1127 .iter()
1128 .map(|item| {
1129 let key = item.as_str().unwrap_or_default();
1130 if let Some(value) = data.get(key) {
1131 value.clone()
1132 } else {
1133 "".to_string()
1134 }
1135 })
1136 .collect();
1137 Some(arr.join("\n"))
1138}
1139
1140pub(crate) fn convert_pingap_config(
1141 data: &[u8],
1142 replace_include: bool,
1143) -> Result<PingapConfig, Error> {
1144 let data: TomlConfig = toml::from_str(
1145 std::string::String::from_utf8_lossy(data)
1146 .to_string()
1147 .as_str(),
1148 )
1149 .map_err(|e| Error::De { source: e })?;
1150
1151 let mut conf = PingapConfig {
1152 basic: data.basic.unwrap_or_default(),
1153 ..Default::default()
1154 };
1155 let mut includes = HashMap::new();
1156 for (name, value) in data.storages.unwrap_or_default() {
1157 let toml = format_toml(&value);
1158 let storage: StorageConf = toml::from_str(toml.as_str())
1159 .map_err(|e| Error::De { source: e })?;
1160 includes.insert(name.clone(), storage.value.clone());
1161 conf.storages.insert(name, storage);
1162 }
1163
1164 for (name, value) in data.upstreams.unwrap_or_default() {
1165 let toml = convert_include_toml(&includes, replace_include, value);
1166
1167 let upstream: UpstreamConf = toml::from_str(toml.as_str())
1168 .map_err(|e| Error::De { source: e })?;
1169 conf.upstreams.insert(name, upstream);
1170 }
1171 for (name, value) in data.locations.unwrap_or_default() {
1172 let toml = convert_include_toml(&includes, replace_include, value);
1173
1174 let location: LocationConf = toml::from_str(toml.as_str())
1175 .map_err(|e| Error::De { source: e })?;
1176 conf.locations.insert(name, location);
1177 }
1178 for (name, value) in data.servers.unwrap_or_default() {
1179 let toml = convert_include_toml(&includes, replace_include, value);
1180
1181 let server: ServerConf = toml::from_str(toml.as_str())
1182 .map_err(|e| Error::De { source: e })?;
1183 conf.servers.insert(name, server);
1184 }
1185 for (name, value) in data.plugins.unwrap_or_default() {
1186 let plugin: PluginConf = toml::from_str(format_toml(&value).as_str())
1187 .map_err(|e| Error::De { source: e })?;
1188 conf.plugins.insert(name, plugin);
1189 }
1190
1191 for (name, value) in data.certificates.unwrap_or_default() {
1192 let certificate: CertificateConf =
1193 toml::from_str(format_toml(&value).as_str())
1194 .map_err(|e| Error::De { source: e })?;
1195 conf.certificates.insert(name, certificate);
1196 }
1197
1198 Ok(conf)
1199}
1200
1201#[derive(Debug, Default, Clone, Deserialize, Serialize)]
1202struct Description {
1203 category: String,
1204 name: String,
1205 data: String,
1206}
1207
1208impl PingapConfig {
1209 pub fn new(data: &[u8], replace_includes: bool) -> Result<Self> {
1210 convert_pingap_config(data, replace_includes)
1211 }
1212 pub fn validate(&self) -> Result<()> {
1214 let mut upstream_names = vec![];
1215 for (name, upstream) in self.upstreams.iter() {
1216 upstream.validate()?;
1217 upstream_names.push(name.to_string());
1218 }
1219 let mut location_names = vec![];
1220 for (name, location) in self.locations.iter() {
1221 location.validate_with_upstream(Some(&upstream_names))?;
1222 location_names.push(name.to_string());
1223 }
1224 let mut listen_addr_list = vec![];
1225 for server in self.servers.values() {
1226 for addr in server.addr.split(',') {
1227 if listen_addr_list.contains(&addr.to_string()) {
1228 return Err(Error::Invalid {
1229 message: format!("{addr} is inused by other server"),
1230 });
1231 }
1232 listen_addr_list.push(addr.to_string());
1233 }
1234 server.validate_with_locations(&location_names)?;
1235 }
1236 for (_, certificate) in self.certificates.iter() {
1245 certificate.validate()?;
1246 }
1247 let ping_conf = toml::to_string_pretty(self)
1248 .map_err(|e| Error::Ser { source: e })?;
1249 convert_pingap_config(ping_conf.as_bytes(), true)?;
1250 Ok(())
1251 }
1252 pub fn hash(&self) -> Result<String> {
1254 let mut lines = vec![];
1255 for desc in self.descriptions() {
1256 lines.push(desc.category);
1257 lines.push(desc.name);
1258 lines.push(desc.data);
1259 }
1260 let hash = crc32fast::hash(lines.join("\n").as_bytes());
1261 Ok(format!("{hash:X}"))
1262 }
1263 pub fn remove(&mut self, category: &str, name: &str) -> Result<()> {
1265 match category {
1266 CATEGORY_UPSTREAM => {
1267 for (location_name, location) in self.locations.iter() {
1268 if let Some(upstream) = &location.upstream
1269 && upstream == name
1270 {
1271 return Err(Error::Invalid {
1272 message: format!(
1273 "upstream({name}) is in used by location({location_name})",
1274 ),
1275 });
1276 }
1277 }
1278 self.upstreams.remove(name);
1279 },
1280 CATEGORY_LOCATION => {
1281 for (server_name, server) in self.servers.iter() {
1282 if let Some(locations) = &server.locations
1283 && locations.contains(&name.to_string())
1284 {
1285 return Err(Error::Invalid {
1286 message: format!(
1287 "location({name}) is in used by server({server_name})"
1288 ),
1289 });
1290 }
1291 }
1292 self.locations.remove(name);
1293 },
1294 CATEGORY_SERVER => {
1295 self.servers.remove(name);
1296 },
1297 CATEGORY_PLUGIN => {
1298 for (location_name, location) in self.locations.iter() {
1299 if let Some(plugins) = &location.plugins
1300 && plugins.contains(&name.to_string())
1301 {
1302 return Err(Error::Invalid {
1303 message: format!(
1304 "proxy plugin({name}) is in used by location({location_name})"
1305 ),
1306 });
1307 }
1308 }
1309 self.plugins.remove(name);
1310 },
1311 CATEGORY_CERTIFICATE => {
1312 self.certificates.remove(name);
1313 },
1314 _ => {},
1315 };
1316 Ok(())
1317 }
1318 fn descriptions(&self) -> Vec<Description> {
1319 let mut value = self.clone();
1320 let mut descriptions = vec![];
1321 for (name, data) in value.servers.iter() {
1322 descriptions.push(Description {
1323 category: CATEGORY_SERVER.to_string(),
1324 name: format!("server:{name}"),
1325 data: toml::to_string_pretty(data).unwrap_or_default(),
1326 });
1327 }
1328 for (name, data) in value.locations.iter() {
1329 descriptions.push(Description {
1330 category: CATEGORY_LOCATION.to_string(),
1331 name: format!("location:{name}"),
1332 data: toml::to_string_pretty(data).unwrap_or_default(),
1333 });
1334 }
1335 for (name, data) in value.upstreams.iter() {
1336 descriptions.push(Description {
1337 category: CATEGORY_UPSTREAM.to_string(),
1338 name: format!("upstream:{name}"),
1339 data: toml::to_string_pretty(data).unwrap_or_default(),
1340 });
1341 }
1342 for (name, data) in value.plugins.iter() {
1343 descriptions.push(Description {
1344 category: CATEGORY_PLUGIN.to_string(),
1345 name: format!("plugin:{name}"),
1346 data: toml::to_string_pretty(data).unwrap_or_default(),
1347 });
1348 }
1349 for (name, data) in value.certificates.iter() {
1350 let mut clone_data = data.clone();
1351 if let Some(cert) = &clone_data.tls_cert {
1352 clone_data.tls_cert = Some(format!(
1353 "crc32:{:X}",
1354 crc32fast::hash(cert.as_bytes())
1355 ));
1356 }
1357 if let Some(key) = &clone_data.tls_key {
1358 clone_data.tls_key = Some(format!(
1359 "crc32:{:X}",
1360 crc32fast::hash(key.as_bytes())
1361 ));
1362 }
1363 descriptions.push(Description {
1364 category: CATEGORY_CERTIFICATE.to_string(),
1365 name: format!("certificate:{name}"),
1366 data: toml::to_string_pretty(&clone_data).unwrap_or_default(),
1367 });
1368 }
1369 for (name, data) in value.storages.iter() {
1370 let mut clone_data = data.clone();
1371 if let Some(secret) = &clone_data.secret {
1372 clone_data.secret = Some(format!(
1373 "crc32:{:X}",
1374 crc32fast::hash(secret.as_bytes())
1375 ));
1376 }
1377 descriptions.push(Description {
1378 category: CATEGORY_STORAGE.to_string(),
1379 name: format!("storage:{name}"),
1380 data: toml::to_string_pretty(&clone_data).unwrap_or_default(),
1381 });
1382 }
1383 value.servers = HashMap::new();
1384 value.locations = HashMap::new();
1385 value.upstreams = HashMap::new();
1386 value.plugins = HashMap::new();
1387 value.certificates = HashMap::new();
1388 value.storages = HashMap::new();
1389 descriptions.push(Description {
1390 category: CATEGORY_BASIC.to_string(),
1391 name: CATEGORY_BASIC.to_string(),
1392 data: toml::to_string_pretty(&value).unwrap_or_default(),
1393 });
1394 descriptions.sort_by_key(|d| d.name.clone());
1395 descriptions
1396 }
1397 pub fn diff(&self, other: &PingapConfig) -> (Vec<String>, Vec<String>) {
1399 let current_map: HashMap<_, _> = self
1401 .descriptions()
1402 .into_iter()
1403 .map(|d| (d.name.clone(), d))
1404 .collect();
1405 let new_map: HashMap<_, _> = other
1406 .descriptions()
1407 .into_iter()
1408 .map(|d| (d.name.clone(), d))
1409 .collect();
1410
1411 let mut affected_categories = HashSet::new();
1413
1414 let mut added_items = vec![];
1416 let mut removed_items = vec![];
1417 let mut modified_items = vec![];
1418
1419 for (name, current_item) in ¤t_map {
1421 match new_map.get(name) {
1422 Some(new_item) => {
1423 if current_item.data != new_item.data {
1425 affected_categories
1426 .insert(current_item.category.clone());
1427
1428 let mut item_diff_result = vec![];
1430 for diff in
1431 diff::lines(¤t_item.data, &new_item.data)
1432 {
1433 match diff {
1434 diff::Result::Left(l) => {
1435 item_diff_result.push(format!("- {l}"))
1436 },
1437 diff::Result::Right(r) => {
1438 item_diff_result.push(format!("+ {r}"))
1439 },
1440 _ => (),
1441 }
1442 }
1443
1444 if !item_diff_result.is_empty() {
1445 modified_items.push(format!("[MODIFIED] {name}"));
1446 modified_items.extend(item_diff_result);
1447 modified_items.push("".to_string()); }
1449 }
1450 },
1451 None => {
1452 removed_items.push(format!("-- [REMOVED] {name}"));
1454 affected_categories.insert(current_item.category.clone());
1455 },
1456 }
1457 }
1458
1459 for (name, new_item) in &new_map {
1461 if !current_map.contains_key(name) {
1462 added_items.push(format!("++ [ADDED] {name}"));
1463 affected_categories.insert(new_item.category.clone());
1464 }
1465 }
1466
1467 let mut final_diff = Vec::new();
1469 if !added_items.is_empty() {
1470 final_diff.extend(added_items);
1471 final_diff.push("".to_string()); }
1473 if !removed_items.is_empty() {
1474 final_diff.extend(removed_items);
1475 final_diff.push("".to_string());
1476 }
1477 if !modified_items.is_empty() {
1478 final_diff.extend(modified_items);
1479 }
1480
1481 (affected_categories.into_iter().collect(), final_diff)
1483 }
1484}
1485
1486#[cfg(test)]
1487mod tests {
1488 use super::{CertificateConf, Hashable, Validate, validate_cert};
1489 use super::{LocationConf, PluginCategory, ServerConf, UpstreamConf};
1490 use pingap_core::PluginStep;
1491 use pingap_util::base64_encode;
1492 use pretty_assertions::assert_eq;
1493 use serde::{Deserialize, Serialize};
1494 use std::str::FromStr;
1495
1496 #[test]
1497 fn test_plugin_step() {
1498 let step = PluginStep::from_str("early_request").unwrap();
1499 assert_eq!(step, PluginStep::EarlyRequest);
1500
1501 assert_eq!("early_request", step.to_string());
1502 }
1503
1504 #[test]
1505 fn test_validate_cert() {
1506 let pem = r#"-----BEGIN CERTIFICATE-----
1508MIIEljCCAv6gAwIBAgIQeYUdeFj3gpzhQes3aGaMZTANBgkqhkiG9w0BAQsFADCB
1509pTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT0wOwYDVQQLDDR4aWVz
1510aHV6aG91QHhpZXNodXpob3VzLU1hY0Jvb2stQWlyLmxvY2FsICjosKLmoJHmtLIp
1511MUQwQgYDVQQDDDtta2NlcnQgeGllc2h1emhvdUB4aWVzaHV6aG91cy1NYWNCb29r
1512LUFpci5sb2NhbCAo6LCi5qCR5rSyKTAeFw0yMzA5MjQxMzA1MjdaFw0yNTEyMjQx
1513MzA1MjdaMGgxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0
1514ZTE9MDsGA1UECww0eGllc2h1emhvdUB4aWVzaHV6aG91cy1NYWNCb29rLUFpci5s
1515b2NhbCAo6LCi5qCR5rSyKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
1516ALuJ8lYEj9uf4iE9hguASq7re87Np+zJc2x/eqr1cR/SgXRStBsjxqI7i3xwMRqX
1517AuhAnM6ktlGuqidl7D9y6AN/UchqgX8AetslRJTpCcEDfL/q24zy0MqOS0FlYEgh
1518s4PIjWsSNoglBDeaIdUpN9cM/64IkAAtHndNt2p2vPfjrPeixLjese096SKEnZM/
1519xBdWF491hx06IyzjtWKqLm9OUmYZB9d/gDGnDsKpqClw8m95opKD4TBHAoE//WvI
1520m1mZnjNTNR27vVbmnc57d2Lx2Ib2eqJG5zMsP2hPBoqS8CKEwMRFLHAcclNkI67U
1521kcSEGaWgr15QGHJPN/FtjDsCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
1522JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFJo0y9bYUM/OuenDjsJ1RyHJfL3n
1523MDQGA1UdEQQtMCuCBm1lLmRldoIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAA
1524AAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBgQAlQbow3+4UyQx+E+J0RwmHBltU6i+K
1525soFfza6FWRfAbTyv+4KEWl2mx51IfHhJHYZvsZqPqGWxm5UvBecskegDExFMNFVm
1526O5QixydQzHHY2krmBwmDZ6Ao88oW/qw4xmMUhzKAZbsqeQyE/uiUdyI4pfDcduLB
1527rol31g9OFsgwZrZr0d1ZiezeYEhemnSlh9xRZW3veKx9axgFttzCMmWdpGTCvnav
1528ZVc3rB+KBMjdCwsS37zmrNm9syCjW1O5a1qphwuMpqSnDHBgKWNpbsgqyZM0oyOc
15299Bkja+BV5wFO+4zH5WtestcrNMeoQ83a5lI0m42u/bUEJ/T/5BQBSFidNuvS7Ylw
1530IZpXa00xvlnm1BOHOfRI4Ehlfa5jmfcdnrGkQLGjiyygQtKcc7rOXGK+mSeyxwhs
1531sIARwslSQd4q0dbYTPKvvUHxTYiCv78vQBAsE15T2GGS80pAFDBW9vOf3upANvOf
1532EHjKf0Dweb4ppL4ddgeAKU5V0qn76K2fFaE=
1533-----END CERTIFICATE-----"#;
1534 let result = validate_cert(pem);
1536 assert_eq!(true, result.is_ok());
1537
1538 let value = base64_encode(pem);
1539 let result = validate_cert(&value);
1540 assert_eq!(true, result.is_ok());
1541 }
1542
1543 #[test]
1544 fn test_plugin_category_serde() {
1545 #[derive(Deserialize, Serialize)]
1546 struct TmpPluginCategory {
1547 category: PluginCategory,
1548 }
1549 let tmp = TmpPluginCategory {
1550 category: PluginCategory::RequestId,
1551 };
1552 let data = serde_json::to_string(&tmp).unwrap();
1553 assert_eq!(r#"{"category":"request_id"}"#, data);
1554
1555 let tmp: TmpPluginCategory = serde_json::from_str(&data).unwrap();
1556 assert_eq!(PluginCategory::RequestId, tmp.category);
1557 }
1558
1559 #[test]
1560 fn test_upstream_conf() {
1561 let mut conf = UpstreamConf::default();
1562
1563 let result = conf.validate();
1564 assert_eq!(true, result.is_err());
1565 assert_eq!(
1566 "Invalid error upstream addrs is empty",
1567 result.expect_err("").to_string()
1568 );
1569
1570 conf.addrs = vec!["127.0.0.1".to_string(), "github".to_string()];
1571 conf.discovery = Some("static".to_string());
1572 let result = conf.validate();
1573 assert_eq!(true, result.is_err());
1574 assert_eq!(
1575 true,
1576 result
1577 .expect_err("")
1578 .to_string()
1579 .contains("Io error failed to lookup address information")
1580 );
1581
1582 conf.addrs = vec!["127.0.0.1".to_string(), "github.com".to_string()];
1583 conf.health_check = Some("http:///".to_string());
1584 let result = conf.validate();
1585 assert_eq!(true, result.is_err());
1586 assert_eq!(
1587 "Url parse error empty host, http:///",
1588 result.expect_err("").to_string()
1589 );
1590
1591 conf.health_check = Some("http://github.com/".to_string());
1592 let result = conf.validate();
1593 assert_eq!(true, result.is_ok());
1594 }
1595
1596 #[test]
1597 fn test_upstream_invalid_ip() {
1598 let invalid_addrs = vec![
1599 "0.0.0.0:80",
1600 "255.255.255.255:80",
1601 "224.0.0.1:80",
1602 "169.254.1.1:80",
1603 "[::]:80",
1604 "[ff02::1]:80",
1605 "[fe80::1]:80",
1606 ];
1607 for addr in invalid_addrs {
1608 let conf = UpstreamConf {
1609 addrs: vec![addr.to_string()],
1610 discovery: Some("static".to_string()),
1611 ..Default::default()
1612 };
1613 let result = conf.validate();
1614 assert!(
1615 result.is_err(),
1616 "{addr} should be rejected as invalid upstream IP"
1617 );
1618 assert!(
1619 result.unwrap_err().to_string().contains("invalid IP"),
1620 "{addr} error should mention invalid IP"
1621 );
1622 }
1623
1624 let valid_addrs =
1625 vec!["127.0.0.1:80", "192.168.1.1:80", "10.0.0.1:8080"];
1626 for addr in valid_addrs {
1627 let conf = UpstreamConf {
1628 addrs: vec![addr.to_string()],
1629 discovery: Some("static".to_string()),
1630 ..Default::default()
1631 };
1632 let result = conf.validate();
1633 assert!(
1634 result.is_ok(),
1635 "{addr} should be accepted as valid upstream IP"
1636 );
1637 }
1638 }
1639
1640 #[test]
1641 fn test_location_conf() {
1642 let mut conf = LocationConf::default();
1643 let upstream_names = vec!["upstream1".to_string()];
1644
1645 conf.upstream = Some("upstream2".to_string());
1646 let result = conf.validate_with_upstream(Some(&upstream_names));
1647 assert_eq!(true, result.is_err());
1648 assert_eq!(
1649 "Invalid error upstream(upstream2) is not found",
1650 result.expect_err("").to_string()
1651 );
1652
1653 conf.upstream = Some("upstream1".to_string());
1654 conf.proxy_set_headers = Some(vec!["X-Request-Id".to_string()]);
1655 let result = conf.validate_with_upstream(Some(&upstream_names));
1656 assert_eq!(true, result.is_err());
1657 assert_eq!(
1658 "Invalid error header X-Request-Id is invalid",
1659 result.expect_err("").to_string()
1660 );
1661
1662 conf.proxy_set_headers = Some(vec!["请求:响应".to_string()]);
1663 let result = conf.validate_with_upstream(Some(&upstream_names));
1664 assert_eq!(true, result.is_err());
1665 assert_eq!(
1666 "Invalid error header name(请求) is invalid, error: invalid HTTP header name",
1667 result.expect_err("").to_string()
1668 );
1669
1670 conf.proxy_set_headers = Some(vec!["X-Request-Id: abcd".to_string()]);
1671 let result = conf.validate_with_upstream(Some(&upstream_names));
1672 assert_eq!(true, result.is_ok());
1673
1674 conf.rewrite = Some(r"foo(bar".to_string());
1675 let result = conf.validate_with_upstream(Some(&upstream_names));
1676 assert_eq!(true, result.is_err());
1677 assert_eq!(
1678 true,
1679 result
1680 .expect_err("")
1681 .to_string()
1682 .starts_with("Regex error regex parse error")
1683 );
1684
1685 conf.rewrite = Some(r"^/api /".to_string());
1686 let result = conf.validate_with_upstream(Some(&upstream_names));
1687 assert_eq!(true, result.is_ok());
1688 }
1689
1690 #[test]
1691 fn test_location_get_wegiht() {
1692 let mut conf = LocationConf {
1693 weight: Some(2048),
1694 ..Default::default()
1695 };
1696
1697 assert_eq!(2048, conf.get_weight());
1698
1699 conf.weight = None;
1700 conf.path = Some("=/api".to_string());
1701 assert_eq!(1029, conf.get_weight());
1702
1703 conf.path = Some("~/api".to_string());
1704 assert_eq!(261, conf.get_weight());
1705
1706 conf.path = Some("/api".to_string());
1707 assert_eq!(516, conf.get_weight());
1708
1709 conf.path = None;
1710 conf.host = Some("github.com".to_string());
1711 assert_eq!(128, conf.get_weight());
1712
1713 conf.host = Some("~github.com".to_string());
1714 assert_eq!(11, conf.get_weight());
1715
1716 conf.host = Some("".to_string());
1717 assert_eq!(0, conf.get_weight());
1718 }
1719
1720 #[test]
1721 fn test_server_conf() {
1722 let mut conf = ServerConf::default();
1723 let location_names = vec!["lo".to_string()];
1724
1725 let result = conf.validate_with_locations(&location_names);
1726 assert_eq!(true, result.is_err());
1727 assert_eq!(
1728 "Io error invalid socket address, ",
1729 result.expect_err("").to_string()
1730 );
1731
1732 conf.addr = "127.0.0.1:3001".to_string();
1733 conf.locations = Some(vec!["lo1".to_string()]);
1734 let result = conf.validate_with_locations(&location_names);
1735 assert_eq!(true, result.is_err());
1736 assert_eq!(
1737 "Invalid error location(lo1) is not found",
1738 result.expect_err("").to_string()
1739 );
1740
1741 conf.locations = Some(vec!["lo".to_string()]);
1742 let result = conf.validate_with_locations(&location_names);
1743 assert_eq!(true, result.is_ok());
1744 }
1745
1746 #[test]
1747 fn test_certificate_conf() {
1748 let pem = r#"-----BEGIN CERTIFICATE-----
1750MIIEljCCAv6gAwIBAgIQeYUdeFj3gpzhQes3aGaMZTANBgkqhkiG9w0BAQsFADCB
1751pTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT0wOwYDVQQLDDR4aWVz
1752aHV6aG91QHhpZXNodXpob3VzLU1hY0Jvb2stQWlyLmxvY2FsICjosKLmoJHmtLIp
1753MUQwQgYDVQQDDDtta2NlcnQgeGllc2h1emhvdUB4aWVzaHV6aG91cy1NYWNCb29r
1754LUFpci5sb2NhbCAo6LCi5qCR5rSyKTAeFw0yMzA5MjQxMzA1MjdaFw0yNTEyMjQx
1755MzA1MjdaMGgxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0
1756ZTE9MDsGA1UECww0eGllc2h1emhvdUB4aWVzaHV6aG91cy1NYWNCb29rLUFpci5s
1757b2NhbCAo6LCi5qCR5rSyKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
1758ALuJ8lYEj9uf4iE9hguASq7re87Np+zJc2x/eqr1cR/SgXRStBsjxqI7i3xwMRqX
1759AuhAnM6ktlGuqidl7D9y6AN/UchqgX8AetslRJTpCcEDfL/q24zy0MqOS0FlYEgh
1760s4PIjWsSNoglBDeaIdUpN9cM/64IkAAtHndNt2p2vPfjrPeixLjese096SKEnZM/
1761xBdWF491hx06IyzjtWKqLm9OUmYZB9d/gDGnDsKpqClw8m95opKD4TBHAoE//WvI
1762m1mZnjNTNR27vVbmnc57d2Lx2Ib2eqJG5zMsP2hPBoqS8CKEwMRFLHAcclNkI67U
1763kcSEGaWgr15QGHJPN/FtjDsCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
1764JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFJo0y9bYUM/OuenDjsJ1RyHJfL3n
1765MDQGA1UdEQQtMCuCBm1lLmRldoIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAA
1766AAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBgQAlQbow3+4UyQx+E+J0RwmHBltU6i+K
1767soFfza6FWRfAbTyv+4KEWl2mx51IfHhJHYZvsZqPqGWxm5UvBecskegDExFMNFVm
1768O5QixydQzHHY2krmBwmDZ6Ao88oW/qw4xmMUhzKAZbsqeQyE/uiUdyI4pfDcduLB
1769rol31g9OFsgwZrZr0d1ZiezeYEhemnSlh9xRZW3veKx9axgFttzCMmWdpGTCvnav
1770ZVc3rB+KBMjdCwsS37zmrNm9syCjW1O5a1qphwuMpqSnDHBgKWNpbsgqyZM0oyOc
17719Bkja+BV5wFO+4zH5WtestcrNMeoQ83a5lI0m42u/bUEJ/T/5BQBSFidNuvS7Ylw
1772IZpXa00xvlnm1BOHOfRI4Ehlfa5jmfcdnrGkQLGjiyygQtKcc7rOXGK+mSeyxwhs
1773sIARwslSQd4q0dbYTPKvvUHxTYiCv78vQBAsE15T2GGS80pAFDBW9vOf3upANvOf
1774EHjKf0Dweb4ppL4ddgeAKU5V0qn76K2fFaE=
1775-----END CERTIFICATE-----"#;
1776 let key = r#"-----BEGIN PRIVATE KEY-----
1777MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7ifJWBI/bn+Ih
1778PYYLgEqu63vOzafsyXNsf3qq9XEf0oF0UrQbI8aiO4t8cDEalwLoQJzOpLZRrqon
1779Zew/cugDf1HIaoF/AHrbJUSU6QnBA3y/6tuM8tDKjktBZWBIIbODyI1rEjaIJQQ3
1780miHVKTfXDP+uCJAALR53Tbdqdrz346z3osS43rHtPekihJ2TP8QXVhePdYcdOiMs
178147Viqi5vTlJmGQfXf4Axpw7CqagpcPJveaKSg+EwRwKBP/1ryJtZmZ4zUzUdu71W
17825p3Oe3di8diG9nqiRuczLD9oTwaKkvAihMDERSxwHHJTZCOu1JHEhBmloK9eUBhy
1783TzfxbYw7AgMBAAECggEALjed0FMJfO+XE+gMm9L/FMKV3W5TXwh6eJemDHG2ckg3
1784fQpQtouHjT2tb3par5ndro0V19tBzzmDV3hH048m3I3JAuI0ja75l/5EO4p+y+Fn
1785IgjoGIFSsUiGBVTNeJlNm0GWkHeJlt3Af09t3RFuYIIklKgpjNGRu4ccl5ExmslF
1786WHv7/1dwzeJCi8iOY2gJZz6N7qHD95VkgVyDj/EtLltONAtIGVdorgq70CYmtwSM
17879XgXszqOTtSJxle+UBmeQTL4ZkUR0W+h6JSpcTn0P9c3fiNDrHSKFZbbpAhO/wHd
1788Ab4IK8IksVyg+tem3m5W9QiXn3WbgcvjJTi83Y3syQKBgQD5IsaSbqwEG3ruttQe
1789yfMeq9NUGVfmj7qkj2JiF4niqXwTpvoaSq/5gM/p7lAtSMzhCKtlekP8VLuwx8ih
1790n4hJAr8pGfyu/9IUghXsvP2DXsCKyypbhzY/F2m4WNIjtyLmed62Nt1PwWWUlo9Q
1791igHI6pieT45vJTBICsRyqC/a/wKBgQDAtLXUsCABQDTPHdy/M/dHZA/QQ/xU8NOs
1792ul5UMJCkSfFNk7b2etQG/iLlMSNup3bY3OPvaCGwwEy/gZ31tTSymgooXQMFxJ7G
17931S/DF45yKD6xJEmAUhwz/Hzor1cM95g78UpZFCEVMnEmkBNb9pmrXRLDuWb0vLE6
1794B6YgiEP6xQKBgBOXuooVjg2co6RWWIQ7WZVV6f65J4KIVyNN62zPcRaUQZ/CB/U9
1795Xm1+xdsd1Mxa51HjPqdyYBpeB4y1iX+8bhlfz+zJkGeq0riuKk895aoJL5c6txAP
1796qCJ6EuReh9grNOFvQCaQVgNJsFVpKcgpsk48tNfuZcMz54Ii5qQlue29AoGAA2Sr
1797Nv2K8rqws1zxQCSoHAe1B5PK46wB7i6x7oWUZnAu4ZDSTfDHvv/GmYaN+yrTuunY
17980aRhw3z/XPfpUiRIs0RnHWLV5MobiaDDYIoPpg7zW6cp7CqF+JxfjrFXtRC/C38q
1799MftawcbLm0Q6MwpallvjMrMXDwQrkrwDvtrnZ4kCgYEA0oSvmSK5ADD0nqYFdaro
1800K+hM90AVD1xmU7mxy3EDPwzjK1wZTj7u0fvcAtZJztIfL+lmVpkvK8KDLQ9wCWE7
1801SGToOzVHYX7VazxioA9nhNne9kaixvnIUg3iowAz07J7o6EU8tfYsnHxsvjlIkBU
1802ai02RHnemmqJaNepfmCdyec=
1803-----END PRIVATE KEY-----"#;
1804 let conf = CertificateConf {
1806 tls_cert: Some(pem.to_string()),
1807 tls_key: Some(key.to_string()),
1808 ..Default::default()
1809 };
1810 let result = conf.validate();
1811 assert_eq!(true, result.is_ok());
1812
1813 assert_eq!("15ba921aee80abc3", conf.hash_key());
1815 }
1817}