Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  report.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Support for crash report creation and upload.
//!
//! Upload currently uses the system libcurl or curl binary rather than a rust network stack (as
//! curl is more mature, albeit the code to interact with it must be a bit more careful).

use super::http;
use crate::std::path::Path;

#[cfg(mock)]
use crate::std::mock::{mock_key, MockKey};

#[cfg(mock)]
mock_key! {
    /// The outer Result is for CrashReport::send(), the inner is for CrashReporterSender::finish().
    pub struct MockReport => Box<dyn Fn(&CrashReport) -> std::io::Result<std::io::Result<String>> + Send + Sync>
}

/// A crash report to upload.
///
/// Post a multipart form payload to the report URL.
///
/// The form data contains:
/// | name | filename | content | mime |
/// ====================================
/// | `extra` | `extra.json` | extra json object | `application/json`|
/// | `upload_file_minidump` | dump file name | dump file contents | derived (probably application/binary) |
/// if present:
/// | `memory_report` | memory file name | memory file contents | derived (probably gzipped json) |
pub struct CrashReport<'a> {
    pub extra: &'a serde_json::Value,
    pub dump_file: &'a Path,
    pub memory_file: Option<&'a Path>,
    pub url: &'a str,
}

impl CrashReport<'_> {
    /// Send the crash report.
    pub fn send(&self) -> std::io::Result<CrashReportSender> {
        #[cfg(mock)]
        if let Some(r) = MockReport.try_get(|f| f(self)) {
            return r.map(|inner| {
                CrashReportSender(http::Request::Mock {
                    response: inner.map(|s| s.into()),
                })
            });
        }

        let extra_json_data = serde_json::to_string(self.extra)?;

        let mut parts = vec![
            http::MimePart {
                name: "extra",
                content: http::MimePartContent::String(&extra_json_data),
                filename: Some("extra.json"),
                mime_type: Some("application/json"),
            },
            http::MimePart {
                name: "upload_file_minidump",
                content: http::MimePartContent::File(self.dump_file),
                filename: None,
                mime_type: None,
            },
        ];
        if let Some(path) = self.memory_file {
            parts.push(http::MimePart {
                name: "memory_report",
                content: http::MimePartContent::File(path),
                filename: None,
                mime_type: None,
            })
        }

        http::RequestBuilder::MimePost { parts }
            .build(self.url)
            .map(CrashReportSender)
    }
}

pub struct CrashReportSender(http::Request);

impl CrashReportSender {
    pub fn finish(self) -> anyhow::Result<Response> {
        let response = self.0.send()?;
        let response = String::from_utf8_lossy(&response).into_owned();
        log::debug!("received response from sending report: {:?}", &*response);
        Ok(Response::parse(response))
    }
}

/// A parsed response from submitting a crash report.
#[derive(Default, Debug)]
pub struct Response {
    pub crash_id: Option<String>,
    pub stop_sending_reports_for: Option<String>,
    pub view_url: Option<String>,
    pub discarded: bool,
}

impl Response {
    /// Parse a server response.
    ///
    /// The response should be newline-separated `<key>=<value>` pairs.
    fn parse<S: AsRef<str>>(response: S) -> Self {
        let mut ret = Self::default();
        // Fields may be omitted, and parsing is best-effort but will not produce any errors (just
        // a default Response struct).
        for line in response.as_ref().lines() {
            if let Some((key, value)) = line.split_once('=') {
                match key {
                    "StopSendingReportsFor" => {
                        ret.stop_sending_reports_for = Some(value.to_owned())
                    }
                    "Discarded" => ret.discarded = true,
                    "CrashID" => ret.crash_id = Some(value.to_owned()),
                    "ViewURL" => ret.view_url = Some(value.to_owned()),
                    _ => (),
                }
            }
        }
        ret
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::std::mock;

    #[test]
    fn report_http() {
        for memory_file in [None, Some(Path::new("minidump.memory.gz"))] {
            let report = CrashReport {
                extra: &serde_json::json! {{
                    "Foo": "Bar"
                }},
                dump_file: Path::new("minidump.dmp"),
                memory_file,
                url: "reports.example.com".as_ref(),
            };

            let checked = crate::test::Counter::new();

            mock::builder()
                .set(
                    http::MockHttp,
                    Box::new(cc!(
                        (checked)
                        move |request, url| {
                            checked.inc();
                            assert_eq!(url, "reports.example.com");
                            let mut parts = vec![
                                http::MimePart {
                                    name: "extra",
                                    content: http::MimePartContent::String(r#"{"Foo":"Bar"}"#),
                                    filename: Some("extra.json"),
                                    mime_type: Some("application/json"),
                                },
                                http::MimePart {
                                    name: "upload_file_minidump",
                                    content: http::MimePartContent::File(Path::new("minidump.dmp")),
                                    filename: None,
                                    mime_type: None,
                                },
                            ];
                            if let Some(name) = memory_file {
                                parts.push(http::MimePart {
                                    name: "memory_report",
                                    content: http::MimePartContent::File(name),
                                    filename: None,
                                    mime_type: None,
                                });
                            }
                            assert_eq!(request, &http::RequestBuilder::MimePost { parts });

                            Ok(Ok(vec![]))
                        }
                    )),
                )
                .run(|| report.send().unwrap());

            checked.assert_one();
        }
    }

    #[test]
    fn report_response() {
        let report = CrashReport {
            extra: &serde_json::json! {{}},
            dump_file: Path::new("minidump.dmp"),
            memory_file: None,
            url: "reports.example.com".as_ref(),
        };

        mock::builder()
                .set(
                    http::MockHttp,
                    Box::new(|_request, _url| {
                        Ok(Ok("CrashID=1234\nDiscarded=1\nStopSendingReportsFor=100\nViewURL=reports.example.com/foo".into()))
                    }),
                )
                .run(|| {
                    let response = report.send().unwrap().finish().unwrap();
                    assert_eq!(response.crash_id.as_deref(), Some("1234"));
                    assert!(response.discarded);
                    assert_eq!(response.stop_sending_reports_for.as_deref(), Some("100"));
                    assert_eq!(response.view_url.as_deref(), Some("reports.example.com/foo"));
                });
    }
}

[ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge