Skip to main content

memscope_rs/analyzer/
export.rs

1//! Export engine module.
2
3use crate::core::error::{MemScopeError, MemScopeResult};
4use crate::view::MemoryView;
5use std::path::Path;
6
7/// Export engine.
8///
9/// Provides export functionality for analysis results.
10pub struct ExportEngine<'a> {
11    view: &'a MemoryView,
12}
13
14impl<'a> ExportEngine<'a> {
15    /// Create new export engine.
16    pub fn new(view: &'a MemoryView) -> Self {
17        Self { view }
18    }
19
20    /// Export to JSON file.
21    pub fn json<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
22        let json = self.to_json()?;
23        std::fs::write(path, json)
24            .map_err(|e| MemScopeError::export("json", format!("Failed to write JSON file: {}", e)))
25    }
26
27    /// Export to HTML dashboard.
28    pub fn html<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
29        let html = self.to_html()?;
30        std::fs::write(path, html)
31            .map_err(|e| MemScopeError::export("html", format!("Failed to write HTML file: {}", e)))
32    }
33
34    /// Generate JSON string.
35    pub fn to_json(&self) -> MemScopeResult<String> {
36        let report = self.build_report();
37        serde_json::to_string_pretty(&report)
38            .map_err(|e| MemScopeError::export("json", format!("Failed to serialize JSON: {}", e)))
39    }
40
41    /// Generate HTML string with proper escaping.
42    ///
43    /// Uses HTML entity encoding to prevent XSS vulnerabilities.
44    pub fn to_html(&self) -> MemScopeResult<String> {
45        let report = self.build_report();
46        let json = serde_json::to_string_pretty(&report).unwrap_or_default();
47        let escaped_json = html_escape(&json);
48
49        Ok(format!(
50            r#"<!DOCTYPE html>
51<html lang="en">
52<head>
53    <meta charset="UTF-8">
54    <meta name="viewport" content="width=device-width, initial-scale=1.0">
55    <title>Memory Analysis Report</title>
56    <style>
57        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 2rem; }}
58        h1 {{ color: #333; }}
59        pre {{ background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow-x: auto; }}
60    </style>
61</head>
62<body>
63    <h1>Memory Analysis Report</h1>
64    <pre>{}</pre>
65</body>
66</html>"#,
67            escaped_json
68        ))
69    }
70
71    fn build_report(&self) -> serde_json::Value {
72        serde_json::json!({
73            "stats": {
74                "allocation_count": self.view.len(),
75                "total_bytes": self.view.total_memory(),
76            },
77            "snapshot": self.view.snapshot(),
78        })
79    }
80}
81
82/// Escape HTML special characters to prevent XSS.
83///
84/// Converts the following characters to HTML entities:
85/// - `&` → `&amp;`
86/// - `<` → `&lt;`
87/// - `>` → `&gt;`
88/// - `"` → `&quot;`
89/// - `'` → `&#x27;`
90fn html_escape(s: &str) -> String {
91    s.chars()
92        .map(|c| match c {
93            '&' => "&amp;".to_string(),
94            '<' => "&lt;".to_string(),
95            '>' => "&gt;".to_string(),
96            '"' => "&quot;".to_string(),
97            '\'' => "&#x27;".to_string(),
98            _ => c.to_string(),
99        })
100        .collect()
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::event_store::MemoryEvent;
107
108    #[test]
109    fn test_export_to_json() {
110        let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
111        let view = MemoryView::from_events(events);
112        let engine = ExportEngine::new(&view);
113        let json = engine.to_json().expect("JSON export should succeed");
114        assert!(json.contains("allocation_count"));
115    }
116
117    #[test]
118    fn test_html_escape() {
119        assert_eq!(html_escape("&"), "&amp;");
120        assert_eq!(html_escape("<"), "&lt;");
121        assert_eq!(html_escape(">"), "&gt;");
122        assert_eq!(html_escape("\""), "&quot;");
123        assert_eq!(html_escape("'"), "&#x27;");
124        assert_eq!(html_escape("Hello <World>"), "Hello &lt;World&gt;");
125    }
126
127    #[test]
128    fn test_html_escape_prevents_xss() {
129        let malicious = "<script>alert('xss')</script>";
130        let escaped = html_escape(malicious);
131        assert!(!escaped.contains("<script>"));
132        assert!(escaped.contains("&lt;script&gt;"));
133    }
134
135    #[test]
136    fn test_to_html_escapes_content() {
137        let events = vec![MemoryEvent::allocate(0x1000, 64, 1)
138            .with_var_name("<script>alert('xss')</script>".to_string())];
139        let view = MemoryView::from_events(events);
140        let engine = ExportEngine::new(&view);
141        let html = engine.to_html().expect("HTML export should succeed");
142
143        // Should not contain unescaped script tags
144        assert!(!html.contains("<script>"));
145        // Should contain escaped version
146        assert!(html.contains("&lt;script&gt;"));
147    }
148}