<commit_before>// Copyright (c) 2017-2018, The rav1e contributors. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.

use context::BlockOffset;
use context::BLOCK_TO_PLANE_SHIFT;
use context::MI_SIZE;
use partition::*;
use plane::*;
use FrameInvariants;
use FrameState;

#[inline(always)]
pub fn get_sad(
  plane_org: &mut PlaneSlice, plane_ref: &mut PlaneSlice, blk_h: usize,
  blk_w: usize
) -> u32 {
  let mut sum = 0 as u32;

  for _r in 0..blk_h {
    {
      let slice_org = plane_org.as_slice_w_width(blk_w);
      let slice_ref = plane_ref.as_slice_w_width(blk_w);
      sum += slice_org
        .iter()
        .zip(slice_ref)
        .map(|(&a, &b)| (a as i32 - b as i32).abs() as u32)
        .sum::<u32>();
    }
    plane_org.y += 1;
    plane_ref.y += 1;
  }

  sum
}

pub fn motion_estimation(
  fi: &FrameInvariants, fs: &FrameState, bsize: BlockSize,
  bo: &BlockOffset, ref_frame: usize, pmv: &MotionVector
) -> MotionVector {
  match fi.rec_buffer.frames[fi.ref_frames[ref_frame - LAST_FRAME]] {
    Some(ref rec) => {
      let po = PlaneOffset {
        x: (bo.x as isize) << BLOCK_TO_PLANE_SHIFT,
        y: (bo.y as isize) << BLOCK_TO_PLANE_SHIFT
      };
      let range = 32 as isize;
      let blk_w = bsize.width();
      let blk_h = bsize.height();
      let border_w = 128 + blk_w as isize * 8;
      let border_h = 128 + blk_h as isize * 8;
      let mvx_min = -(bo.x as isize) * (8 * MI_SIZE) as isize - border_w;
      let mvx_max = (fi.w_in_b - bo.x - blk_w / MI_SIZE) as isize * (8 * MI_SIZE) as isize + border_w;
      let mvy_min = -(bo.y as isize) * (8 * MI_SIZE) as isize - border_h;
      let mvy_max = (fi.h_in_b - bo.y - blk_h / MI_SIZE) as isize * (8 * MI_SIZE) as isize + border_h;
      let x_lo = po.x + ((-range + (pmv.col / 8) as isize).max(mvx_min / 8));
      let x_hi = po.x + ((range + (pmv.col / 8) as isize).min(mvx_max / 8));
      let y_lo = po.y + ((-range + (pmv.row / 8) as isize).max(mvy_min / 8));
      let y_hi = po.y + ((range + (pmv.row / 8) as isize).min(mvy_max / 8));

      let mut lowest_sad = 128 * 128 * 4096 as u32;
      let mut best_mv = MotionVector { row: 0, col: 0 };

      for y in (y_lo..y_hi).step_by(2) {
        for x in (x_lo..x_hi).step_by(2) {
          let mut plane_org = fs.input.planes[0].slice(&po);
          let mut plane_ref = rec.frame.planes[0].slice(&PlaneOffset { x, y });

          let sad = get_sad(&mut plane_org, &mut plane_ref, blk_h, blk_w);

          if sad < lowest_sad {
            lowest_sad = sad;
            best_mv = MotionVector {
              row: 8 * (y as i16 - po.y as i16),
              col: 8 * (x as i16 - po.x as i16)
            }
          }
        }
      }

      let mode = PredictionMode::NEWMV;
      let mut tmp_plane = Plane::new(blk_w, blk_h, 0, 0, 0, 0);

      let mut steps = vec![8, 4, 2];
      if fi.allow_high_precision_mv {
        steps.push(1);
      }

      for step in steps {
        let center_mv_h = best_mv;
        for i in 0..3 {
          for j in 0..3 {
            // Skip the center point that was already tested
            if i == 1 && j == 1 {
              continue;
            }

            let cand_mv = MotionVector {
              row: center_mv_h.row + step * (i as i16 - 1),
              col: center_mv_h.col + step * (j as i16 - 1)
            };

            if (cand_mv.col as isize) < mvx_min || (cand_mv.col as isize) > mvx_max {
              continue;
            }
            if (cand_mv.row as isize) < mvy_min || (cand_mv.row as isize) > mvy_max {
              continue;
            }

            {
              let tmp_slice =
                &mut tmp_plane.mut_slice(&PlaneOffset { x: 0, y: 0 });

              mode.predict_inter(
                fi, 0, &po, tmp_slice, blk_w, blk_h, ref_frame, &cand_mv, 8,
              );
            }

            let mut plane_org = fs.input.planes[0].slice(&po);
            let mut plane_ref = tmp_plane.slice(&PlaneOffset { x: 0, y: 0 });

            let sad = get_sad(&mut plane_org, &mut plane_ref, blk_h, blk_w);

            if sad < lowest_sad {
              lowest_sad = sad;
              best_mv = cand_mv;
            }
          }
        }
      }

      best_mv
    }

    None => MotionVector { row: 0, col: 0 }
  }
}
<commit_msg>Add unit/regression test for SAD implementations (#611)<commit_after>// Copyright (c) 2017-2018, The rav1e contributors. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.

use context::BlockOffset;
use context::BLOCK_TO_PLANE_SHIFT;
use context::MI_SIZE;
use partition::*;
use plane::*;
use FrameInvariants;
use FrameState;

#[inline(always)]
pub fn get_sad(
  plane_org: &mut PlaneSlice, plane_ref: &mut PlaneSlice, blk_h: usize,
  blk_w: usize
) -> u32 {
  let mut sum = 0 as u32;

  for _r in 0..blk_h {
    {
      let slice_org = plane_org.as_slice_w_width(blk_w);
      let slice_ref = plane_ref.as_slice_w_width(blk_w);
      sum += slice_org
        .iter()
        .zip(slice_ref)
        .map(|(&a, &b)| (a as i32 - b as i32).abs() as u32)
        .sum::<u32>();
    }
    plane_org.y += 1;
    plane_ref.y += 1;
  }

  sum
}

pub fn motion_estimation(
  fi: &FrameInvariants, fs: &FrameState, bsize: BlockSize,
  bo: &BlockOffset, ref_frame: usize, pmv: &MotionVector
) -> MotionVector {
  match fi.rec_buffer.frames[fi.ref_frames[ref_frame - LAST_FRAME]] {
    Some(ref rec) => {
      let po = PlaneOffset {
        x: (bo.x as isize) << BLOCK_TO_PLANE_SHIFT,
        y: (bo.y as isize) << BLOCK_TO_PLANE_SHIFT
      };
      let range = 32 as isize;
      let blk_w = bsize.width();
      let blk_h = bsize.height();
      let border_w = 128 + blk_w as isize * 8;
      let border_h = 128 + blk_h as isize * 8;
      let mvx_min = -(bo.x as isize) * (8 * MI_SIZE) as isize - border_w;
      let mvx_max = (fi.w_in_b - bo.x - blk_w / MI_SIZE) as isize * (8 * MI_SIZE) as isize + border_w;
      let mvy_min = -(bo.y as isize) * (8 * MI_SIZE) as isize - border_h;
      let mvy_max = (fi.h_in_b - bo.y - blk_h / MI_SIZE) as isize * (8 * MI_SIZE) as isize + border_h;
      let x_lo = po.x + ((-range + (pmv.col / 8) as isize).max(mvx_min / 8));
      let x_hi = po.x + ((range + (pmv.col / 8) as isize).min(mvx_max / 8));
      let y_lo = po.y + ((-range + (pmv.row / 8) as isize).max(mvy_min / 8));
      let y_hi = po.y + ((range + (pmv.row / 8) as isize).min(mvy_max / 8));

      let mut lowest_sad = 128 * 128 * 4096 as u32;
      let mut best_mv = MotionVector { row: 0, col: 0 };

      for y in (y_lo..y_hi).step_by(2) {
        for x in (x_lo..x_hi).step_by(2) {
          let mut plane_org = fs.input.planes[0].slice(&po);
          let mut plane_ref = rec.frame.planes[0].slice(&PlaneOffset { x, y });

          let sad = get_sad(&mut plane_org, &mut plane_ref, blk_h, blk_w);

          if sad < lowest_sad {
            lowest_sad = sad;
            best_mv = MotionVector {
              row: 8 * (y as i16 - po.y as i16),
              col: 8 * (x as i16 - po.x as i16)
            }
          }
        }
      }

      let mode = PredictionMode::NEWMV;
      let mut tmp_plane = Plane::new(blk_w, blk_h, 0, 0, 0, 0);

      let mut steps = vec![8, 4, 2];
      if fi.allow_high_precision_mv {
        steps.push(1);
      }

      for step in steps {
        let center_mv_h = best_mv;
        for i in 0..3 {
          for j in 0..3 {
            // Skip the center point that was already tested
            if i == 1 && j == 1 {
              continue;
            }

            let cand_mv = MotionVector {
              row: center_mv_h.row + step * (i as i16 - 1),
              col: center_mv_h.col + step * (j as i16 - 1)
            };

            if (cand_mv.col as isize) < mvx_min || (cand_mv.col as isize) > mvx_max {
              continue;
            }
            if (cand_mv.row as isize) < mvy_min || (cand_mv.row as isize) > mvy_max {
              continue;
            }

            {
              let tmp_slice =
                &mut tmp_plane.mut_slice(&PlaneOffset { x: 0, y: 0 });

              mode.predict_inter(
                fi, 0, &po, tmp_slice, blk_w, blk_h, ref_frame, &cand_mv, 8,
              );
            }

            let mut plane_org = fs.input.planes[0].slice(&po);
            let mut plane_ref = tmp_plane.slice(&PlaneOffset { x: 0, y: 0 });

            let sad = get_sad(&mut plane_org, &mut plane_ref, blk_h, blk_w);

            if sad < lowest_sad {
              lowest_sad = sad;
              best_mv = cand_mv;
            }
          }
        }
      }

      best_mv
    }

    None => MotionVector { row: 0, col: 0 }
  }
}

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

  // Generate plane data for get_sad_same()
  fn setup_sad() -> (Plane, Plane) {
    let mut input_plane = Plane::new(640, 480, 0, 0, 128 + 8, 128 + 8);
    let mut rec_plane = input_plane.clone();

    for (i, row) in input_plane.data.chunks_mut(input_plane.cfg.stride).enumerate() {
      for (j, mut pixel) in row.into_iter().enumerate() {
        *pixel = ((j + i) % 256) as u16;
      }
    }

    for (i, row) in rec_plane.data.chunks_mut(rec_plane.cfg.stride).enumerate() {
      for (j, mut pixel) in row.into_iter().enumerate() {
        *pixel = ((j as i32 - i as i32) % 256) as u16;
      }
    }

    (input_plane, rec_plane)
  }

  // Regression and validation test for SAD computation
  #[test]
  fn get_sad_same() {
    use partition::BlockSize;
    use partition::BlockSize::*;

    let blocks: Vec<(BlockSize, u32)> = vec![
      (BLOCK_4X4, 393592),
      (BLOCK_4X8, 395176),
      (BLOCK_8X4, 1440456),
      (BLOCK_8X8, 1835664),
      (BLOCK_8X16, 1842256),
      (BLOCK_16X8, 6022352),
      (BLOCK_16X16, 7864736),
      (BLOCK_16X32, 7893152),
      (BLOCK_32X16, 24605344),
      (BLOCK_32X32, 32499008),
      (BLOCK_32X64, 32629056),
      (BLOCK_64X32, 99412288),
      (BLOCK_64X64, 132043392),
      (BLOCK_64X128, 132621248),
      (BLOCK_128X64, 399430272),
      (BLOCK_128X128, 532067552),
      (BLOCK_4X16, 398344),
      (BLOCK_16X4, 3533800),
      (BLOCK_8X32, 1855440),
      (BLOCK_32X8, 14392656),
      (BLOCK_16X64, 7949984),
      (BLOCK_64X16, 58061984),
    ];

    let (input_plane, rec_plane) = setup_sad();

    for block in blocks {
        let bsw = block.0.width();
        let bsh = block.0.height();
        let po = PlaneOffset { x: 40, y: 40 };

        let mut input_slice = input_plane.slice(&po);
        let mut rec_slice = rec_plane.slice(&po);

        assert_eq!(block.1, get_sad(&mut input_slice, &mut rec_slice, bsw, bsh));
    }
  }
}
<|endoftext|>


<commit_before>use ipnetwork::IpNetwork;

use crate::publish_rate_limit::PublishRateLimit;
use crate::{env, env_optional, uploaders::Uploader, Env};

mod base;
mod database_pools;

pub use self::base::Base;
pub use self::database_pools::{DatabasePools, DbPoolConfig};
use std::collections::HashSet;
use std::time::Duration;

const DEFAULT_VERSION_ID_CACHE_SIZE: u64 = 10_000;
const DEFAULT_VERSION_ID_CACHE_TTL: u64 = 5 * 60; // 5 minutes

pub struct Server {
    pub base: Base,
    pub db: DatabasePools,
    pub session_key: String,
    pub gh_client_id: String,
    pub gh_client_secret: String,
    pub gh_base_url: String,
    pub max_upload_size: u64,
    pub max_unpack_size: u64,
    pub publish_rate_limit: PublishRateLimit,
    pub blocked_traffic: Vec<(String, Vec<String>)>,
    pub max_allowed_page_offset: u32,
    pub page_offset_ua_blocklist: Vec<String>,
    pub page_offset_cidr_blocklist: Vec<IpNetwork>,
    pub excluded_crate_names: Vec<String>,
    pub domain_name: String,
    pub allowed_origins: Vec<String>,
    pub downloads_persist_interval_ms: usize,
    pub ownership_invitations_expiration_days: u64,
    pub metrics_authorization_token: Option<String>,
    pub use_test_database_pool: bool,
    pub instance_metrics_log_every_seconds: Option<u64>,
    pub force_unconditional_redirects: bool,
    pub blocked_routes: HashSet<String>,
    pub version_id_cache_size: u64,
    pub version_id_cache_ttl: Duration,
}

impl Default for Server {
    /// Returns a default value for the application's config
    ///
    /// Sets the following default values:
    ///
    /// - `Config::max_upload_size`: 10MiB
    /// - `Config::ownership_invitations_expiration_days`: 30
    ///
    /// Pulls values from the following environment variables:
    ///
    /// - `SESSION_KEY`: The key used to sign and encrypt session cookies.
    /// - `GH_CLIENT_ID`: The client ID of the associated GitHub application.
    /// - `GH_CLIENT_SECRET`: The client secret of the associated GitHub application.
    /// - `BLOCKED_TRAFFIC`: A list of headers and environment variables to use for blocking
    ///   traffic. See the `block_traffic` module for more documentation.
    /// - `DOWNLOADS_PERSIST_INTERVAL_MS`: how frequent to persist download counts (in ms).
    /// - `METRICS_AUTHORIZATION_TOKEN`: authorization token needed to query metrics. If missing,
    ///   querying metrics will be completely disabled.
    /// - `WEB_MAX_ALLOWED_PAGE_OFFSET`: Page offsets larger than this value are rejected. Defaults
    ///   to 200.
    /// - `WEB_PAGE_OFFSET_UA_BLOCKLIST`: A comma seperated list of user-agent substrings that will
    ///   be blocked if `WEB_MAX_ALLOWED_PAGE_OFFSET` is exceeded. Including an empty string in the
    ///   list will block *all* user-agents exceeding the offset. If not set or empty, no blocking
    ///   will occur.
    /// - `WEB_PAGE_OFFSET_CIDR_BLOCKLIST`: A comma separated list of CIDR blocks that will be used
    ///   to block IP addresses given in the `X-Real-Ip` HTTP header, e.g. `192.168.1.0/24`.
    ///   If not set or empty, no blocking will occur.
    /// - `INSTANCE_METRICS_LOG_EVERY_SECONDS`: How frequently should instance metrics be logged.
    ///   If the environment variable is not present instance metrics are not logged.
    /// - `FORCE_UNCONDITIONAL_REDIRECTS`: Whether to force unconditional redirects in the download
    ///   endpoint even with a healthy database pool.
    /// - `BLOCKED_ROUTES`: A comma separated list of HTTP route patterns that are manually blocked
    ///   by an operator (e.g. `/crates/:crate_id/:version/download`).
    ///
    /// # Panics
    ///
    /// This function panics if the Server configuration is invalid.
    fn default() -> Self {
        let allowed_origins = env("WEB_ALLOWED_ORIGINS")
            .split(',')
            .map(ToString::to_string)
            .collect();
        let page_offset_ua_blocklist = match env_optional::<String>("WEB_PAGE_OFFSET_UA_BLOCKLIST")
        {
            None => vec![],
            Some(s) if s.is_empty() => vec![],
            Some(s) => s.split(',').map(String::from).collect(),
        };
        let page_offset_cidr_blocklist =
            match env_optional::<String>("WEB_PAGE_OFFSET_CIDR_BLOCKLIST") {
                None => vec![],
                Some(s) if s.is_empty() => vec![],
                Some(s) => s.split(',').map(parse_cidr_block).collect(),
            };

        let base = Base::from_environment();
        let excluded_crate_names = match env_optional::<String>("EXCLUDED_CRATE_NAMES") {
            None => vec![],
            Some(s) if s.is_empty() => vec![],
            Some(s) => s.split(',').map(String::from).collect(),
        };
        Server {
            db: DatabasePools::full_from_environment(&base),
            base,
            session_key: env("SESSION_KEY"),
            gh_client_id: env("GH_CLIENT_ID"),
            gh_client_secret: env("GH_CLIENT_SECRET"),
            gh_base_url: "https://api.github.com".to_string(),
            max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
            max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
            publish_rate_limit: Default::default(),
            blocked_traffic: blocked_traffic(),
            max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200),
            page_offset_ua_blocklist,
            page_offset_cidr_blocklist,
            excluded_crate_names,
            domain_name: domain_name(),
            allowed_origins,
            downloads_persist_interval_ms: dotenv::var("DOWNLOADS_PERSIST_INTERVAL_MS")
                .map(|interval| {
                    interval
                        .parse()
                        .expect("invalid DOWNLOADS_PERSIST_INTERVAL_MS")
                })
                .unwrap_or(60_000), // 1 minute
            ownership_invitations_expiration_days: 30,
            metrics_authorization_token: dotenv::var("METRICS_AUTHORIZATION_TOKEN").ok(),
            use_test_database_pool: false,
            instance_metrics_log_every_seconds: env_optional("INSTANCE_METRICS_LOG_EVERY_SECONDS"),
            force_unconditional_redirects: dotenv::var("FORCE_UNCONDITIONAL_REDIRECTS").is_ok(),
            blocked_routes: env_optional("BLOCKED_ROUTES")
                .map(|routes: String| routes.split(',').map(|s| s.into()).collect())
                .unwrap_or_else(HashSet::new),
            version_id_cache_size: env_optional("VERSION_ID_CACHE_SIZE")
                .unwrap_or(DEFAULT_VERSION_ID_CACHE_SIZE),
            version_id_cache_ttl: Duration::from_secs(
                env_optional("VERSION_ID_CACHE_TTL").unwrap_or(DEFAULT_VERSION_ID_CACHE_TTL),
            ),
        }
    }
}

impl Server {
    pub fn env(&self) -> Env {
        self.base.env
    }

    pub fn uploader(&self) -> &Uploader {
        self.base.uploader()
    }
}

pub(crate) fn domain_name() -> String {
    dotenv::var("DOMAIN_NAME").unwrap_or_else(|_| "crates.io".into())
}

/// Parses a CIDR block string to a valid `IpNetwork` struct.
///
/// The purpose is to be able to block IP ranges that overload the API that uses pagination.
///
/// The minimum number of bits for a host prefix must be
///
/// * at least 16 for IPv4 based CIDRs.
/// * at least 64 for IPv6 based CIDRs
///
fn parse_cidr_block(block: &str) -> IpNetwork {
    let network = block.parse();
    match network {
        Ok(cidr) => {
            let host_prefix = match cidr {
                IpNetwork::V4(_) => 16,
                IpNetwork::V6(_) => 64,
            };
            if cidr.prefix() < host_prefix {
                panic!(
                    "WEB_PAGE_OFFSET_CIDR_BLOCKLIST only allows CIDR blocks with a host prefix \
                                of at least 16 bits (IPv4) or 64 bits (IPv6)."
                );
            } else {
                cidr
            }
        }
        Err(_) => panic!("WEB_PAGE_OFFSET_CIDR_BLOCKLIST must contain IPv4 or IPv6 CIDR blocks."),
    }
}

fn blocked_traffic() -> Vec<(String, Vec<String>)> {
    let pattern_list = dotenv::var("BLOCKED_TRAFFIC").unwrap_or_default();
    parse_traffic_patterns(&pattern_list)
        .map(|(header, value_env_var)| {
            let value_list = dotenv::var(value_env_var).unwrap_or_default();
            let values = value_list.split(',').map(String::from).collect();
            (header.into(), values)
        })
        .collect()
}

fn parse_traffic_patterns(patterns: &str) -> impl Iterator<Item = (&str, &str)> {
    patterns.split_terminator(',').map(|pattern| {
        if let Some(idx) = pattern.find('=') {
            (&pattern[..idx], &pattern[(idx + 1)..])
        } else {
            panic!(
                "BLOCKED_TRAFFIC must be in the form HEADER=VALUE_ENV_VAR, \
                 got invalid pattern {}",
                pattern
            )
        }
    })
}

#[test]
fn parse_traffic_patterns_splits_on_comma_and_looks_for_equal_sign() {
    let pattern_string_1 = "Foo=BAR,Bar=BAZ";
    let pattern_string_2 = "Baz=QUX";
    let pattern_string_3 = "";

    let patterns_1 = parse_traffic_patterns(pattern_string_1).collect::<Vec<_>>();
    assert_eq!(vec![("Foo", "BAR"), ("Bar", "BAZ")], patterns_1);

    let patterns_2 = parse_traffic_patterns(pattern_string_2).collect::<Vec<_>>();
    assert_eq!(vec![("Baz", "QUX")], patterns_2);

    assert_none!(parse_traffic_patterns(pattern_string_3).next());
}

#[test]
fn parse_cidr_block_list_successfully() {
    assert_eq!(
        parse_cidr_block("127.0.0.1/24"),
        "127.0.0.1/24".parse::<IpNetwork>().unwrap()
    );
    assert_eq!(
        parse_cidr_block("192.168.0.1/31"),
        "192.168.0.1/31".parse::<IpNetwork>().unwrap()
    );
}

#[test]
#[should_panic]
fn parse_cidr_blocks_panics_when_host_ipv4_prefix_is_too_low() {
    parse_cidr_block("127.0.0.1/8");
}

#[test]
#[should_panic]
fn parse_cidr_blocks_panics_when_host_ipv6_prefix_is_too_low() {
    parse_cidr_block("2001:0db8:0123:4567:89ab:cdef:1234:5678/56");
}

#[test]
fn parse_ipv6_based_cidr_blocks() {
    assert_eq!(
        parse_cidr_block("2002::1234:abcd:ffff:c0a8:101/64"),
        "2002::1234:abcd:ffff:c0a8:101/64"
            .parse::<IpNetwork>()
            .unwrap()
    );
    assert_eq!(
        parse_cidr_block("2001:0db8:0123:4567:89ab:cdef:1234:5678/92"),
        "2001:0db8:0123:4567:89ab:cdef:1234:5678/92"
            .parse::<IpNetwork>()
            .unwrap()
    );
}
<commit_msg>config: Return `Result` from `parse_cidr_block()` instead of panicking<commit_after>use anyhow::{anyhow, Context};
use ipnetwork::IpNetwork;

use crate::publish_rate_limit::PublishRateLimit;
use crate::{env, env_optional, uploaders::Uploader, Env};

mod base;
mod database_pools;

pub use self::base::Base;
pub use self::database_pools::{DatabasePools, DbPoolConfig};
use std::collections::HashSet;
use std::time::Duration;

const DEFAULT_VERSION_ID_CACHE_SIZE: u64 = 10_000;
const DEFAULT_VERSION_ID_CACHE_TTL: u64 = 5 * 60; // 5 minutes

pub struct Server {
    pub base: Base,
    pub db: DatabasePools,
    pub session_key: String,
    pub gh_client_id: String,
    pub gh_client_secret: String,
    pub gh_base_url: String,
    pub max_upload_size: u64,
    pub max_unpack_size: u64,
    pub publish_rate_limit: PublishRateLimit,
    pub blocked_traffic: Vec<(String, Vec<String>)>,
    pub max_allowed_page_offset: u32,
    pub page_offset_ua_blocklist: Vec<String>,
    pub page_offset_cidr_blocklist: Vec<IpNetwork>,
    pub excluded_crate_names: Vec<String>,
    pub domain_name: String,
    pub allowed_origins: Vec<String>,
    pub downloads_persist_interval_ms: usize,
    pub ownership_invitations_expiration_days: u64,
    pub metrics_authorization_token: Option<String>,
    pub use_test_database_pool: bool,
    pub instance_metrics_log_every_seconds: Option<u64>,
    pub force_unconditional_redirects: bool,
    pub blocked_routes: HashSet<String>,
    pub version_id_cache_size: u64,
    pub version_id_cache_ttl: Duration,
}

impl Default for Server {
    /// Returns a default value for the application's config
    ///
    /// Sets the following default values:
    ///
    /// - `Config::max_upload_size`: 10MiB
    /// - `Config::ownership_invitations_expiration_days`: 30
    ///
    /// Pulls values from the following environment variables:
    ///
    /// - `SESSION_KEY`: The key used to sign and encrypt session cookies.
    /// - `GH_CLIENT_ID`: The client ID of the associated GitHub application.
    /// - `GH_CLIENT_SECRET`: The client secret of the associated GitHub application.
    /// - `BLOCKED_TRAFFIC`: A list of headers and environment variables to use for blocking
    ///   traffic. See the `block_traffic` module for more documentation.
    /// - `DOWNLOADS_PERSIST_INTERVAL_MS`: how frequent to persist download counts (in ms).
    /// - `METRICS_AUTHORIZATION_TOKEN`: authorization token needed to query metrics. If missing,
    ///   querying metrics will be completely disabled.
    /// - `WEB_MAX_ALLOWED_PAGE_OFFSET`: Page offsets larger than this value are rejected. Defaults
    ///   to 200.
    /// - `WEB_PAGE_OFFSET_UA_BLOCKLIST`: A comma seperated list of user-agent substrings that will
    ///   be blocked if `WEB_MAX_ALLOWED_PAGE_OFFSET` is exceeded. Including an empty string in the
    ///   list will block *all* user-agents exceeding the offset. If not set or empty, no blocking
    ///   will occur.
    /// - `WEB_PAGE_OFFSET_CIDR_BLOCKLIST`: A comma separated list of CIDR blocks that will be used
    ///   to block IP addresses given in the `X-Real-Ip` HTTP header, e.g. `192.168.1.0/24`.
    ///   If not set or empty, no blocking will occur.
    /// - `INSTANCE_METRICS_LOG_EVERY_SECONDS`: How frequently should instance metrics be logged.
    ///   If the environment variable is not present instance metrics are not logged.
    /// - `FORCE_UNCONDITIONAL_REDIRECTS`: Whether to force unconditional redirects in the download
    ///   endpoint even with a healthy database pool.
    /// - `BLOCKED_ROUTES`: A comma separated list of HTTP route patterns that are manually blocked
    ///   by an operator (e.g. `/crates/:crate_id/:version/download`).
    ///
    /// # Panics
    ///
    /// This function panics if the Server configuration is invalid.
    fn default() -> Self {
        let allowed_origins = env("WEB_ALLOWED_ORIGINS")
            .split(',')
            .map(ToString::to_string)
            .collect();
        let page_offset_ua_blocklist = match env_optional::<String>("WEB_PAGE_OFFSET_UA_BLOCKLIST")
        {
            None => vec![],
            Some(s) if s.is_empty() => vec![],
            Some(s) => s.split(',').map(String::from).collect(),
        };
        let page_offset_cidr_blocklist =
            match env_optional::<String>("WEB_PAGE_OFFSET_CIDR_BLOCKLIST") {
                None => vec![],
                Some(s) if s.is_empty() => vec![],
                Some(s) => s
                    .split(',')
                    .map(parse_cidr_block)
                    .collect::<Result<_, _>>()
                    .unwrap(),
            };

        let base = Base::from_environment();
        let excluded_crate_names = match env_optional::<String>("EXCLUDED_CRATE_NAMES") {
            None => vec![],
            Some(s) if s.is_empty() => vec![],
            Some(s) => s.split(',').map(String::from).collect(),
        };
        Server {
            db: DatabasePools::full_from_environment(&base),
            base,
            session_key: env("SESSION_KEY"),
            gh_client_id: env("GH_CLIENT_ID"),
            gh_client_secret: env("GH_CLIENT_SECRET"),
            gh_base_url: "https://api.github.com".to_string(),
            max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
            max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
            publish_rate_limit: Default::default(),
            blocked_traffic: blocked_traffic(),
            max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200),
            page_offset_ua_blocklist,
            page_offset_cidr_blocklist,
            excluded_crate_names,
            domain_name: domain_name(),
            allowed_origins,
            downloads_persist_interval_ms: dotenv::var("DOWNLOADS_PERSIST_INTERVAL_MS")
                .map(|interval| {
                    interval
                        .parse()
                        .expect("invalid DOWNLOADS_PERSIST_INTERVAL_MS")
                })
                .unwrap_or(60_000), // 1 minute
            ownership_invitations_expiration_days: 30,
            metrics_authorization_token: dotenv::var("METRICS_AUTHORIZATION_TOKEN").ok(),
            use_test_database_pool: false,
            instance_metrics_log_every_seconds: env_optional("INSTANCE_METRICS_LOG_EVERY_SECONDS"),
            force_unconditional_redirects: dotenv::var("FORCE_UNCONDITIONAL_REDIRECTS").is_ok(),
            blocked_routes: env_optional("BLOCKED_ROUTES")
                .map(|routes: String| routes.split(',').map(|s| s.into()).collect())
                .unwrap_or_else(HashSet::new),
            version_id_cache_size: env_optional("VERSION_ID_CACHE_SIZE")
                .unwrap_or(DEFAULT_VERSION_ID_CACHE_SIZE),
            version_id_cache_ttl: Duration::from_secs(
                env_optional("VERSION_ID_CACHE_TTL").unwrap_or(DEFAULT_VERSION_ID_CACHE_TTL),
            ),
        }
    }
}

impl Server {
    pub fn env(&self) -> Env {
        self.base.env
    }

    pub fn uploader(&self) -> &Uploader {
        self.base.uploader()
    }
}

pub(crate) fn domain_name() -> String {
    dotenv::var("DOMAIN_NAME").unwrap_or_else(|_| "crates.io".into())
}

/// Parses a CIDR block string to a valid `IpNetwork` struct.
///
/// The purpose is to be able to block IP ranges that overload the API that uses pagination.
///
/// The minimum number of bits for a host prefix must be
///
/// * at least 16 for IPv4 based CIDRs.
/// * at least 64 for IPv6 based CIDRs
///
fn parse_cidr_block(block: &str) -> anyhow::Result<IpNetwork> {
    let cidr = block
        .parse()
        .context("WEB_PAGE_OFFSET_CIDR_BLOCKLIST must contain IPv4 or IPv6 CIDR blocks.")?;

    let host_prefix = match cidr {
        IpNetwork::V4(_) => 16,
        IpNetwork::V6(_) => 64,
    };

    if cidr.prefix() < host_prefix {
        return Err(anyhow!("WEB_PAGE_OFFSET_CIDR_BLOCKLIST only allows CIDR blocks with a host prefix of at least 16 bits (IPv4) or 64 bits (IPv6)."));
    }

    Ok(cidr)
}

fn blocked_traffic() -> Vec<(String, Vec<String>)> {
    let pattern_list = dotenv::var("BLOCKED_TRAFFIC").unwrap_or_default();
    parse_traffic_patterns(&pattern_list)
        .map(|(header, value_env_var)| {
            let value_list = dotenv::var(value_env_var).unwrap_or_default();
            let values = value_list.split(',').map(String::from).collect();
            (header.into(), values)
        })
        .collect()
}

fn parse_traffic_patterns(patterns: &str) -> impl Iterator<Item = (&str, &str)> {
    patterns.split_terminator(',').map(|pattern| {
        if let Some(idx) = pattern.find('=') {
            (&pattern[..idx], &pattern[(idx + 1)..])
        } else {
            panic!(
                "BLOCKED_TRAFFIC must be in the form HEADER=VALUE_ENV_VAR, \
                 got invalid pattern {}",
                pattern
            )
        }
    })
}

#[test]
fn parse_traffic_patterns_splits_on_comma_and_looks_for_equal_sign() {
    let pattern_string_1 = "Foo=BAR,Bar=BAZ";
    let pattern_string_2 = "Baz=QUX";
    let pattern_string_3 = "";

    let patterns_1 = parse_traffic_patterns(pattern_string_1).collect::<Vec<_>>();
    assert_eq!(vec![("Foo", "BAR"), ("Bar", "BAZ")], patterns_1);

    let patterns_2 = parse_traffic_patterns(pattern_string_2).collect::<Vec<_>>();
    assert_eq!(vec![("Baz", "QUX")], patterns_2);

    assert_none!(parse_traffic_patterns(pattern_string_3).next());
}

#[test]
fn parse_cidr_block_list_successfully() {
    assert_ok_eq!(
        parse_cidr_block("127.0.0.1/24"),
        "127.0.0.1/24".parse::<IpNetwork>().unwrap()
    );
    assert_ok_eq!(
        parse_cidr_block("192.168.0.1/31"),
        "192.168.0.1/31".parse::<IpNetwork>().unwrap()
    );
}

#[test]
fn parse_cidr_blocks_panics_when_host_ipv4_prefix_is_too_low() {
    assert_err!(parse_cidr_block("127.0.0.1/8"));
}

#[test]
fn parse_cidr_blocks_panics_when_host_ipv6_prefix_is_too_low() {
    assert_err!(parse_cidr_block(
        "2001:0db8:0123:4567:89ab:cdef:1234:5678/56"
    ));
}

#[test]
fn parse_ipv6_based_cidr_blocks() {
    assert_ok_eq!(
        parse_cidr_block("2002::1234:abcd:ffff:c0a8:101/64"),
        "2002::1234:abcd:ffff:c0a8:101/64"
            .parse::<IpNetwork>()
            .unwrap()
    );
    assert_ok_eq!(
        parse_cidr_block("2001:0db8:0123:4567:89ab:cdef:1234:5678/92"),
        "2001:0db8:0123:4567:89ab:cdef:1234:5678/92"
            .parse::<IpNetwork>()
            .unwrap()
    );
}
<|endoftext|>


<commit_before>
use std::default::Default;
use std::path::{Path, PathBuf};
use std::fs;
use std::fs::File;
use std::io;
use std::io::ErrorKind;
use std::env;
use std::io::Read;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::error::Error;
use toml;

#[derive(Debug)]
pub enum ConfigError {
    IoError(io::Error),
    TomlParserErrors(Vec<toml::ParserError>),
    InvalidFieldName(String),
    InvalidFieldType(& 'static str, String),
}

impl Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::IoError(ref e) => e.description(),
            ConfigError::TomlParserErrors(ref v) => v[0].description(),
            ConfigError::InvalidFieldName(_) => "Invalid field name",
            ConfigError::InvalidFieldType(_, _) => "Invalid field type",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            ConfigError::IoError(ref e) => Some(e),
            ConfigError::TomlParserErrors(ref v) => Some(&v[0]),
            _ => None
        }
    }
}

impl Display for ConfigError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            ConfigError::IoError(ref e) => write!(f, "IO Error: {}", e),
            ConfigError::TomlParserErrors(ref v) => write!(f, "Parser Error ({} total): {}",
                    v.len(), v[0]),
            ConfigError::InvalidFieldName(ref s) => write!(f, "Invalid feild name: {}", s),
            ConfigError::InvalidFieldType(expected, ref got) => write!(f, "Expected type {} got {}", expected, got),
        }
    }
}

impl From<io::Error> for ConfigError {
    fn from(e: io::Error) -> ConfigError {
        ConfigError::IoError(e)
    }
}

#[derive(RustcDecodable, Debug)]
pub struct Config {
    pub show_ascii: bool,
    pub show_linenum: bool,
    pub line_width: Option<u32>
}

impl Default for Config {
    fn default() -> Config {
        Config {
            show_ascii: true,
            show_linenum: true,
            line_width: None,
        }
    }
}

macro_rules! try_unwrap_toml {
    ($e:expr, $t:ident) => ({
        match $e {
            toml::Value::$t(v) => v,
            other => {
                return Err(ConfigError::InvalidFieldType(stringify!($t), format!("{}", other)))
            }
        }
    })
}

macro_rules! decode_toml {
    ($obj:expr, $name:ident, $table:expr, $toml_type:ident, $map_func:expr) => ({
        $obj.$name = match $table.remove(stringify!($name)) {
            Some(val) => $map_func(try_unwrap_toml!(val, $toml_type)),
            None => $obj.$name
        };
    });
    ($obj:expr, $name:ident, $table:expr, $toml_type:ident) => ({
        $obj.$name = match $table.remove(stringify!($name)) {
            Some(val) => try_unwrap_toml!(val, $toml_type),
            None => $obj.$name
        };
    });
}

impl Config {
    pub fn get_config_usage() -> &'static str {
        "The supported configuration options with their default values:
    show_ascii=true        Shows an ascii representation in the right hand side
    show_linenum=true      Shows the byte offset in the left hand side
    line_width=0           The number of bytes per line, 0 meaning auto-wrap

The configuration can be set in the rex.conf file located in the
$XDG_CONFIG_HOME path (usually, ~/.config/rex/rex.conf). Additionally,
properties can be set on the commandline as rex -C show_ascii=false.
"
    }

    fn apply_toml(&mut self, mut t: toml::Table) -> Result<(), ConfigError> {
        decode_toml!(self, show_ascii, t, Boolean);
        decode_toml!(self, show_linenum, t, Boolean);
        decode_toml!(self, line_width, t, Integer, |i| if i > 0 { Some(i as u32) } else { None });
        if let Some((key, _)) = t.into_iter().next() {
            Err(ConfigError::InvalidFieldName(key))
        } else {
            Ok(())
        }
    }

    pub fn set_from_string(&mut self, set_line: &str) -> Result<(), ConfigError> {
        let mut parser = toml::Parser::new(&set_line);
        if let Some(table) = parser.parse() {
            return self.apply_toml(table);
        }
        Err(ConfigError::TomlParserErrors(parser.errors))
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, ConfigError> {
        let mut s = String::new();
        let mut f = try!(File::open(path));
        try!(f.read_to_string(&mut s));
        let mut config: Config = Default::default();
        try!(config.set_from_string(&s));
        Ok(config)
    }

    fn get_config_path() -> PathBuf {
        let mut p = PathBuf::new();
        p.push(env::var("XDG_CONFIG_HOME").unwrap_or_else(
                |_| env::var("HOME").unwrap_or("/".into()) + "/.config"
            ));
        p.join("rex").join("rex.conf")
    }

    pub fn open_default() -> Result<Config, ConfigError> {
        let config_path = Config::get_config_path();
        match fs::metadata(config_path) {
            Ok(_) => Config::from_file(Config::get_config_path()),
            Err(ref err) if err.kind() == ErrorKind::NotFound => Ok(Default::default()),
            Err(err) => Err(err.into()),
        }
    }
}
<commit_msg>Small whitespace fix<commit_after>
use std::default::Default;
use std::path::{Path, PathBuf};
use std::fs;
use std::fs::File;
use std::io;
use std::io::ErrorKind;
use std::env;
use std::io::Read;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::error::Error;
use toml;

#[derive(Debug)]
pub enum ConfigError {
    IoError(io::Error),
    TomlParserErrors(Vec<toml::ParserError>),
    InvalidFieldName(String),
    InvalidFieldType(& 'static str, String),
}

impl Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::IoError(ref e) => e.description(),
            ConfigError::TomlParserErrors(ref v) => v[0].description(),
            ConfigError::InvalidFieldName(_) => "Invalid field name",
            ConfigError::InvalidFieldType(_, _) => "Invalid field type",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            ConfigError::IoError(ref e) => Some(e),
            ConfigError::TomlParserErrors(ref v) => Some(&v[0]),
            _ => None
        }
    }
}

impl Display for ConfigError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            ConfigError::IoError(ref e) => write!(f, "IO Error: {}", e),
            ConfigError::TomlParserErrors(ref v) => write!(f, "Parser Error ({} total): {}",
                    v.len(), v[0]),
            ConfigError::InvalidFieldName(ref s) => write!(f, "Invalid feild name: {}", s),
            ConfigError::InvalidFieldType(expected, ref got) => write!(f, "Expected type {} got {}", expected, got),
        }
    }
}

impl From<io::Error> for ConfigError {
    fn from(e: io::Error) -> ConfigError {
        ConfigError::IoError(e)
    }
}

#[derive(RustcDecodable, Debug)]
pub struct Config {
    pub show_ascii: bool,
    pub show_linenum: bool,
    pub line_width: Option<u32>
}

impl Default for Config {
    fn default() -> Config {
        Config {
            show_ascii: true,
            show_linenum: true,
            line_width: None,
        }
    }
}

macro_rules! try_unwrap_toml {
    ($e:expr, $t:ident) => ({
        match $e {
            toml::Value::$t(v) => v,
            other => {
                return Err(ConfigError::InvalidFieldType(stringify!($t), format!("{}", other)))
            }
        }
    })
}

macro_rules! decode_toml {
    ($obj:expr, $name:ident, $table:expr, $toml_type:ident, $map_func:expr) => ({
        $obj.$name = match $table.remove(stringify!($name)) {
            Some(val) => $map_func(try_unwrap_toml!(val, $toml_type)),
            None => $obj.$name
        };
    });
    ($obj:expr, $name:ident, $table:expr, $toml_type:ident) => ({
        $obj.$name = match $table.remove(stringify!($name)) {
            Some(val) => try_unwrap_toml!(val, $toml_type),
            None => $obj.$name
        };
    });
}

impl Config {
    pub fn get_config_usage() -> &'static str {
        "
The supported configuration options with their default values:
    show_ascii=true        Shows an ascii representation in the right hand side
    show_linenum=true      Shows the byte offset in the left hand side
    line_width=0           The number of bytes per line, 0 meaning auto-wrap

The configuration can be set in the rex.conf file located in the
$XDG_CONFIG_HOME path (usually, ~/.config/rex/rex.conf). Additionally,
properties can be set on the commandline as rex -C show_ascii=false.
"
    }

    fn apply_toml(&mut self, mut t: toml::Table) -> Result<(), ConfigError> {
        decode_toml!(self, show_ascii, t, Boolean);
        decode_toml!(self, show_linenum, t, Boolean);
        decode_toml!(self, line_width, t, Integer, |i| if i > 0 { Some(i as u32) } else { None });
        if let Some((key, _)) = t.into_iter().next() {
            Err(ConfigError::InvalidFieldName(key))
        } else {
            Ok(())
        }
    }

    pub fn set_from_string(&mut self, set_line: &str) -> Result<(), ConfigError> {
        let mut parser = toml::Parser::new(&set_line);
        if let Some(table) = parser.parse() {
            return self.apply_toml(table);
        }
        Err(ConfigError::TomlParserErrors(parser.errors))
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, ConfigError> {
        let mut s = String::new();
        let mut f = try!(File::open(path));
        try!(f.read_to_string(&mut s));
        let mut config: Config = Default::default();
        try!(config.set_from_string(&s));
        Ok(config)
    }

    fn get_config_path() -> PathBuf {
        let mut p = PathBuf::new();
        p.push(env::var("XDG_CONFIG_HOME").unwrap_or_else(
                |_| env::var("HOME").unwrap_or("/".into()) + "/.config"
            ));
        p.join("rex").join("rex.conf")
    }

    pub fn open_default() -> Result<Config, ConfigError> {
        let config_path = Config::get_config_path();
        match fs::metadata(config_path) {
            Ok(_) => Config::from_file(Config::get_config_path()),
            Err(ref err) if err.kind() == ErrorKind::NotFound => Ok(Default::default()),
            Err(err) => Err(err.into()),
        }
    }
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::path::Path;
use toml;

pub struct Config {
    pub decrement_ttl: bool,
    pub upstream_servers: Vec<String>,
    pub failover: bool,
    pub upstream_max_failures: u32,
    pub cache_size: usize,
    pub udp_ports: u16,
    pub listen_addr: String,
    pub webservice_enabled: bool,
    pub webservice_listen_addr: String,
}

impl Config {
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
        let mut fd = try!(File::open(path));
        let mut toml = String::new();
        try!(fd.read_to_string(&mut toml));
        Self::from_string(&toml)
    }

    pub fn from_string(toml: &str) -> Result<Config, Error> {
        let toml_config = match toml.parse() {
            Ok(toml_config) => toml_config,
            Err(_) => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Syntax error - config file is not valid TOML"))
            }
        };
        Self::parse(toml_config)
    }

    fn parse(toml_config: toml::Value) -> Result<Config, Error> {
        let decrement_ttl_str = toml_config.lookup("upstream.type").map_or("authoritative", |x| {
            x.as_str().expect("upstream.type must be a string")
        });
        let decrement_ttl = match decrement_ttl_str {
            "authoritative" => false,
            "resolver" => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Invalid value for the type of upstream servers. Must be \
                                       'authoritative or 'resolver'"))
            }
        };

        let upstream_servers = toml_config.lookup("upstream.servers")
            .expect("upstream.servers is required")
            .as_slice()
            .expect("Invalid list of upstream servers")
            .iter()
            .map(|x| x.as_str().expect("upstream servers must be strings").to_owned())
            .collect();

        let failover_str = toml_config.lookup("upstream.strategy").map_or("uniform", |x| {
            x.as_str().expect("upstream.strategy must be a string")
        });
        let failover = match failover_str {
            "uniform" => false,
            "fallback" => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Invalid value for the load balancing/failover strategy"))
            }
        };

        let upstream_max_failures =
            toml_config.lookup("upstream.max_failures").map_or(3, |x| {
                x.as_integer().expect("upstream.max_failures must be an integer")
            }) as u32;

        let cache_size = toml_config.lookup("cache.max_items").map_or(250_000, |x| {
            x.as_integer().expect("cache.max_items must be an integer")
        }) as usize;

        let udp_ports = toml_config.lookup("network.udp_ports").map_or(8, |x| {
            x.as_integer().expect("network.udp_ports must be an integer")
        }) as u16;

        let listen_addr = toml_config.lookup("network.listen")
            .map_or("0.0.0.0:53",
                    |x| x.as_str().expect("network.listen_addr must be a string"))
            .to_owned();

        let webservice_enabled = toml_config.lookup("webservice.enabled").map_or(false, |x| {
            x.as_bool().expect("webservice.enabled must be a boolean")
        });

        let webservice_listen_addr = toml_config.lookup("webservice.listen")
            .map_or("127.0.0.1:8888",
                    |x| x.as_str().expect("webservice.listen_addr must be a string"))
            .to_owned();

        Ok(Config {
            decrement_ttl: decrement_ttl,
            upstream_servers: upstream_servers,
            failover: failover,
            upstream_max_failures: upstream_max_failures,
            cache_size: cache_size,
            udp_ports: udp_ports,
            listen_addr: listen_addr,
            webservice_enabled: webservice_enabled,
            webservice_listen_addr: webservice_listen_addr,
        })
    }
}
<commit_msg>Config values can totally be cloned<commit_after>use std::io::prelude::*;
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::path::Path;
use toml;

