Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/crashreporter/client/app/src/net/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 19 kB image not shown  

Quelle  http.rs

  Sprache: Rust
 

/* 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/. */


//! Interface to HTTP requests/responses which will transparently use various backends to send the
//! data.
//!
//! This is far from a general implementation, instead implementing the sort of requests that we
//! need.

use crate::config::installation_program_path;
use crate::std::{
    self, env,
    fs::{File, OpenOptions},
    io::{Read, Seek},
    mem::ManuallyDrop,
    path::{Path, PathBuf},
    process::Child,
    sync::atomic::{AtomicUsize, Ordering::Relaxed},
};
use anyhow::Context;
use once_cell::sync::Lazy;
use serde::Serialize;

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

#[cfg(mock)]
mock_key! {
    /// The outer Result is for RequestBuilder::build(), the inner is for Request::send().
    pub struct MockHttp => Box<dyn Fn(&RequestBuilder, &str) -> std::io::Result<std::io::Result<Vec<u8>>> + Send + Sync>
}

#[cfg(mock)]
impl MockHttp {
    /// If returned from a MockHttp callback, other transports will be attempted.
    #[allow(unused)]
    pub fn try_others() -> std::io::Result<std::io::Result<Vec<u8>>> {
        Err(std::io::ErrorKind::Interrupted.into())
    }
}

/// The user agent used by this application.
pub fn user_agent() -> &'static str {
    static USER_AGENT: Lazy<String> = Lazy::new(|| {
        format!(
            "{}/{} ({} {})",
            env!("CARGO_PKG_NAME"),
            mozbuild::config::MOZ_APP_VERSION,
            std::env::consts::OS,
            std::env::consts::ARCH,
        )
    });
    &*USER_AGENT
}

pub(cratestruct BackgroundTaskAttempts {
    remaining: AtomicUsize,
}

impl BackgroundTaskAttempts {
    #[cfg_attr(all(mock, not(test)), allow(unused))]
    pub const fn new(count: usize) -> Self {
        BackgroundTaskAttempts {
            remaining: AtomicUsize::new(count),
        }
    }

    /// Returns whether the background task should be attempted again.
    pub fn should_attempt(&self) -> bool {
        self.remaining.load(Relaxed) != 0
    }

    /// Record a failed background task attempt.
    pub fn failed(&self) {
        self.remaining.fetch_sub(1, Relaxed);
    }

    /// Remove all background task attempts.
    pub fn drain(&self) {
        self.remaining.store(0, Relaxed);
    }
}

std::mock::mocked_static! {
    /// How many times the background task may fail before discontinuing use.
    ///
    /// The background task may repeatedly fail for many reasons, for example due to a crash or
    /// misconfigured network settings.
    static BACKGROUND_TASK_ATTEMPTS: BackgroundTaskAttempts = BackgroundTaskAttempts::new(2);

    impl BACKGROUND_TASK_ATTEMPTS {
        fn should_attempt(self) -> bool {
            Self.try_get(|v| v.should_attempt()).unwrap_or(true)
        }

        fn failed(self) {
            Self.try_get(|v| v.failed());
        }

        fn drain(self) {
            Self.try_get(|v| v.drain());
        }
    }
}

// TODO set reasonable connect timeout and low speed limit?

/*
 * IMPORTANT! Keep the serialized JSON format for RequestBuilder compatible with
 * toolkit/crashreporter/networking/BackgroundTask_crashNetwork.sys.mjs
 */


/// Types of requests that can be created.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type")]
pub enum RequestBuilder<'a> {
    /// Send a POST with multiple mime parts.
    MimePost { parts: Vec<MimePart<'a>> },
    /// Send a POST.
    Post {
        body: &'a [u8],
        headers: &'a [(String, String)],
    },
}

/// A single mime part to send.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MimePart<'a> {
    pub name: &'a str,
    pub content: MimePartContent<'a>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filename: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<&'a str>,
}

/// The content of a mime part.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type", content = "value")]
pub enum MimePartContent<'a> {
    /// Send a file's contents.
    File(&'a Path),
    /// Send a specific string as the contents.
    String(&'a str),
}

/// A request that is ready to be sent.
pub enum Request<'a> {
    BackgroundTaskChild {
        child: Child,
        file: TempRequestFile,
        builder: RequestBuilder<'a>,
        url: &'a str,
    },
    CurlChild {
        child: Child,
        stdin: Option<Box<dyn Read + Send + 'static>>,
    },
    LibCurl {
        easy: super::libcurl::Easy<'static>,
    },
    #[cfg(mock)]
    Mock {
        response: std::io::Result<Vec<u8>>,
    },
}

impl<'a> RequestBuilder<'a> {
    /// Build the request with the given url.
    pub fn build(&self, url: &'a str) -> std::io::Result<Request<'a>> {
        log::debug!("starting request to {url}: {self:?}");

        // When mocking is enabled, check for that first.
        #[cfg(mock)]
        if let Some(r) = self.try_send_with_mock(url) {
            return r;
        }

        // First we try to invoke a firefox background task to send the request. This is
        // preferrable because it will respect the user's network settings, however it is less
        // reliable if the cause of our crash is a bug in early firefox startup or network code.
        if BACKGROUND_TASK_ATTEMPTS.should_attempt() {
            match self.send_with_background_task(url) {
                Ok(r) => return Ok(r),
                Err(e) => {
                    log::info!(
                        "failed to invoke background task ({e}), falling back to curl backend"
                    );
                    // A failure to spawn the background task more than likely indicates it will
                    // never work in the future.
                    BACKGROUND_TASK_ATTEMPTS.drain();
                }
            };
        }

        self.send_with_curl(url)
    }

    /// Send the request with the firefox `crashreporterNetworkBackend` background task.
    fn send_with_background_task(&self, url: &'a str) -> std::io::Result<Request<'a>> {
        let path = installation_program_path(mozbuild::config::MOZ_APP_NAME);
        let mut cmd = crate::process::background_command(path);
        cmd.args(["--backgroundtask""crashreporterNetworkBackend"]);
        cmd.arg(url);
        cmd.arg(user_agent());
        // Disable crash reporting in the background task. We don't want a crash in the background
        // task to launch another crash reporter flow. See bugs 1991491/1987145.
        cmd.env("MOZ_CRASHREPORTER_DISABLE""1")
            .env_remove("MOZ_CRASHREPORTER");

        let mut file = TempRequestFile::new()?;
        serde_json::to_writer(&mut *file, self)?;
        cmd.arg(&file.path);

        cmd.stdin(std::process::Stdio::null());
        cmd.stdout(std::process::Stdio::piped());
        cmd.stderr(std::process::Stdio::piped());

        cmd.spawn().map(move |child| Request::BackgroundTaskChild {
            child,
            file,
            builder: self.clone(),
            url,
        })
    }

    /// Send the request with the curl backend.
    pub fn send_with_curl(&self, url: &str) -> std::io::Result<Request<'static>> {
        // Windows 10+ and macOS 10.15+ contain `curl` 7.64.1+ as a system-provided executable, so
        // `send_with_curl_executable` should not fail.
        //
        // Linux distros generally do not contain `curl`, but `libcurl` is very likely to be
        // incidentally installed (if not outright part of the distro base packages). Based on a
        // cursory look at the debian repositories as an exemplar, the curl executable (rather than
        // library) is much less likely to be incidentally installed.
        //
        // For uniformity, we always will try the curl executable first, then try libcurl if that
        // fails.

        let curl_err = match self.send_with_curl_executable(url) {
            Ok(r) => return Ok(r),
            Err(e) => e,
        };

        // When mocking is enabled, default to _not_ using libcurl (because there is no mock
        // interface; it will really use libcurl). However we add a hook to use libcurl for tests
        // which want to send real data to some server (which may be a local test server).
        #[cfg(mock)]
        if !crate::std::mock::try_hook(false"use_system_libcurl") {
            log::error!("use_system_libcurl not enabled and curl failed: {curl_err}");
            panic!("no mock handler available to build http request");
        }

        log::info!("failed to invoke curl ({curl_err}), trying libcurl");

        self.send_with_libcurl(url)
    }

    /// Send the request with the `curl` executable.
    fn send_with_curl_executable(&self, url: &str) -> std::io::Result<Request<'static>> {
        let mut cmd = crate::process::background_command("curl");
        let mut stdin: Option<Box<dyn Read + Send + 'static>> = None;

        cmd.arg("--fail");
        cmd.args(["--user-agent", user_agent()]);

        match self {
            Self::MimePost { parts } => {
                for part in parts {
                    part.curl_command_args(&mut cmd, &mut stdin)?;
                }
            }
            Self::Post { body, headers } => {
                for (k, v) in headers.iter() {
                    cmd.args(["--header", &format!("{k}: {v}")]);
                }

                cmd.args(["--data-binary""@-"]);
                stdin = Some(Box::new(std::io::Cursor::new(body.to_vec())));
            }
        }

        cmd.arg(url);

        cmd.stdin(std::process::Stdio::piped());
        cmd.stdout(std::process::Stdio::piped());
        cmd.stderr(std::process::Stdio::piped());

        cmd.spawn()
            .map(move |child| Request::CurlChild { child, stdin })
    }

    /// Send the request with the `curl` library.
    fn send_with_libcurl(&self, url: &str) -> std::io::Result<Request<'static>> {
        let curl = super::libcurl::load()?;
        let mut easy = curl.easy()?;

        easy.set_url(url)?;
        easy.set_user_agent(user_agent())?;
        easy.set_max_redirs(30)?;

        match self {
            Self::MimePost { parts } => {
                let mut mime = easy.mime()?;

                for part in parts {
                    part.curl_mime(&mut mime)?;
                }

                easy.set_mime_post(mime)?;
            }
            Self::Post { body, headers } => {
                let mut header_list = easy.slist();
                for (k, v) in headers.iter() {
                    header_list.append(&format!("{k}: {v}"))?;
                }
                easy.set_headers(header_list)?;

                easy.set_postfields(*body)?;
            }
        }

        Ok(Request::LibCurl { easy })
    }

    #[cfg(mock)]
    fn try_send_with_mock(&self, url: &str) -> Option<std::io::Result<Request<'static>>> {
        let result = MockHttp.try_get(|f| f(self, url))?;
        if result
            .as_ref()
            .err()
            .map(|e| e.kind() == std::io::ErrorKind::Interrupted)
            .unwrap_or(false)
        {
            None
        } else {
            Some(result.map(|response| Request::Mock { response }))
        }
    }
}

pub struct TempRequestFile {
    path: PathBuf,
    file: ManuallyDrop<File>,
}

static REQUEST_NUM: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);

impl TempRequestFile {
    fn new() -> std::io::Result<Self> {
        // Incorporate process id and a process-unique id to avoid runtime conflicts.
        let path = std::env::temp_dir().join(format!(
            "{}{}-request{}.json",
            env!("CARGO_PKG_NAME"),
            std::process::id(),
            REQUEST_NUM.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
        ));
        if let Some(p) = path.parent() {
            std::fs::create_dir_all(p)?;
        }
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .truncate(true)
            .open(&path)?;
        Ok(TempRequestFile {
            path,
            file: ManuallyDrop::new(file),
        })
    }
}

impl std::ops::Deref for TempRequestFile {
    type Target = File;
    fn deref(&self) -> &Self::Target {
        &self.file
    }
}

impl std::ops::DerefMut for TempRequestFile {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.file
    }
}

impl Drop for TempRequestFile {
    fn drop(&mut self) {
        // # Safety
        // We do not use self.file after this
        unsafe { ManuallyDrop::drop(&mut self.file) };
        let _ = std::fs::remove_file(&self.path);
    }
}

impl MimePart<'_> {
    fn curl_command_args(
        &self,
        cmd: &mut crate::std::process::Command,
        stdin: &mut Option<Box<dyn Read + Send + 'static>>,
    ) -> std::io::Result<()> {
        use std::fmt::Write;
        let mut formarg = format!("{}="self.name);
        match self.content {
            MimePartContent::File(f) => {
                write!(formarg, "@{}", CurlQuote(&f.display().to_string())).unwrap()
            }
            MimePartContent::String(s) => {
                // `@-` causes the data to be read from stdin, which is desirable to
                // not have to worry about process argument string length limitations
                // (though they are generally pretty high limits).
                write!(formarg, "@-").unwrap();
                if stdin
                    .replace(Box::new(std::io::Cursor::new(s.to_owned())))
                    .is_some()
                {
                    return Err(std::io::Error::other(
                        "only one MimePartContent::String supported",
                    ));
                }
            }
        }
        if let Some(filename) = self.filename {
            write!(formarg, ";filename={}", filename).unwrap();
        }
        if let Some(mime_type) = self.mime_type {
            write!(formarg, ";type={}", mime_type).unwrap();
        }

        cmd.arg("--form");
        cmd.arg(formarg);

        Ok(())
    }

    fn curl_mime(&self, mime: &mut super::libcurl::Mime) -> std::io::Result<()> {
        let mut p = mime.add_part()?;
        p.set_name(&self.name)?;
        match self.content {
            MimePartContent::File(f) => {
                p.set_filename(&f.display().to_string())?;
                p.set_filedata(f)?;
            }
            MimePartContent::String(s) => {
                p.set_data(s.as_bytes())?;
            }
        }
        if let Some(filename) = self.filename {
            p.set_filename(filename)?;
        }
        if let Some(mime_type) = self.mime_type {
            p.set_type(mime_type)?;
        }
        Ok(())
    }
}

impl Request<'_> {
    /// Send the request, returning the response body (if any).
    pub fn send(self) -> anyhow::Result<Vec<u8>> {
        Ok(match self {
            Self::BackgroundTaskChild {
                child,
                mut file,
                builder,
                url,
            } => {
                (move || {
                    let output = child
                        .wait_with_output()
                        .context("failed to wait on background task process")?;

                    if !output.status.success() {
                        BACKGROUND_TASK_ATTEMPTS.failed();
                        if !BACKGROUND_TASK_ATTEMPTS.should_attempt() {
                            log::info!("the background task process has exceeded the acceptable number of failures and will no longer be used");
                        }

                        anyhow::bail!(
                            "process failed (exit status {}) with stderr: {}",
                            output.status,
                            String::from_utf8_lossy(&output.stderr)
                        );
                    }

                    file.rewind().context("failed to rewind response file")?;
                    let mut ret = Vec::new();
                    file.read_to_end(&mut ret)
                        .context("failed to read response file")?;
                    Ok(ret)
                })()
                .or_else(|e| {
                    // If any error occurs, we try again with the curl backend. In theory we
                    // could do this specifically if the process returns a failure exit code,
                    // however since we want to give the best effort in sending the request, we
                    // do it for any failure.
                    log::error!("background task error: {e:#}");
                    log::info!("falling back to curl backend");

                    builder.send_with_curl(url).context("curl error")?.send()
                })?
            }
            Self::CurlChild { mut child, stdin } => {
                if let Some(mut stdin) = stdin {
                    let mut child_stdin = child
                        .stdin
                        .take()
                        .context("failed to get curl process stdin")?;
                    std::io::copy(&mut stdin, &mut child_stdin)
                        .context("failed to write to stdin of curl process")?;
                    // stdin is dropped at the end of this scope so that the stream gets an EOF,
                    // otherwise curl will wait for more input.
                }
                let output = child
                    .wait_with_output()
                    .context("failed to wait on curl process")?;
                anyhow::ensure!(
                    output.status.success(),
                    "process failed (exit status {}) with stderr: {}",
                    output.status,
                    String::from_utf8_lossy(&output.stderr)
                );
                output.stdout
            }
            Self::LibCurl { easy } => {
                let response = easy.perform()?;
                let response_code = easy.get_response_code()?;

                anyhow::ensure!(
                    response_code == 200,
                    "unexpected response code ({response_code}): {}",
                    String::from_utf8_lossy(&response).as_ref()
                );

                response
            }
            #[cfg(mock)]
            Self::Mock { response } => response?,
        })
    }
}

/// Quote a string per https://curl.se/docs/manpage.html#-F.
/// That is, add quote characters and escape " and \ with backslashes.
struct CurlQuote<'a>(&'a str);

impl CurlQuote<'_> {
    fn quoted(&self) -> String {
        let quote = std::iter::once(&b'"');
        let escaped = self.0.as_bytes().iter().flat_map(|b| match b {
            b'"' => br#"\""#.as_slice(),
            b'\\' => br#"\\"#.as_slice(),
            other => std::slice::from_ref(other),
        });
        let bytes = quote.clone().chain(escaped).chain(quote).copied().collect();
        // # Safety
        // The source bytes came from a `str`, so must have been valid utf8. We are inserting valid
        // utf8 characters (quotes and backslashes, which are single bytes in utf8) at character
        // boundaries (at the beginning/end of the string and before other backslash/quote
        // characters).
        unsafe { String::from_utf8_unchecked(bytes) }
    }
}

impl std::fmt::Display for CurlQuote<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}"self.quoted())
    }
}

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

    #[test]
    fn curl_quote() {
        assert_eq!(
            CurlQuote(r#"hello "world""#).to_string(),
            r#""hello \"world\"""#
        );
        assert_eq!(
            CurlQuote(r#"C:\dir\\"\""dir""#).to_string(),
            r#""C:\\dir\\\\\"\\\"\"dir\"""#
        );
    }
}

Messung V0.5 in Prozent
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.