#[derive(Clone, Debug)]
pub struct Config {
    pub decrement_ttl: bool,
    pub upstream_servers: Vec<String>,
    pub failover: bool,
    pub upstream_max_failures: u32,
    pub cache_size: usize,
    pub udp_ports: u16,
    pub listen_addr: String,
    pub webservice_enabled: bool,
    pub webservice_listen_addr: String,
}

impl Config {
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
        let mut fd = try!(File::open(path));
        let mut toml = String::new();
        try!(fd.read_to_string(&mut toml));
        Self::from_string(&toml)
    }

    pub fn from_string(toml: &str) -> Result<Config, Error> {
        let toml_config = match toml.parse() {
            Ok(toml_config) => toml_config,
            Err(_) => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Syntax error - config file is not valid TOML"))
            }
        };
        Self::parse(toml_config)
    }

    fn parse(toml_config: toml::Value) -> Result<Config, Error> {
        let decrement_ttl_str = toml_config.lookup("upstream.type").map_or("authoritative", |x| {
            x.as_str().expect("upstream.type must be a string")
        });
        let decrement_ttl = match decrement_ttl_str {
            "authoritative" => false,
            "resolver" => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Invalid value for the type of upstream servers. Must be \
                                       'authoritative or 'resolver'"))
            }
        };

        let upstream_servers = toml_config.lookup("upstream.servers")
            .expect("upstream.servers is required")
            .as_slice()
            .expect("Invalid list of upstream servers")
            .iter()
            .map(|x| x.as_str().expect("upstream servers must be strings").to_owned())
            .collect();

        let failover_str = toml_config.lookup("upstream.strategy").map_or("uniform", |x| {
            x.as_str().expect("upstream.strategy must be a string")
        });
        let failover = match failover_str {
            "uniform" => false,
            "fallback" => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Invalid value for the load balancing/failover strategy"))
            }
        };

        let upstream_max_failures =
            toml_config.lookup("upstream.max_failures").map_or(3, |x| {
                x.as_integer().expect("upstream.max_failures must be an integer")
            }) as u32;

        let cache_size = toml_config.lookup("cache.max_items").map_or(250_000, |x| {
            x.as_integer().expect("cache.max_items must be an integer")
        }) as usize;

        let udp_ports = toml_config.lookup("network.udp_ports").map_or(8, |x| {
            x.as_integer().expect("network.udp_ports must be an integer")
        }) as u16;

        let listen_addr = toml_config.lookup("network.listen")
            .map_or("0.0.0.0:53",
                    |x| x.as_str().expect("network.listen_addr must be a string"))
            .to_owned();

        let webservice_enabled = toml_config.lookup("webservice.enabled").map_or(false, |x| {
            x.as_bool().expect("webservice.enabled must be a boolean")
        });

        let webservice_listen_addr = toml_config.lookup("webservice.listen")
            .map_or("127.0.0.1:8888",
                    |x| x.as_str().expect("webservice.listen_addr must be a string"))
            .to_owned();

        Ok(Config {
            decrement_ttl: decrement_ttl,
            upstream_servers: upstream_servers,
            failover: failover,
            upstream_max_failures: upstream_max_failures,
            cache_size: cache_size,
            udp_ports: udp_ports,
            listen_addr: listen_addr,
            webservice_enabled: webservice_enabled,
            webservice_listen_addr: webservice_listen_addr,
        })
    }
}
<|endoftext|>


<commit_before>/*
Copyright ⓒ 2015 cargo-script contributors.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
/*!
This module just contains any big string literals I don't want cluttering up the rest of the code.
*/

/**
The message output when the user invokes `cargo script` with no further arguments.  We need to do this ourselves because `clap` doesn't provide any way to generate this message manually.
*/
pub const NO_ARGS_MESSAGE: &'static str = "\
The following required arguments were not supplied:
\t'<script>'

USAGE:
\tcargo script [FLAGS OPTIONS] [--] <script> <args>...

For more information try --help";

/*
What follows are the templates used to wrap script input.  The input provided by the user is inserted in place of `%b`.  *Proper* templates of some kind would be nice, but damnit, I'm lazy.
*/

/// The template used for script file inputs.
pub const FILE_TEMPLATE: &'static str = r#"%b"#;

/// The template used for `--expr` input.
pub const EXPR_TEMPLATE: &'static str = r#"
%p
fn main() {
    match ({%b}) {
        __cargo_script_expr => println!("{:?}", __cargo_script_expr)
    }
}
"#;

/*
Regarding the loop templates: what I *want* is for the result of the closure to be printed to standard output *only* if it's not `()`.

* TODO: Just use TypeId, dumbass.
* TODO: Merge the `LOOP_*` templates so there isn't duplicated code.  It's icky.
*/

/// The template used for `--loop` input, assuming no `--count` flag is also given.
pub const LOOP_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        let output = closure(&line_buffer);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str) -> T, T: 'static {
    closure
}
"#;

/// The template used for `--count --loop` input.
pub const LOOP_COUNT_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    let mut count = 0;
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        count += 1;
        let output = closure(&line_buffer, count);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str, usize) -> T, T: 'static {
    closure
}
"#;

/**
The default manifest used for packages.  `%n` is replaced with the "safe name" of the input, which *should* be safe to use as a file name.
*/
pub const DEFAULT_MANIFEST: &'static str = r#"
[package]
name = "%n"
version = "0.1.0"
authors = ["Anonymous"]

[[bin]]
name = "%n"
path = "%n.rs"
"#;

/**
The name of the package metadata file.
*/
pub const METADATA_FILE: &'static str = "metadata.json";

/**
Extensions to check when trying to find script input by name.
*/
pub const SEARCH_EXTS: &'static [&'static str] = &["crs", "rs"];

/**
When generating a package's unique ID, how many hex nibbles of the digest should be used *at most*?

The largest meaningful value is `40`.
*/
pub const ID_DIGEST_LEN_MAX: usize = 16;

/**
How old can stuff in the cache be before we automatically clear it out?

Measured in milliseconds.
*/
// It's been *one week* since you looked at me,
// cocked your head to the side and said "I'm angry."
pub const MAX_CACHE_AGE_MS: u64 = 1*7*24*60*60*1000;
<commit_msg>Removed redundant parens in expression template.<commit_after>/*
Copyright ⓒ 2015 cargo-script contributors.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
/*!
This module just contains any big string literals I don't want cluttering up the rest of the code.
*/

/**
The message output when the user invokes `cargo script` with no further arguments.  We need to do this ourselves because `clap` doesn't provide any way to generate this message manually.
*/
pub const NO_ARGS_MESSAGE: &'static str = "\
The following required arguments were not supplied:
\t'<script>'

USAGE:
\tcargo script [FLAGS OPTIONS] [--] <script> <args>...

For more information try --help";

/*
What follows are the templates used to wrap script input.  The input provided by the user is inserted in place of `%b`.  *Proper* templates of some kind would be nice, but damnit, I'm lazy.
*/

/// The template used for script file inputs.
pub const FILE_TEMPLATE: &'static str = r#"%b"#;

/// The template used for `--expr` input.
pub const EXPR_TEMPLATE: &'static str = r#"
%p
fn main() {
    match {%b} {
        __cargo_script_expr => println!("{:?}", __cargo_script_expr)
    }
}
"#;

/*
Regarding the loop templates: what I *want* is for the result of the closure to be printed to standard output *only* if it's not `()`.

* TODO: Just use TypeId, dumbass.
* TODO: Merge the `LOOP_*` templates so there isn't duplicated code.  It's icky.
*/

/// The template used for `--loop` input, assuming no `--count` flag is also given.
pub const LOOP_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        let output = closure(&line_buffer);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str) -> T, T: 'static {
    closure
}
"#;

/// The template used for `--count --loop` input.
pub const LOOP_COUNT_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    let mut count = 0;
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        count += 1;
        let output = closure(&line_buffer, count);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str, usize) -> T, T: 'static {
    closure
}
"#;

/**
The default manifest used for packages.  `%n` is replaced with the "safe name" of the input, which *should* be safe to use as a file name.
*/
pub const DEFAULT_MANIFEST: &'static str = r#"
[package]
name = "%n"
version = "0.1.0"
authors = ["Anonymous"]

[[bin]]
name = "%n"
path = "%n.rs"
"#;

/**
The name of the package metadata file.
*/
pub const METADATA_FILE: &'static str = "metadata.json";

/**
Extensions to check when trying to find script input by name.
*/
pub const SEARCH_EXTS: &'static [&'static str] = &["crs", "rs"];

/**
When generating a package's unique ID, how many hex nibbles of the digest should be used *at most*?

The largest meaningful value is `40`.
*/
pub const ID_DIGEST_LEN_MAX: usize = 16;

/**
How old can stuff in the cache be before we automatically clear it out?

Measured in milliseconds.
*/
// It's been *one week* since you looked at me,
// cocked your head to the side and said "I'm angry."
pub const MAX_CACHE_AGE_MS: u64 = 1*7*24*60*60*1000;
<|endoftext|>


<commit_before>use rustc::lint::*;
use rustc::middle::ty;
use rustc_front::hir::*;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use syntax::parse::token::InternedString;
use syntax::util::small_vector::SmallVector;
use utils::{SpanlessEq, SpanlessHash};
use utils::{get_parent_expr, in_macro, span_note_and_lint};

/// **What it does:** This lint checks for consecutive `ifs` with the same condition. This lint is
/// `Warn` by default.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:** `if a == b { .. } else if a == b { .. }`
declare_lint! {
    pub IFS_SAME_COND,
    Warn,
    "consecutive `ifs` with the same condition"
}

/// **What it does:** This lint checks for `if/else` with the same body as the *then* part and the
/// *else* part. This lint is `Warn` by default.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:** `if .. { 42 } else { 42 }`
declare_lint! {
    pub IF_SAME_THEN_ELSE,
    Warn,
    "if with the same *then* and *else* blocks"
}

/// **What it does:** This lint checks for `match` with identical arm bodies.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
/// ```rust,ignore
/// match foo {
///     Bar => bar(),
///     Quz => quz(),
///     Baz => bar(), // <= oups
/// ```
declare_lint! {
    pub MATCH_SAME_ARMS,
    Warn,
    "`match` with identical arm bodies"
}

#[derive(Copy, Clone, Debug)]
pub struct CopyAndPaste;

impl LintPass for CopyAndPaste {
    fn get_lints(&self) -> LintArray {
        lint_array![
            IFS_SAME_COND,
            IF_SAME_THEN_ELSE,
            MATCH_SAME_ARMS
        ]
    }
}

impl LateLintPass for CopyAndPaste {
    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
        if !in_macro(cx, expr.span) {
            // skip ifs directly in else, it will be checked in the parent if
            if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
                if else_expr.id == expr.id {
                    return;
                }
            }

            let (conds, blocks) = if_sequence(expr);
            lint_same_then_else(cx, blocks.as_slice());
            lint_same_cond(cx, conds.as_slice());
            lint_match_arms(cx, expr);
        }
    }
}

/// Implementation of `IF_SAME_THEN_ELSE`.
fn lint_same_then_else(cx: &LateContext, blocks: &[&Block]) {
    let hash : &Fn(&&Block) -> u64 = &|block| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_block(block);
        h.finish()
    };

    let eq : &Fn(&&Block, &&Block) -> bool = &|&lhs, &rhs| -> bool {
        SpanlessEq::new(cx).eq_block(lhs, rhs)
    };

    if let Some((i, j)) = search_same(blocks, hash, eq) {
        span_note_and_lint(cx, IF_SAME_THEN_ELSE, j.span, "this `if` has identical blocks", i.span, "same as this");
    }
}

/// Implementation of `IFS_SAME_COND`.
fn lint_same_cond(cx: &LateContext, conds: &[&Expr]) {
    let hash : &Fn(&&Expr) -> u64 = &|expr| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_expr(expr);
        h.finish()
    };

    let eq : &Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
        SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs)
    };

    if let Some((i, j)) = search_same(conds, hash, eq) {
        span_note_and_lint(cx, IFS_SAME_COND, j.span, "this `if` has the same condition as a previous if", i.span, "same as this");
    }
}

/// Implementation if `MATCH_SAME_ARMS`.
fn lint_match_arms(cx: &LateContext, expr: &Expr) {
    let hash = |arm: &Arm| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_expr(&arm.body);
        h.finish()
    };

    let eq = |lhs: &Arm, rhs: &Arm| -> bool {
        SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
            // all patterns should have the same bindings
            bindings(cx, &lhs.pats[0]) == bindings(cx, &rhs.pats[0])
    };

    if let ExprMatch(_, ref arms, MatchSource::Normal) = expr.node {
        if let Some((i, j)) = search_same(&**arms, hash, eq) {
            span_note_and_lint(cx, MATCH_SAME_ARMS, j.body.span, "this `match` has identical arm bodies", i.body.span, "same as this");
        }
    }
}

/// Return the list of condition expressions and the list of blocks in a sequence of `if/else`.
/// Eg. would return `([a, b], [c, d, e])` for the expression
/// `if a { c } else if b { d } else { e }`.
fn if_sequence(mut expr: &Expr) -> (SmallVector<&Expr>, SmallVector<&Block>) {
    let mut conds = SmallVector::zero();
    let mut blocks = SmallVector::zero();

    while let ExprIf(ref cond, ref then_block, ref else_expr) = expr.node {
        conds.push(&**cond);
        blocks.push(&**then_block);

        if let Some(ref else_expr) = *else_expr {
            expr = else_expr;
        }
        else {
            break;
        }
    }

    // final `else {..}`
    if !blocks.is_empty() {
        if let ExprBlock(ref block) = expr.node {
            blocks.push(&**block);
        }
    }

    (conds, blocks)
}

/// Return the list of bindings in a pattern.
fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> HashMap<InternedString, ty::Ty<'tcx>> {
    fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut HashMap<InternedString, ty::Ty<'tcx>>) {
        match pat.node {
            PatBox(ref pat) | PatRegion(ref pat, _) => bindings_impl(cx, pat, map),
            PatEnum(_, Some(ref pats)) => {
                for pat in pats {
                    bindings_impl(cx, pat, map);
                }
            }
            PatIdent(_, ref ident, ref as_pat) => {
                if let Entry::Vacant(v) = map.entry(ident.node.name.as_str()) {
                    v.insert(cx.tcx.pat_ty(pat));
                }
                if let Some(ref as_pat) = *as_pat {
                    bindings_impl(cx, as_pat, map);
                }
            },
            PatStruct(_, ref fields, _) => {
                for pat in fields {
                    bindings_impl(cx, &pat.node.pat, map);
                }
            }
            PatTup(ref fields) => {
                for pat in fields {
                    bindings_impl(cx, pat, map);
                }
            }
            PatVec(ref lhs, ref mid, ref rhs) => {
                for pat in lhs {
                    bindings_impl(cx, pat, map);
                }
                if let Some(ref mid) = *mid {
                    bindings_impl(cx, mid, map);
                }
                for pat in rhs {
                    bindings_impl(cx, pat, map);
                }
            }
            PatEnum(..) | PatLit(..) | PatQPath(..) | PatRange(..) | PatWild => (),
        }
    }

    let mut result = HashMap::new();
    bindings_impl(cx, pat, &mut result);
    result
}

fn search_same<T, Hash, Eq>(exprs: &[T],
                            hash: Hash,
                            eq: Eq) -> Option<(&T, &T)>
where Hash: Fn(&T) -> u64,
      Eq: Fn(&T, &T) -> bool {
    // common cases
    if exprs.len() < 2 {
        return None;
    }
    else if exprs.len() == 2 {
        return if eq(&exprs[0], &exprs[1]) {
            Some((&exprs[0], &exprs[1]))
        }
        else {
            None
        }
    }

    let mut map : HashMap<_, Vec<&_>> = HashMap::with_capacity(exprs.len());

    for expr in exprs {
        match map.entry(hash(expr)) {
            Entry::Occupied(o) => {
                for o in o.get() {
                    if eq(&o, expr) {
                        return Some((&o, expr))
                    }
                }
            }
            Entry::Vacant(v) => { v.insert(vec![expr]); }
        }
    }

    None
}
<commit_msg>Fix comment<commit_after>use rustc::lint::*;
use rustc::middle::ty;
use rustc_front::hir::*;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use syntax::parse::token::InternedString;
use syntax::util::small_vector::SmallVector;
use utils::{SpanlessEq, SpanlessHash};
use utils::{get_parent_expr, in_macro, span_note_and_lint};

/// **What it does:** This lint checks for consecutive `ifs` with the same condition. This lint is
/// `Warn` by default.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:** `if a == b { .. } else if a == b { .. }`
declare_lint! {
    pub IFS_SAME_COND,
    Warn,
    "consecutive `ifs` with the same condition"
}

/// **What it does:** This lint checks for `if/else` with the same body as the *then* part and the
/// *else* part. This lint is `Warn` by default.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:** `if .. { 42 } else { 42 }`
declare_lint! {
    pub IF_SAME_THEN_ELSE,
    Warn,
    "if with the same *then* and *else* blocks"
}

/// **What it does:** This lint checks for `match` with identical arm bodies.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
/// ```rust,ignore
/// match foo {
///     Bar => bar(),
///     Quz => quz(),
///     Baz => bar(), // <= oups
/// }
/// ```
declare_lint! {
    pub MATCH_SAME_ARMS,
    Warn,
    "`match` with identical arm bodies"
}

#[derive(Copy, Clone, Debug)]
pub struct CopyAndPaste;

impl LintPass for CopyAndPaste {
    fn get_lints(&self) -> LintArray {
        lint_array![
            IFS_SAME_COND,
            IF_SAME_THEN_ELSE,
            MATCH_SAME_ARMS
        ]
    }
}

impl LateLintPass for CopyAndPaste {
    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
        if !in_macro(cx, expr.span) {
            // skip ifs directly in else, it will be checked in the parent if
            if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
                if else_expr.id == expr.id {
                    return;
                }
            }

            let (conds, blocks) = if_sequence(expr);
            lint_same_then_else(cx, blocks.as_slice());
            lint_same_cond(cx, conds.as_slice());
            lint_match_arms(cx, expr);
        }
    }
}

/// Implementation of `IF_SAME_THEN_ELSE`.
fn lint_same_then_else(cx: &LateContext, blocks: &[&Block]) {
    let hash : &Fn(&&Block) -> u64 = &|block| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_block(block);
        h.finish()
    };

    let eq : &Fn(&&Block, &&Block) -> bool = &|&lhs, &rhs| -> bool {
        SpanlessEq::new(cx).eq_block(lhs, rhs)
    };

    if let Some((i, j)) = search_same(blocks, hash, eq) {
        span_note_and_lint(cx, IF_SAME_THEN_ELSE, j.span, "this `if` has identical blocks", i.span, "same as this");
    }
}

/// Implementation of `IFS_SAME_COND`.
fn lint_same_cond(cx: &LateContext, conds: &[&Expr]) {
    let hash : &Fn(&&Expr) -> u64 = &|expr| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_expr(expr);
        h.finish()
    };

    let eq : &Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
        SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs)
    };

    if let Some((i, j)) = search_same(conds, hash, eq) {
        span_note_and_lint(cx, IFS_SAME_COND, j.span, "this `if` has the same condition as a previous if", i.span, "same as this");
    }
}

/// Implementation if `MATCH_SAME_ARMS`.
fn lint_match_arms(cx: &LateContext, expr: &Expr) {
    let hash = |arm: &Arm| -> u64 {
        let mut h = SpanlessHash::new(cx);
        h.hash_expr(&arm.body);
        h.finish()
    };

    let eq = |lhs: &Arm, rhs: &Arm| -> bool {
        SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
            // all patterns should have the same bindings
            bindings(cx, &lhs.pats[0]) == bindings(cx, &rhs.pats[0])
    };

    if let ExprMatch(_, ref arms, MatchSource::Normal) = expr.node {
        if let Some((i, j)) = search_same(&**arms, hash, eq) {
            span_note_and_lint(cx, MATCH_SAME_ARMS, j.body.span, "this `match` has identical arm bodies", i.body.span, "same as this");
        }
    }
}

/// Return the list of condition expressions and the list of blocks in a sequence of `if/else`.
/// Eg. would return `([a, b], [c, d, e])` for the expression
/// `if a { c } else if b { d } else { e }`.
fn if_sequence(mut expr: &Expr) -> (SmallVector<&Expr>, SmallVector<&Block>) {
    let mut conds = SmallVector::zero();
    let mut blocks = SmallVector::zero();

    while let ExprIf(ref cond, ref then_block, ref else_expr) = expr.node {
        conds.push(&**cond);
        blocks.push(&**then_block);

        if let Some(ref else_expr) = *else_expr {
            expr = else_expr;
        }
        else {
            break;
        }
    }

    // final `else {..}`
    if !blocks.is_empty() {
        if let ExprBlock(ref block) = expr.node {
            blocks.push(&**block);
        }
    }

    (conds, blocks)
}

/// Return the list of bindings in a pattern.
fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> HashMap<InternedString, ty::Ty<'tcx>> {
    fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut HashMap<InternedString, ty::Ty<'tcx>>) {
        match pat.node {
            PatBox(ref pat) | PatRegion(ref pat, _) => bindings_impl(cx, pat, map),
            PatEnum(_, Some(ref pats)) => {
                for pat in pats {
                    bindings_impl(cx, pat, map);
                }
            }
            PatIdent(_, ref ident, ref as_pat) => {
                if let Entry::Vacant(v) = map.entry(ident.node.name.as_str()) {
                    v.insert(cx.tcx.pat_ty(pat));
                }
                if let Some(ref as_pat) = *as_pat {
                    bindings_impl(cx, as_pat, map);
                }
            },
            PatStruct(_, ref fields, _) => {
                for pat in fields {
                    bindings_impl(cx, &pat.node.pat, map);
                }
            }
            PatTup(ref fields) => {
                for pat in fields {
                    bindings_impl(cx, pat, map);
                }
            }
            PatVec(ref lhs, ref mid, ref rhs) => {
                for pat in lhs {
                    bindings_impl(cx, pat, map);
                }
                if let Some(ref mid) = *mid {
                    bindings_impl(cx, mid, map);
                }
                for pat in rhs {
                    bindings_impl(cx, pat, map);
                }
            }
            PatEnum(..) | PatLit(..) | PatQPath(..) | PatRange(..) | PatWild => (),
        }
    }

    let mut result = HashMap::new();
    bindings_impl(cx, pat, &mut result);
    result
}

fn search_same<T, Hash, Eq>(exprs: &[T],
                            hash: Hash,
                            eq: Eq) -> Option<(&T, &T)>
where Hash: Fn(&T) -> u64,
      Eq: Fn(&T, &T) -> bool {
    // common cases
    if exprs.len() < 2 {
        return None;
    }
    else if exprs.len() == 2 {
        return if eq(&exprs[0], &exprs[1]) {
            Some((&exprs[0], &exprs[1]))
        }
        else {
            None
        }
    }

    let mut map : HashMap<_, Vec<&_>> = HashMap::with_capacity(exprs.len());

    for expr in exprs {
        match map.entry(hash(expr)) {
            Entry::Occupied(o) => {
                for o in o.get() {
                    if eq(&o, expr) {
                        return Some((&o, expr))
                    }
                }
            }
            Entry::Vacant(v) => { v.insert(vec![expr]); }
        }
    }

    None
}
<|endoftext|>


<commit_before>extern crate reqwest;
use reqwest::multipart;

fn main() -> Result<(), reqwest::Error> {
    let form = multipart::Form::new()
        .text("from", "test@tester.com")
        .text("to", "devs@tester.net")
        .text("subject", "Hello")
        .text("text", "Testing the converter!");

    let res = reqwest::Client::new()
        .post("\\")
        .basic_auth("test", Some(""))
        .multipart(form)
        .send()?
        .text()?;
    println!("{}", res);

    Ok(())
}
<commit_msg>re-add correct host string from PR<commit_after>extern crate reqwest;
use reqwest::multipart;

fn main() -> Result<(), reqwest::Error> {
    let form = multipart::Form::new()
        .text("from", "test@tester.com")
        .text("to", "devs@tester.net")
        .text("subject", "Hello")
        .text("text", "Testing the converter!");

    let res = reqwest::Client::new()
        .post("https://api.net/v3")
        .basic_auth("test", Some(""))
        .multipart(form)
        .send()?
        .text()?;
    println!("{}", res);

    Ok(())
}
<|endoftext|>