<commit_before>#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct CurrencyAmount {
  pub currency: String,
  pub amount: f64,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct PostalAddress {
  pub name: String,
  pub address1: String,
  pub address2: Option<String>,
  pub city: String,
  pub state: String,
  pub postalCode: String,
  pub country: String,
  pub addressType: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct ShippingInformation {
  pub phone: String,
  pub estimatedDeliveryDate: i64,
  pub estimatedShipDate: i64,
  pub methodCode: String,
  pub postalAddress: PostalAddress,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineItem {
  pub productName: String,
  pub sku: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Tax {
  pub taxName: String,
  pub taxAmount: CurrencyAmount,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineChargeItem {
  pub chargeType: String,
  pub chargeName: String,
  pub chargeAmount: CurrencyAmount,
  pub tax: Option<Tax>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Quantity {
  pub unitOfMeasurement: String,
  pub amount: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineCharges {
  pub charge: Vec<OrderLineChargeItem>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineTrackingInfoCarrier {
  pub otherCarrier: Option<String>,
  pub carrier: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineTrackingInfo {
  pub shipDateTime: i64,
  pub carrierName: OrderLineTrackingInfoCarrier,
  pub methodCode: String,
  pub trackingNumber: String,
  pub trackingURL: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineStatus {
  pub status: String,
  pub statusQuantity: Quantity,
  // pub cancellationReason: Option<?>,
  pub trackingInfo: Option<OrderLineTrackingInfo>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineStatuss {
  pub orderLineStatus: Vec<OrderLineStatus>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLine {
  pub lineNumber: String,
  pub item: OrderLineItem,
  pub charges: OrderLineCharges,
  pub orderLineQuantity: Quantity,
  pub statusDate: i64,
  pub orderLineStatuses: OrderLineStatuss,
  // pub refund: Option<?>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLines {
  pub orderLine: Vec<OrderLine>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Order {
  pub purchaseOrderId: String,
  pub customerOrderId: String,
  pub customerEmailId: String,
  pub orderDate: i64,
  pub shippingInfo: ShippingInformation,
  pub orderLines: OrderLines,
}

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

  #[test]
  fn deserialize() {
    use serde_json;

    let json = r##"
      [
        {
          "purchaseOrderId": "11",
          "customerOrderId": "12",
          "customerEmailId": "1@relay.walmart.com",
          "orderDate": 1501903867000,
          "shippingInfo": {
            "phone": "9176070000",
            "estimatedDeliveryDate": 1502863200000,
            "estimatedShipDate": 1502258400000,
            "methodCode": "Standard",
            "postalAddress": {
              "name": "Foo Bar",
              "address1": "7777 Madisoner Ct",
              "address2": null,
              "city": "Fake",
              "state": "VA",
              "postalCode": "78787",
              "country": "USA",
              "addressType": "RESIDENTIAL"
            }
          },
          "orderLines": {
            "orderLine": [
              {
                "lineNumber": "1",
                "item": {
                  "productName": "Edifier H850 Over-the-ear Pro Headphones",
                  "sku": "edifier-h850"
                },
                "charges": {
                  "charge": [
                    {
                      "chargeType": "PRODUCT",
                      "chargeName": "ItemPrice",
                      "chargeAmount": {
                        "currency": "USD",
                        "amount": 39.99
                      },
                      "tax": null
                    }
                  ]
                },
                "orderLineQuantity": {
                  "unitOfMeasurement": "EACH",
                  "amount": "1"
                },
                "statusDate": 1502114839000,
                "orderLineStatuses": {
                  "orderLineStatus": [
                    {
                      "status": "Shipped",
                      "statusQuantity": {
                        "unitOfMeasurement": "EACH",
                        "amount": "1"
                      },
                      "cancellationReason": null,
                      "trackingInfo": {
                        "shipDateTime": 1502089301000,
                        "carrierName": {
                          "otherCarrier": "OtherCarrier",
                          "carrier": null
                        },
                        "methodCode": "Standard",
                        "trackingNumber": "TBA387619000000",
                        "trackingURL": "https://edifier-usa.myshopify.com/admin/orders/5994717066"
                      }
                    }
                  ]
                },
                "refund": null
              }
            ]
          }
        },
        {
          "purchaseOrderId": "21",
          "customerOrderId": "22",
          "customerEmailId": "2@relay.walmart.com",
          "orderDate": 1491380201000,
          "shippingInfo": {
            "phone": "6026900000",
            "estimatedDeliveryDate": 1492236000000,
            "estimatedShipDate": 1491631200000,
            "methodCode": "Standard",
            "postalAddress": {
              "name": "Joe Da",
              "address1": "1234 Vard St.  Apt 4444",
              "address2": null,
              "city": "Fake",
              "state": "AZ",
              "postalCode": "77777",
              "country": "USA",
              "addressType": "RESIDENTIAL"
            }
          },
          "orderLines": {
            "orderLine": [
              {
                "lineNumber": "1",
                "item": {
                  "productName": "Edifier R1280T Powered Bookshelf Speakers Studio Monitors",
                  "sku": "EDIFIERr1280t"
                },
                "charges": {
                  "charge": [
                    {
                      "chargeType": "PRODUCT",
                      "chargeName": "ItemPrice",
                      "chargeAmount": {
                        "currency": "USD",
                        "amount": 0.0
                      },
                      "tax": null
                    }
                  ]
                },
                "orderLineQuantity": {
                  "unitOfMeasurement": "EACH",
                  "amount": "1"
                },
                "statusDate": 1491839507000,
                "orderLineStatuses": {
                  "orderLineStatus": [
                    {
                      "status": "Cancelled",
                      "statusQuantity": {
                        "unitOfMeasurement": "EACH",
                        "amount": "1"
                      },
                      "cancellationReason": null,
                      "trackingInfo": null
                    }
                  ]
                },
                "refund": null
              }
            ]
          }
        }
      ]
    "##;

    let orders: Vec<Order> = serde_json::from_str(&json).unwrap();
    assert_eq!(orders[0].orderLines.orderLine[0].lineNumber, "1");
    assert_eq!(orders[0].orderLines.orderLine[0].item.sku, "edifier-h850");
  }
}<commit_msg>fix: OrderLineTrackingInfo.trackingURL is nullable.<commit_after>#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct CurrencyAmount {
  pub currency: String,
  pub amount: f64,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct PostalAddress {
  pub name: String,
  pub address1: String,
  pub address2: Option<String>,
  pub city: String,
  pub state: String,
  pub postalCode: String,
  pub country: String,
  pub addressType: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct ShippingInformation {
  pub phone: String,
  pub estimatedDeliveryDate: i64,
  pub estimatedShipDate: i64,
  pub methodCode: String,
  pub postalAddress: PostalAddress,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineItem {
  pub productName: String,
  pub sku: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Tax {
  pub taxName: String,
  pub taxAmount: CurrencyAmount,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineChargeItem {
  pub chargeType: String,
  pub chargeName: String,
  pub chargeAmount: CurrencyAmount,
  pub tax: Option<Tax>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Quantity {
  pub unitOfMeasurement: String,
  pub amount: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineCharges {
  pub charge: Vec<OrderLineChargeItem>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineTrackingInfoCarrier {
  pub otherCarrier: Option<String>,
  pub carrier: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineTrackingInfo {
  pub shipDateTime: i64,
  pub carrierName: OrderLineTrackingInfoCarrier,
  pub methodCode: String,
  pub trackingNumber: String,
  pub trackingURL: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineStatus {
  pub status: String,
  pub statusQuantity: Quantity,
  // pub cancellationReason: Option<?>,
  pub trackingInfo: Option<OrderLineTrackingInfo>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLineStatuss {
  pub orderLineStatus: Vec<OrderLineStatus>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLine {
  pub lineNumber: String,
  pub item: OrderLineItem,
  pub charges: OrderLineCharges,
  pub orderLineQuantity: Quantity,
  pub statusDate: i64,
  pub orderLineStatuses: OrderLineStatuss,
  // pub refund: Option<?>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct OrderLines {
  pub orderLine: Vec<OrderLine>,
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Order {
  pub purchaseOrderId: String,
  pub customerOrderId: String,
  pub customerEmailId: String,
  pub orderDate: i64,
  pub shippingInfo: ShippingInformation,
  pub orderLines: OrderLines,
}

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

  #[test]
  fn deserialize() {
    use serde_json;

    let json = r##"
      [
        {
          "purchaseOrderId": "11",
          "customerOrderId": "12",
          "customerEmailId": "1@relay.walmart.com",
          "orderDate": 1501903867000,
          "shippingInfo": {
            "phone": "9176070000",
            "estimatedDeliveryDate": 1502863200000,
            "estimatedShipDate": 1502258400000,
            "methodCode": "Standard",
            "postalAddress": {
              "name": "Foo Bar",
              "address1": "7777 Madisoner Ct",
              "address2": null,
              "city": "Fake",
              "state": "VA",
              "postalCode": "78787",
              "country": "USA",
              "addressType": "RESIDENTIAL"
            }
          },
          "orderLines": {
            "orderLine": [
              {
                "lineNumber": "1",
                "item": {
                  "productName": "Edifier H850 Over-the-ear Pro Headphones",
                  "sku": "edifier-h850"
                },
                "charges": {
                  "charge": [
                    {
                      "chargeType": "PRODUCT",
                      "chargeName": "ItemPrice",
                      "chargeAmount": {
                        "currency": "USD",
                        "amount": 39.99
                      },
                      "tax": null
                    }
                  ]
                },
                "orderLineQuantity": {
                  "unitOfMeasurement": "EACH",
                  "amount": "1"
                },
                "statusDate": 1502114839000,
                "orderLineStatuses": {
                  "orderLineStatus": [
                    {
                      "status": "Shipped",
                      "statusQuantity": {
                        "unitOfMeasurement": "EACH",
                        "amount": "1"
                      },
                      "cancellationReason": null,
                      "trackingInfo": {
                        "shipDateTime": 1502089301000,
                        "carrierName": {
                          "otherCarrier": "OtherCarrier",
                          "carrier": null
                        },
                        "methodCode": "Standard",
                        "trackingNumber": "TBA387619000000",
                        "trackingURL": "https://edifier-usa.myshopify.com/admin/orders/5994717066"
                      }
                    }
                  ]
                },
                "refund": null
              }
            ]
          }
        },
        {
          "purchaseOrderId": "21",
          "customerOrderId": "22",
          "customerEmailId": "2@relay.walmart.com",
          "orderDate": 1491380201000,
          "shippingInfo": {
            "phone": "6026900000",
            "estimatedDeliveryDate": 1492236000000,
            "estimatedShipDate": 1491631200000,
            "methodCode": "Standard",
            "postalAddress": {
              "name": "Joe Da",
              "address1": "1234 Vard St.  Apt 4444",
              "address2": null,
              "city": "Fake",
              "state": "AZ",
              "postalCode": "77777",
              "country": "USA",
              "addressType": "RESIDENTIAL"
            }
          },
          "orderLines": {
            "orderLine": [
              {
                "lineNumber": "1",
                "item": {
                  "productName": "Edifier R1280T Powered Bookshelf Speakers Studio Monitors",
                  "sku": "EDIFIERr1280t"
                },
                "charges": {
                  "charge": [
                    {
                      "chargeType": "PRODUCT",
                      "chargeName": "ItemPrice",
                      "chargeAmount": {
                        "currency": "USD",
                        "amount": 0.0
                      },
                      "tax": null
                    }
                  ]
                },
                "orderLineQuantity": {
                  "unitOfMeasurement": "EACH",
                  "amount": "1"
                },
                "statusDate": 1491839507000,
                "orderLineStatuses": {
                  "orderLineStatus": [
                    {
                      "status": "Cancelled",
                      "statusQuantity": {
                        "unitOfMeasurement": "EACH",
                        "amount": "1"
                      },
                      "cancellationReason": null,
                      "trackingInfo": null
                    }
                  ]
                },
                "refund": null
              }
            ]
          }
        }
      ]
    "##;

    let orders: Vec<Order> = serde_json::from_str(&json).unwrap();
    assert_eq!(orders[0].orderLines.orderLine[0].lineNumber, "1");
    assert_eq!(orders[0].orderLines.orderLine[0].item.sku, "edifier-h850");
  }
}
<|endoftext|>


<commit_before>//! Alacritty - The GPU Enhanced Terminal.

#![warn(rust_2018_idioms, future_incompatible)]
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// With the default subsystem, 'console', windows creates an additional console
// window for the program.
// This is silently ignored on non-windows systems.
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
#![windows_subsystem = "windows"]

#[cfg(not(any(feature = "x11", feature = "wayland", target_os = "macos", windows)))]
compile_error!(r#"at least one of the "x11"/"wayland" features must be enabled"#);

#[cfg(target_os = "macos")]
use std::env;
use std::io::{self, Write};
use std::path::PathBuf;
use std::string::ToString;
use std::{fs, process};

use glutin::event_loop::EventLoop as GlutinEventLoop;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use log::info;
#[cfg(windows)]
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};

use alacritty_terminal::tty;

mod cli;
mod clipboard;
mod config;
mod daemon;
mod display;
mod event;
mod input;
#[cfg(unix)]
mod ipc;
mod logging;
#[cfg(target_os = "macos")]
mod macos;
mod message_bar;
#[cfg(windows)]
mod panic;
mod renderer;
mod scheduler;
mod window_context;

mod gl {
    #![allow(clippy::all)]
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}

use crate::cli::Options;
#[cfg(unix)]
use crate::cli::{MessageOptions, Subcommands};
use crate::config::{monitor, UiConfig};
use crate::event::{Event, Processor};
#[cfg(target_os = "macos")]
use crate::macos::locale;

fn main() {
    #[cfg(windows)]
    panic::attach_handler();

    // When linked with the windows subsystem windows won't automatically attach
    // to the console of the parent process, so we do it explicitly. This fails
    // silently if the parent has no console.
    #[cfg(windows)]
    unsafe {
        AttachConsole(ATTACH_PARENT_PROCESS);
    }

    // Load command line options.
    let options = Options::new();

    #[cfg(unix)]
    let result = match options.subcommands {
        Some(Subcommands::Msg(options)) => msg(options),
        None => alacritty(options),
    };

    #[cfg(not(unix))]
    let result = alacritty(options);

    // Handle command failure.
    if let Err(err) = result {
        eprintln!("Error: {}", err);
        process::exit(1);
    }
}

/// `msg` subcommand entrypoint.
#[cfg(unix)]
fn msg(options: MessageOptions) -> Result<(), String> {
    ipc::send_message(options.socket, options.message).map_err(|err| err.to_string())
}

/// Temporary files stored for Alacritty.
///
/// This stores temporary files to automate their destruction through its `Drop` implementation.
struct TemporaryFiles {
    #[cfg(unix)]
    socket_path: Option<PathBuf>,
    log_file: Option<PathBuf>,
}

impl Drop for TemporaryFiles {
    fn drop(&mut self) {
        // Clean up the IPC socket file.
        #[cfg(unix)]
        if let Some(socket_path) = &self.socket_path {
            let _ = fs::remove_file(socket_path);
        }

        // Clean up logfile.
        if let Some(log_file) = &self.log_file {
            if fs::remove_file(log_file).is_ok() {
                let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display());
            }
        }
    }
}

/// Run main Alacritty entrypoint.
///
/// Creates a window, the terminal state, PTY, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn alacritty(options: Options) -> Result<(), String> {
    // Setup glutin event loop.
    let window_event_loop = GlutinEventLoop::<Event>::with_user_event();

    // Initialize the logger as soon as possible as to capture output from other subsystems.
    let log_file = logging::initialize(&options, window_event_loop.create_proxy())
        .expect("Unable to initialize logger");

    info!("Welcome to Alacritty");

    #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
    info!("Running on {}", if window_event_loop.is_x11() { "X11" } else { "Wayland" });
    #[cfg(not(any(feature = "x11", target_os = "macos", windows)))]
    info!("Running on Wayland");

    // Load configuration file.
    let config = config::load(&options);
    log_config_path(&config);

    // Update the log level from config.
    log::set_max_level(config.debug.log_level);

    // Set environment variables.
    tty::setup_env(&config.terminal_config);

    // Switch to home directory.
    #[cfg(target_os = "macos")]
    env::set_current_dir(dirs::home_dir().unwrap()).unwrap();

    // Set macOS locale.
    #[cfg(target_os = "macos")]
    locale::set_locale_environment();

    // Create a config monitor when config was loaded from path.
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    if config.live_config_reload {
        monitor::watch(config.config_paths.clone(), window_event_loop.create_proxy());
    }

    // Create the IPC socket listener.
    #[cfg(unix)]
    let socket_path = if config.ipc_socket {
        ipc::spawn_ipc_socket(&options, window_event_loop.create_proxy())
    } else {
        None
    };

    // Setup automatic RAII cleanup for our files.
    let log_cleanup = log_file.filter(|_| !config.debug.persistent_logging);
    let _files = TemporaryFiles {
        #[cfg(unix)]
        socket_path,
        log_file: log_cleanup,
    };

    // Event processor.
    let window_options = options.window_options.clone();
    let mut processor = Processor::new(config, options, &window_event_loop);

    // Create the first Alacritty window.
    let proxy = window_event_loop.create_proxy();
    processor
        .create_window(&window_event_loop, proxy, window_options)
        .map_err(|err| err.to_string())?;

    info!("Initialisation complete");

    // Start event loop and block until shutdown.
    processor.run(window_event_loop);

    // This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
    // The cause:
    //   - Drop for ConPTY will deadlock if the conout pipe has already been dropped
    //   - ConPTY is dropped when the last of processor and window context are dropped, because both
    //     of them own an Arc<ConPTY>
    //
    // The fix is to ensure that processor is dropped first. That way, when window context (i.e.
    // PTY) is dropped, it can ensure ConPTY is dropped before the conout pipe in the PTY drop
    // order.
    //
    // FIXME: Change PTY API to enforce the correct drop order with the typesystem.
    drop(processor);

    // FIXME patch notify library to have a shutdown method.
    // config_reloader.join().ok();

    // Without explicitly detaching the console cmd won't redraw it's prompt.
    #[cfg(windows)]
    unsafe {
        FreeConsole();
    }

    info!("Goodbye");
    Ok(())
}

fn log_config_path(config: &UiConfig) {
    if config.config_paths.is_empty() {
        return;
    }

    let mut msg = String::from("Configuration files loaded from:");
    for path in &config.config_paths {
        msg.push_str(&format!("\n  {:?}", path.display()));
    }

    info!("{}", msg);
}
<commit_msg>Add version to info log<commit_after>//! Alacritty - The GPU Enhanced Terminal.

#![warn(rust_2018_idioms, future_incompatible)]
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// With the default subsystem, 'console', windows creates an additional console
// window for the program.
// This is silently ignored on non-windows systems.
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
#![windows_subsystem = "windows"]

#[cfg(not(any(feature = "x11", feature = "wayland", target_os = "macos", windows)))]
compile_error!(r#"at least one of the "x11"/"wayland" features must be enabled"#);

#[cfg(target_os = "macos")]
use std::env;
use std::io::{self, Write};
use std::path::PathBuf;
use std::string::ToString;
use std::{fs, process};

use glutin::event_loop::EventLoop as GlutinEventLoop;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use log::info;
#[cfg(windows)]
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};

use alacritty_terminal::tty;

mod cli;
mod clipboard;
mod config;
mod daemon;
mod display;
mod event;
mod input;
#[cfg(unix)]
mod ipc;
mod logging;
#[cfg(target_os = "macos")]
mod macos;
mod message_bar;
#[cfg(windows)]
mod panic;
mod renderer;
mod scheduler;
mod window_context;

mod gl {
    #![allow(clippy::all)]
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}

use crate::cli::Options;
#[cfg(unix)]
use crate::cli::{MessageOptions, Subcommands};
use crate::config::{monitor, UiConfig};
use crate::event::{Event, Processor};
#[cfg(target_os = "macos")]
use crate::macos::locale;

fn main() {
    #[cfg(windows)]
    panic::attach_handler();

    // When linked with the windows subsystem windows won't automatically attach
    // to the console of the parent process, so we do it explicitly. This fails
    // silently if the parent has no console.
    #[cfg(windows)]
    unsafe {
        AttachConsole(ATTACH_PARENT_PROCESS);
    }

    // Load command line options.
    let options = Options::new();

    #[cfg(unix)]
    let result = match options.subcommands {
        Some(Subcommands::Msg(options)) => msg(options),
        None => alacritty(options),
    };

    #[cfg(not(unix))]
    let result = alacritty(options);

    // Handle command failure.
    if let Err(err) = result {
        eprintln!("Error: {}", err);
        process::exit(1);
    }
}

/// `msg` subcommand entrypoint.
#[cfg(unix)]
fn msg(options: MessageOptions) -> Result<(), String> {
    ipc::send_message(options.socket, options.message).map_err(|err| err.to_string())
}

/// Temporary files stored for Alacritty.
///
/// This stores temporary files to automate their destruction through its `Drop` implementation.
struct TemporaryFiles {
    #[cfg(unix)]
    socket_path: Option<PathBuf>,
    log_file: Option<PathBuf>,
}

impl Drop for TemporaryFiles {
    fn drop(&mut self) {
        // Clean up the IPC socket file.
        #[cfg(unix)]
        if let Some(socket_path) = &self.socket_path {
            let _ = fs::remove_file(socket_path);
        }

        // Clean up logfile.
        if let Some(log_file) = &self.log_file {
            if fs::remove_file(log_file).is_ok() {
                let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display());
            }
        }
    }
}

/// Run main Alacritty entrypoint.
///
/// Creates a window, the terminal state, PTY, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn alacritty(options: Options) -> Result<(), String> {
    // Setup glutin event loop.
    let window_event_loop = GlutinEventLoop::<Event>::with_user_event();

    // Initialize the logger as soon as possible as to capture output from other subsystems.
    let log_file = logging::initialize(&options, window_event_loop.create_proxy())
        .expect("Unable to initialize logger");

    info!("Welcome to Alacritty");
    info!("Version {}", env!("VERSION"));

    #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
    info!("Running on {}", if window_event_loop.is_x11() { "X11" } else { "Wayland" });
    #[cfg(not(any(feature = "x11", target_os = "macos", windows)))]
    info!("Running on Wayland");

    // Load configuration file.
    let config = config::load(&options);
    log_config_path(&config);

    // Update the log level from config.
    log::set_max_level(config.debug.log_level);

    // Set environment variables.
    tty::setup_env(&config.terminal_config);

    // Switch to home directory.
    #[cfg(target_os = "macos")]
    env::set_current_dir(dirs::home_dir().unwrap()).unwrap();

    // Set macOS locale.
    #[cfg(target_os = "macos")]
    locale::set_locale_environment();

    // Create a config monitor when config was loaded from path.
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    if config.live_config_reload {
        monitor::watch(config.config_paths.clone(), window_event_loop.create_proxy());
    }

    // Create the IPC socket listener.
    #[cfg(unix)]
    let socket_path = if config.ipc_socket {
        ipc::spawn_ipc_socket(&options, window_event_loop.create_proxy())
    } else {
        None
    };

    // Setup automatic RAII cleanup for our files.
    let log_cleanup = log_file.filter(|_| !config.debug.persistent_logging);
    let _files = TemporaryFiles {
        #[cfg(unix)]
        socket_path,
        log_file: log_cleanup,
    };

    // Event processor.
    let window_options = options.window_options.clone();
    let mut processor = Processor::new(config, options, &window_event_loop);

    // Create the first Alacritty window.
    let proxy = window_event_loop.create_proxy();
    processor
        .create_window(&window_event_loop, proxy, window_options)
        .map_err(|err| err.to_string())?;

    info!("Initialisation complete");

    // Start event loop and block until shutdown.
    processor.run(window_event_loop);

    // This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
    // The cause:
    //   - Drop for ConPTY will deadlock if the conout pipe has already been dropped
    //   - ConPTY is dropped when the last of processor and window context are dropped, because both
    //     of them own an Arc<ConPTY>
    //
    // The fix is to ensure that processor is dropped first. That way, when window context (i.e.
    // PTY) is dropped, it can ensure ConPTY is dropped before the conout pipe in the PTY drop
    // order.
    //
    // FIXME: Change PTY API to enforce the correct drop order with the typesystem.
    drop(processor);

    // FIXME patch notify library to have a shutdown method.
    // config_reloader.join().ok();

    // Without explicitly detaching the console cmd won't redraw it's prompt.
    #[cfg(windows)]
    unsafe {
        FreeConsole();
    }

    info!("Goodbye");
    Ok(())
}

fn log_config_path(config: &UiConfig) {
    if config.config_paths.is_empty() {
        return;
    }

    let mut msg = String::from("Configuration files loaded from:");
    for path in &config.config_paths {
        msg.push_str(&format!("\n  {:?}", path.display()));
    }

    info!("{}", msg);
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::collections::hash_map::HashMap;
use std::collections::hash_set::HashSet;
use std::collections::vec_deque::VecDeque;
use std::vec::Vec;
use js_types::js_type::{JsT, JsType};
use uuid::Uuid;

/// A map from uuids to references to objects. The interface to the garbage
/// collector, this map gets updated whenever a GC cycle is performed. References
/// that are no longer live may be dropped from the map.
pub struct UuidMap<'r> {
    inner: HashMap<Uuid, &'r RefCell<JsT>>,
}

impl<'r> UuidMap<'r> {
    pub fn new() -> UuidMap<'r> {
        UuidMap {
            inner: HashMap::new(),
        }
    }

    pub fn insert_by_refcell(&mut self, jst: &'r RefCell<JsT>) {
        self.inner.insert(jst.borrow().uuid, jst);
    }

    pub fn insert_by_val(&mut self, jst: JsT) -> bool {
        if let Some(ref refcell) = self.inner.get(&jst.uuid) {
            let mut inner = refcell.borrow_mut();
            *inner = jst;
            true
        } else {
            false
        }
    }

    pub fn remove_by_uuid(&mut self, uuid: Uuid) {
        self.inner.remove(&uuid);
    }

    pub fn remove_by_ref(&mut self, jst: &'r RefCell<JsT>) {
        self.inner.remove(&jst.borrow().uuid);
    }

    pub fn get_by_uuid(&self, uuid: Uuid) -> Option<JsT> {
        if let Some(ref refcell) = self.inner.get(&uuid) {
            Some(refcell.borrow().clone())
        } else {
            None
        }
    }
}

/// A graph node that stores sets of object uuids, as well as a list of
/// pointers to nodes it can flow to.
/// A variable v is live on edge e if there is:
///     - a node n in the CFG s.t. use[n] contains v
///     - a directed path from e -> n s.t. for every statement s on the path,
///       def[s] does not contain v
#[derive(Clone)]
struct LivenessNode {
    defs: HashSet<Uuid>,
    uses: HashSet<Uuid>,
    ins: HashSet<Uuid>,
    outs: HashSet<Uuid>,
    succ: Vec<Box<LivenessNode>>,
    pred: Vec<Box<LivenessNode>>,
}

struct LivenessGraph {
    nodes: Vec<LivenessNode>,
}

type Statement = i32; // TODO, placeholder

impl LivenessNode {
    fn new() -> LivenessNode {
        LivenessNode {
            defs: HashSet::new(),
            uses: HashSet::new(),
            ins: HashSet::new(),
            outs: HashSet::new(),
            succ: Vec::new(),
            pred: Vec::new(),
        }
    }

    fn node_flow(&mut self) {
        self.ins = self.uses.union(&self.outs.difference(&self.defs).cloned().collect())
                            .cloned().collect();

        self.outs = self.succ.clone().into_iter().flat_map(|s| s.ins).collect();
    }

    // TODO Given a list of AST instructions, parse out what UUIDs are used
    fn compute_uses(&mut self, statements: Vec<Statement>) {

    }

    fn compute_defs(&mut self, statements: Vec<Statement>) {

    }

}

impl LivenessGraph {
    fn new() -> LivenessGraph {
        LivenessGraph {
            nodes: Vec::new(),
        }
    }

    fn graph_flow(&mut self) {
        let mut node_queue: VecDeque<&mut LivenessNode> = self.nodes.iter_mut().collect();
        while let Some(n) = node_queue.pop_front() {
            let old_ins = n.ins.clone();
            let old_outs = n.outs.clone();
            n.node_flow();
            if old_outs != n.outs {
                n.succ.iter_mut().map(|s| node_queue.push_back(s));
            }
        }
    }
}

pub struct StackFrame<'m> {
    members: Vec<&'m JsT>,
}

impl<'m> StackFrame<'m> {
    pub fn new() -> StackFrame<'m> {
        StackFrame { members: Vec::new(), }
    }

    pub fn alloc_ref(&mut self, refce: &'m mut JsT)  {
        self.members.push(refce);
    }
}
<commit_msg>Remove UuidMap type (unused)<commit_after>use std::cell::RefCell;
use std::collections::hash_map::HashMap;
use std::collections::hash_set::HashSet;
use std::collections::vec_deque::VecDeque;
use std::vec::Vec;
use js_types::js_type::{JsVar, JsType};
use uuid::Uuid;

/// A graph node that stores sets of object uuids, as well as a list of
/// pointers to nodes it can flow to.
/// A variable v is live on edge e if there is:
///     - a node n in the CFG s.t. use[n] contains v
///     - a directed path from e -> n s.t. for every statement s on the path,
///       def[s] does not contain v
#[derive(Clone)]
struct LivenessNode {
    defs: HashSet<Uuid>,
    uses: HashSet<Uuid>,
    ins: HashSet<Uuid>,
    outs: HashSet<Uuid>,
    succ: Vec<Box<LivenessNode>>,
    pred: Vec<Box<LivenessNode>>,
}

struct LivenessGraph {
    nodes: Vec<LivenessNode>,
}

type Statement = i32; // TODO, placeholder

impl LivenessNode {
    fn new() -> LivenessNode {
        LivenessNode {
            defs: HashSet::new(),
            uses: HashSet::new(),
            ins: HashSet::new(),
            outs: HashSet::new(),
            succ: Vec::new(),
            pred: Vec::new(),
        }
    }

    fn node_flow(&mut self) {
        self.ins = self.uses.union(&self.outs.difference(&self.defs).cloned().collect())
                            .cloned().collect();

        self.outs = self.succ.clone().into_iter().flat_map(|s| s.ins).collect();
    }

    // TODO Given a list of AST instructions, parse out what UUIDs are used
    fn compute_uses(&mut self, statements: Vec<Statement>) {

    }

    fn compute_defs(&mut self, statements: Vec<Statement>) {

    }

}

impl LivenessGraph {
    fn new() -> LivenessGraph {
        LivenessGraph {
            nodes: Vec::new(),
        }
    }

    fn graph_flow(&mut self) {
        let mut node_queue: VecDeque<&mut LivenessNode> = self.nodes.iter_mut().collect();
        while let Some(n) = node_queue.pop_front() {
            let old_ins = n.ins.clone();
            let old_outs = n.outs.clone();
            n.node_flow();
            if old_outs != n.outs {
                n.succ.iter_mut().map(|s| node_queue.push_back(s));
            }
        }
    }
}

pub struct StackFrame<'m> {
    members: Vec<&'m JsVar>,
}

impl<'m> StackFrame<'m> {
    pub fn new() -> StackFrame<'m> {
        StackFrame { members: Vec::new(), }
    }

    pub fn alloc_ref(&mut self, refce: &'m mut JsVar)  {
        self.members.push(refce);
    }
}
<|endoftext|>


<commit_before>pub mod entrypoint;
pub use entrypoint::EntryPointAnalyzer;

pub mod exports;
pub use exports::ExportsAnalyzer;

pub mod imports;
pub use imports::ImportsAnalyzer;

pub mod ptrs;
pub use ptrs::PtrAnalyzer;

pub mod runtimefunctions;
pub use runtimefunctions::RuntimeFunctionAnalyzer;

// TODO: analyzer for orphan instructions (no predecessors) to functions
// TODO: analyzer for jump-tables, ptr tables, see vivisect/pointertables.py
// TODO: analyzer for __guard_fids_table
// TODO: analyzer for global ctors, initializers (__initterm_e, __initterm)
// TODO: analyzer for import thunks
// TODO: analyzer for TLS callbacks
// TODO: analyzer to inspect operands for pointers into the text section, e.g. argument to CreateThread
// TODO: analyzer to scan for prologues
// TODO: analyzer for RTTI
// TODO: analyzer for non-returning functions
// TODO: analyzer for VEH
// TODO: analyzer for SEH
// TODO: analyzer for FLIRT-like signatures to set function names

// TODO: analyzer for non-contiguous functions
//   e.g. __security_init_cookie in 07FB252D2E853A9B1B32F30EDE411F2EFBB9F01E4A7782DB5EACF3F55CF34902

// for k32.dll, the entrypoint + exports does not catch 0x1800012d4
// this is because its called by sub_180001630
// this has no CALL xref-to.
// it does have:
//   - RUNTIME_FUNCTION at 1800AB06C
//   - __guard_fids_table entry at 18007ABAC (TODO: analysis pass: guard fids table)
//   - function pointer at 1800779E0 (TODO: analysis pass: ptr from data->text)
<commit_msg>analysis: add notes<commit_after>pub mod entrypoint;
pub use entrypoint::EntryPointAnalyzer;

pub mod exports;
pub use exports::ExportsAnalyzer;

pub mod imports;
pub use imports::ImportsAnalyzer;

pub mod ptrs;
pub use ptrs::PtrAnalyzer;

pub mod runtimefunctions;
pub use runtimefunctions::RuntimeFunctionAnalyzer;

// TODO: analyzer for __guard_fids_table
//  ref: https://lucasg.github.io/2017/02/05/Control-Flow-Guard/
//  ref: https://lifeinhex.com/control-flow-guard-in-windows-8-1-and-vs2015/
//  ref: https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#load-configuration-directory
// TODO: analyzer for global ctors, initializers (__initterm_e, __initterm)
// TODO: analyzer for import thunks
// TODO: analyzer for TLS callbacks
// TODO: analyzer for switch tables (e.g. 748aa5fcfa2af451c76039faf6a8684d:10001AD8)
// TODO: analyzer for non-returning functions
// TODO: analyzer for code referenced from LoadConfig:
//   - ULONGLONG  SEHandlerTable;                 // VA
//   - ULONGLONG  SEHandlerCount;
//   - ULONGLONG  GuardCFCheckFunctionPointer;    // VA
//   - ULONGLONG  GuardCFDispatchFunctionPointer; // VA
//   - ULONGLONG  GuardCFFunctionTable;           // VA
//   - ULONGLONG  GuardCFFunctionCount;
//   - ULONGLONG  GuardAddressTakenIatEntryTable; // VA
//   - ULONGLONG  GuardAddressTakenIatEntryCount;
//   - ULONGLONG  GuardLongJumpTargetTable;       // VA
//   - ULONGLONG  GuardLongJumpTargetCount;
//   - ULONGLONG  DynamicValueRelocTable;         // VA
//   - ULONGLONG  HybridMetadataPointer;          // VA
// ref: https://lucasg.github.io/2017/02/05/Control-Flow-Guard/

// heuristic:
// TODO: analyzer to inspect operands for pointers into the text section, e.g. argument to CreateThread
// TODO: analyzer for jump-tables, ptr tables, see vivisect/pointertables.py
// TODO: analyzer to scan for prologues
// TODO: analyzer for RTTI
// TODO: analyzer for VEH
// TODO: analyzer for SEH
// TODO: analyzer for FLIRT-like signatures to set function names

// TODO: anomaly analyzer for non-contiguous functions
//   e.g. __security_init_cookie in 07FB252D2E853A9B1B32F30EDE411F2EFBB9F01E4A7782DB5EACF3F55CF34902
<|endoftext|>


<commit_before>/* 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 http://mozilla.org/MPL/2.0/. */

//! A middleware for authenticating requests based on
//! [JWT](https://jwt.io/introduction/).
//!
//! # The Auth Middleware
//!
//! Here _auth_ stands for _authentication_. A
//! [POST `/login`](https://github.com/fxbox/users/blob/master/doc/API.md#post-login)
//! request will authenticate a user. If so, a session JWT is returned
//! in the body of the response. This token must be sent with any further request to
//! keep track of the session.

use super::users_db::{ReadFilter, User, UsersDb};
use super::errors::*;

use crypto::sha2::Sha256;
use iron::{AroundMiddleware, Handler, headers, status};
use iron::method::Method;
use iron::prelude::*;
use jwt::{self, Error, Header, Token};

/// Structure representing [JWT claims section](https://jwt.io/introduction/).
///
/// Claims made by the authentication protocol includes `id` and `name` with
/// database unique id and username respectively.
#[derive(Default, RustcDecodable, RustcEncodable)]
pub struct SessionClaims{
    pub id: i32,
    pub name: String
}

/// Factory to create a session token `String` for a user in the database.
pub struct SessionToken;

impl SessionToken {
    pub fn for_user(user: &User) -> Result<String, Error> {
        let jwt_header: jwt::Header = Default::default();
        let claims = SessionClaims {
            id: user.id.unwrap(),
            name: user.name.to_owned()
        };
        let token = jwt::Token::new(jwt_header, claims);
        token.signed(
            user.secret.to_owned().as_bytes(),
            Sha256::new()
        )
    }
}

/// Represents an authorized endpoint.
///
/// When initializing [AuthMiddleware](./struct.AuthMiddleware.html) you need to
/// pass some routes to be authenticated, these are instances of `AuthEndpoint`.
#[derive(Debug)]
pub struct AuthEndpoint(pub Vec<Method>, pub Vec<String>);

impl PartialEq for AuthEndpoint {
    fn eq(&self, other: &AuthEndpoint) -> bool {
        let AuthEndpoint(ref self_method, ref self_path) = *self;
        let AuthEndpoint(ref other_method, ref other_path) = *other;

        let mut contains_method = false;
        for (_, method) in self_method.iter().enumerate() {
            if other_method.contains(method) {
                contains_method = true;
            }
        }

        if !contains_method {
            return false;
        }

        for (i, path) in self_path.iter().enumerate() {
            if &other_path[i] != path && "*" != path {
                return false;
            }
        }
        true
    }
}

struct AuthHandler<H: Handler> {
    handler: H,
    auth_endpoints: Vec<AuthEndpoint>
}

impl<H: Handler> Handler for AuthHandler<H> {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        {
            let endpoint = AuthEndpoint(vec![req.method.clone()],
                                        req.url.path.clone());
            // If this is not an authenticated endpoint, just proceed with the
            // original request.
            if !self.auth_endpoints.contains(&endpoint) {
                return self.handler.handle(req);
            }
        }
        // Otherwise, we need to verify the authorization header.
        match req.headers.get::<headers::Authorization<headers::Bearer>>() {
            Some(&headers::Authorization(headers::Bearer { ref token })) => {
                let token = match Token::<Header, SessionClaims>::parse(token) {
                    Ok(token) => token,
                    Err(_) => return EndpointError::with(status::Unauthorized, 401)
                };

                // To verify the token we need to get the secret associated to
                // user id contained in the token claim.
                let db = UsersDb::new();
                match db.read(ReadFilter::Id(token.claims.id)) {
                    Ok(users) => {
                        if users.len() != 1 {
                            return EndpointError::with(status::Unauthorized, 401)
                        }
                        if !token.verify(users[0].secret.to_owned().as_bytes(),
                                         Sha256::new()) {
                            return EndpointError::with(status::Unauthorized, 401)
                        }
                    },
                    Err(_) => return EndpointError::with(status::Unauthorized, 401)
                }
            },
            _ => return EndpointError::with(status::Unauthorized, 401)
        };
        self.handler.handle(req)
    }
}

/// Handle JWT authentication on specified endpoints.
///
/// # Examples
///
/// Before passing it as middleware, you should specify the authenticated
/// endpoints:
///
/// ```
/// extern crate iron;
/// extern crate foxbox_users;
///
/// fn main() {
///     use foxbox_users::users_router::UsersRouter;
///     use foxbox_users::auth_middleware::AuthMiddleware;
///     use iron::prelude::{Chain, Iron};
///
///     let router = UsersRouter::init();
///     let mut chain = Chain::new(router);
///     chain.around(AuthMiddleware{
///         auth_endpoints: vec![]
///     });
/// # if false {
///     Iron::new(chain).http("localhost:3000").unwrap();
/// # }
/// }
/// ```
pub struct AuthMiddleware {
    /// `Vec<AuthEndpoint>` containing the set of endpoints to be authenticated.
    pub auth_endpoints: Vec<AuthEndpoint>
}

impl AroundMiddleware for AuthMiddleware {
    fn around(self, handler: Box<Handler>) -> Box<Handler> {
        Box::new(AuthHandler {
            handler: handler,
            auth_endpoints: self.auth_endpoints
        }) as Box<Handler>
    }
}

#[cfg(test)]
describe! auth_middleware_tests {
    before_each {
        use iron::headers::Headers;
        use iron::prelude::*;
        use iron::method::Method;
        use iron::status::Status;
        use iron_test::request;
        use router::Router;

        fn not_implemented(_: &mut Request) -> IronResult<Response> {
            Ok(Response::with(Status::NotImplemented))
        }

        let mut router = Router::new();
        router.get("/authenticated", not_implemented);
        router.delete("/authenticated", not_implemented);
        router.get("/not_authenticated", not_implemented);

        let mut chain = Chain::new(router);
        chain.around(AuthMiddleware {
            auth_endpoints: vec![
                AuthEndpoint(vec![Method::Get, Method::Delete],
                             vec!["authenticated".to_owned()])
            ]
        });
    }

    it "should allow request to not authenticated endpoint" {
        match request::get("http://localhost:3000/not_authenticated",
                           Headers::new(), &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        }
    }

    it "should reject request to authenticated endpoint" {
        match request::get("http://localhost:3000/authenticated",
                           Headers::new(), &chain) {
            Ok(_) => assert!(false),
            Err(err) => {
                assert_eq!(err.response.status.unwrap(), Status::Unauthorized)
            }
        }
    }

    it "should allow request to authenticated endpoint" {
        use super::super::users_db::{UserBuilder, UsersDb};

        use iron::headers::{Authorization, Bearer};
        use crypto::sha2::Sha256;
        use jwt;

        let db = UsersDb::new();
        db.clear().ok();
        let user = UserBuilder::new()
            .id(1).name(String::from("username"))
            .password(String::from("password"))
            .email(String::from("username@example.com"))
            .secret(String::from("secret"))
            .finalize().unwrap();
        db.create(&user).ok();
        let jwt_header: jwt::Header = Default::default();
        let claims = SessionClaims {
            id: user.id.unwrap(),
            name: user.name.to_owned()
        };
        let token = jwt::Token::new(jwt_header, claims);
        let signed = token.signed(
            user.secret.to_owned().as_bytes(),
            Sha256::new()
        ).ok().unwrap();
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer { token: signed.to_owned() }));
        match request::get("http://localhost:3000/authenticated",
                           headers.clone(), &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        };
        match request::delete("http://localhost:3000/authenticated",
                              headers, &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        }

    }
}
<commit_msg>Accept the path as a string instead of a vector for authenticated endpoints. r=arcturus<commit_after>/* 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 http://mozilla.org/MPL/2.0/. */

//! A middleware for authenticating requests based on
//! [JWT](https://jwt.io/introduction/).
//!
//! # The Auth Middleware
//!
//! Here _auth_ stands for _authentication_. A
//! [POST `/login`](https://github.com/fxbox/users/blob/master/doc/API.md#post-login)
//! request will authenticate a user. If so, a session JWT is returned
//! in the body of the response. This token must be sent with any further request to
//! keep track of the session.

use super::users_db::{ReadFilter, User, UsersDb};
use super::errors::*;

use crypto::sha2::Sha256;
use iron::{AroundMiddleware, Handler, headers, status};
use iron::method::Method;
use iron::prelude::*;
use jwt::{self, Error, Header, Token};

/// Structure representing [JWT claims section](https://jwt.io/introduction/).
///
/// Claims made by the authentication protocol includes `id` and `name` with
/// database unique id and username respectively.
#[derive(Default, RustcDecodable, RustcEncodable)]
pub struct SessionClaims{
    pub id: i32,
    pub name: String
}

/// Factory to create a session token `String` for a user in the database.
pub struct SessionToken;

impl SessionToken {
    pub fn for_user(user: &User) -> Result<String, Error> {
        let jwt_header: jwt::Header = Default::default();
        let claims = SessionClaims {
            id: user.id.unwrap(),
            name: user.name.to_owned()
        };
        let token = jwt::Token::new(jwt_header, claims);
        token.signed(
            user.secret.to_owned().as_bytes(),
            Sha256::new()
        )
    }
}

/// Represents an authorized endpoint.
///
/// When initializing [AuthMiddleware](./struct.AuthMiddleware.html) you need to
/// pass some routes to be authenticated, these are instances of `AuthEndpoint`.
///
/// `AuthEndpoints` take a vector of methods and a string representing the path
/// of the endpoint to be authenticated. This path can contain wildcard
/// parts. For example:
///
/// AuthEndpoint(vec![Method::Get, Method::Post], "/a/path/:foo/bar/:baz")
///
/// would match with a GET or POST request to /a/path/whatever/bar/whatever
#[derive(Debug)]
pub struct AuthEndpoint(pub Vec<Method>, pub String);

impl PartialEq for AuthEndpoint {
    fn eq(&self, other: &AuthEndpoint) -> bool {
        let AuthEndpoint(ref self_method, ref self_path) = *self;
        let AuthEndpoint(ref other_method, ref other_path) = *other;

        let self_path: Vec<&str> = if self_path.starts_with('/') {
            self_path[1..].split('/').collect()
        } else {
            self_path[0..].split('/').collect()
        };
        let other_path: Vec<&str> = if other_path.starts_with('/') {
            other_path[1..].split('/').collect()
        } else {
            other_path[0..].split('/').collect()
        };

        if self_path.len() != other_path.len() {
            return false;
        }

        let mut contains_method = false;
        for (_, method) in self_method.iter().enumerate() {
            if other_method.contains(method) {
                contains_method = true;
            }
        }

        if !contains_method {
            return false;
        }

        for (i, path) in self_path.iter().enumerate() {
            if &other_path[i] != path && !path.starts_with(':') {
                return false;
            }
        }
        true
    }
}

struct AuthHandler<H: Handler> {
    handler: H,
    auth_endpoints: Vec<AuthEndpoint>
}

impl<H: Handler> Handler for AuthHandler<H> {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        {
            let endpoint = AuthEndpoint(vec![req.method.clone()],
                                        req.url.path.join("/"));
            // If this is not an authenticated endpoint, just proceed with the
            // original request.
            if !self.auth_endpoints.contains(&endpoint) {
                return self.handler.handle(req);
            }
        }
        // Otherwise, we need to verify the authorization header.
        match req.headers.get::<headers::Authorization<headers::Bearer>>() {
            Some(&headers::Authorization(headers::Bearer { ref token })) => {
                let token = match Token::<Header, SessionClaims>::parse(token) {
                    Ok(token) => token,
                    Err(_) => return EndpointError::with(status::Unauthorized, 401)
                };

                // To verify the token we need to get the secret associated to
                // user id contained in the token claim.
                let db = UsersDb::new();
                match db.read(ReadFilter::Id(token.claims.id)) {
                    Ok(users) => {
                        if users.len() != 1 {
                            return EndpointError::with(status::Unauthorized, 401)
                        }
                        if !token.verify(users[0].secret.to_owned().as_bytes(),
                                         Sha256::new()) {
                            return EndpointError::with(status::Unauthorized, 401)
                        }
                    },
                    Err(_) => return EndpointError::with(status::Unauthorized, 401)
                }
            },
            _ => return EndpointError::with(status::Unauthorized, 401)
        };
        self.handler.handle(req)
    }
}

/// Handle JWT authentication on specified endpoints.
///
/// # Examples
///
/// Before passing it as middleware, you should specify the authenticated
/// endpoints:
///
/// ```
/// extern crate iron;
/// extern crate foxbox_users;
///
/// fn main() {
///     use foxbox_users::users_router::UsersRouter;
///     use foxbox_users::auth_middleware::AuthMiddleware;
///     use iron::prelude::{Chain, Iron};
///
///     let router = UsersRouter::init();
///     let mut chain = Chain::new(router);
///     chain.around(AuthMiddleware{
///         auth_endpoints: vec![]
///     });
/// # if false {
///     Iron::new(chain).http("localhost:3000").unwrap();
/// # }
/// }
/// ```
pub struct AuthMiddleware {
    /// `Vec<AuthEndpoint>` containing the set of endpoints to be authenticated.
    pub auth_endpoints: Vec<AuthEndpoint>
}

impl AroundMiddleware for AuthMiddleware {
    fn around(self, handler: Box<Handler>) -> Box<Handler> {
        Box::new(AuthHandler {
            handler: handler,
            auth_endpoints: self.auth_endpoints
        }) as Box<Handler>
    }
}

#[cfg(test)]
describe! auth_middleware_tests {
    before_each {
        use iron::headers::Headers;
        use iron::prelude::*;
        use iron::method::Method;
        use iron::status::Status;
        use iron_test::request;
        use router::Router;

        fn not_implemented(_: &mut Request) -> IronResult<Response> {
            Ok(Response::with(Status::NotImplemented))
        }

        let mut router = Router::new();
        router.get("/authenticated", not_implemented);
        router.get("/authenticated/:foo/bar/:baz", not_implemented);
        router.delete("/authenticated", not_implemented);
        router.get("/not_authenticated", not_implemented);

        let mut chain = Chain::new(router);
        chain.around(AuthMiddleware {
            auth_endpoints: vec![
                AuthEndpoint(vec![Method::Get, Method::Delete],
                             "/authenticated".to_owned()),
                AuthEndpoint(vec![Method::Get],
                             "/authenticated/:foo/bar/:baz".to_owned())
            ]
        });
    }

    it "should allow request to not authenticated endpoint" {
        match request::get("http://localhost:3000/not_authenticated",
                           Headers::new(), &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        }
    }

    it "should reject request to authenticated endpoint" {
        match request::get("http://localhost:3000/authenticated",
                           Headers::new(), &chain) {
            Ok(_) => assert!(false),
            Err(err) => {
                assert_eq!(err.response.status.unwrap(), Status::Unauthorized)
            }
        }
    }

    it "should allow request to authenticated endpoint" {
        use super::super::users_db::{UserBuilder, UsersDb};

        use iron::headers::{Authorization, Bearer};
        use crypto::sha2::Sha256;
        use jwt;

        let db = UsersDb::new();
        db.clear().ok();
        let user = UserBuilder::new()
            .id(1).name(String::from("username"))
            .password(String::from("password"))
            .email(String::from("username@example.com"))
            .secret(String::from("secret"))
            .finalize().unwrap();
        db.create(&user).ok();
        let jwt_header: jwt::Header = Default::default();
        let claims = SessionClaims {
            id: user.id.unwrap(),
            name: user.name.to_owned()
        };
        let token = jwt::Token::new(jwt_header, claims);
        let signed = token.signed(
            user.secret.to_owned().as_bytes(),
            Sha256::new()
        ).ok().unwrap();
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer { token: signed.to_owned() }));
        match request::get("http://localhost:3000/authenticated",
                           headers.clone(), &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        };
        match request::get("http://localhost:3000/authenticated/afoo/bar/baz",
                           headers.clone(), &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        };
        match request::delete("http://localhost:3000/authenticated",
                              headers, &chain) {
            Ok(res) => {
                assert_eq!(res.status.unwrap(), Status::NotImplemented)
            },
            Err(_) => assert!(false)
        }

    }
}
<|endoftext|>


<commit_before>use super::{AnimeInfo, SyncBackend};
use config::Config;
use error::BackendError;
use input;
use reqwest::Client;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

pub struct Anilist {
    client: Client,
    access_token: String,
}

impl SyncBackend for Anilist {
    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let access_token = match config.user.access_token {
            Some(_) => config.user.decode_access_token()?,
            None => {
                // TODO: add better error reporting
                open_url(LOGIN_URL)?;

                println!("please authorize your account in the opened browser tab and paste the code below:");
                let token = input::read_line()?;
                config.user.encode_access_token(&token);

                token
            }
        };

        let instance = Anilist {
            client: Client::new(),
            access_token,
        };

        Ok(instance)
    }

    fn find_series_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        Ok(Vec::new())
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    // TODO: add support for Windows / macOS
    Command::new("xdg-open").arg(url).status()
}
<commit_msg>Implement find_series_by_name() method for Anilist backend<commit_after>use super::{AnimeInfo, SyncBackend};
use config::Config;
use error::BackendError;
use input;
use reqwest::header::{Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

pub struct Anilist {
    client: Client,
    access_token: String,
}

impl Anilist {
    fn send_request(
        &self,
        query_str: &str,
        variables: &serde_json::Value,
    ) -> Result<Response, BackendError> {
        let body = json!({
            "query": query_str,
            "variables": variables,
        }).to_string();

        let mut headers = Headers::new();
        headers.set(ContentType::json());
        headers.set(Authorization(Bearer {
            token: self.access_token.to_owned(),
        }));

        let response = self
            .client
            .post(API_URL)
            .header(ContentType::json())
            .body(body)
            .send()?;
        Ok(response)
    }
}

impl SyncBackend for Anilist {
    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let access_token = match config.user.access_token {
            Some(_) => config.user.decode_access_token()?,
            None => {
                // TODO: add better error reporting
                open_url(LOGIN_URL)?;

                println!("please authorize your account in the opened browser tab and paste the code below:");
                let token = input::read_line()?;
                config.user.encode_access_token(&token);

                token
            }
        };

        let instance = Anilist {
            client: Client::new(),
            access_token,
        };

        Ok(instance)
    }

    fn find_series_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        let query = r#"
            query ($name: String) {
                Page (page: 1, perPage: 30) {
                    media (search: $name, type: ANIME) {
                        id
                        title {
                            romaji
                        }
                        episodes
                    }
                }
            }
        "#;

        let vars = json!({ "name": name });

        let resp: serde_json::Value = {
            let text = self.send_request(query, &vars)?.text()?;
            serde_json::from_str(&text)?
        };

        use serde_json::Value;
        let mut series = Vec::new();

        match resp["data"]["Page"]["media"] {
            Value::Array(ref entries) => {
                for entry in entries {
                    let series_info: MediaData = serde_json::from_value(entry.clone())?;
                    series.push(series_info.into());
                }
            }
            _ => return Err(BackendError::InvalidJsonResponse),
        }

        Ok(series)
    }
}

#[derive(Deserialize)]
struct MediaData {
    id: u32,
    title: Title,
    episodes: u32,
}

#[derive(Deserialize)]
struct Title {
    romaji: String,
}

impl Into<AnimeInfo> for MediaData {
    fn into(self) -> AnimeInfo {
        AnimeInfo {
            id: self.id,
            title: self.title.romaji,
            episodes: self.episodes,
        }
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    // TODO: add support for Windows / macOS
    Command::new("xdg-open").arg(url).status()
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(clippy)]

extern crate tikv;
extern crate getopts;
#[macro_use]
extern crate log;

use tikv::storage::{Storage, Dsn};
use tikv::kvserver::server::run::run;
use tikv::util::{self, logger};
use tikv::storage::RaftKvConfig;
use getopts::{Options, Matches};
use std::env;
use std::fs;
use std::path::Path;
use std::collections::HashSet;
use log::LogLevelFilter;

const DEFAULT_HOST: &'static str = "0.0.0.0";
const DEFAULT_PORT: &'static str = "6102";
const DEFAULT_DSN: &'static str = "mem";

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn initial_log(matches: &Matches) {
    let log_filter = match matches.opt_str("L") {
        Some(level) => logger::get_level_by_string(&level),
        None => LogLevelFilter::Info,
    };
    util::init_log(log_filter).unwrap();
}

fn build_rocksdb_dsn(dirs: &[String]) -> Dsn {
    if dirs.is_empty() {
        panic!("use rocksdb dsn but no rocksdb directory is specified!");
    }
    Dsn::RocksDBPath(dirs[0].as_ref())
}

fn build_raftkv_dsn<'a>(matches: &Matches,
                        cfg: &'a mut RaftKvConfig,
                        dirs: &'a [String],
                        pd_addr: &'a str)
                        -> Dsn<'a> {
    assert!(dirs.len() > 0,
            "use raftkv dsn but no rocksdb directory is specified!");
    cfg.store_pathes = dirs.to_vec();
    if pd_addr.len() == 0 {
        panic!("pd_addr is required when using raftkv.");
    }
    let raftserver_addr = matches.opt_str("R").expect("raftkv dsn require raftserver addr");
    cfg.server_cfg.addr = raftserver_addr;
    let cluster_id = matches.opt_str("I").expect("raftkv dsn require cluster id");
    cfg.server_cfg.cluster_id = u64::from_str_radix(&cluster_id, 10).expect("invalid cluster id");
    if cfg.server_cfg.cluster_id == 0 {
        panic!("cluster should not be 0!");
    }
    Dsn::RaftKv(cfg, pd_addr)
}

fn build_store(matches: &Matches,
               dsn_name: &str,
               pathes: &[String],
               pd_addr: &str,
               kv_addr: &str)
               -> Storage {
    let mut cfg = RaftKvConfig::default();
    cfg.server_cfg.client_addr = kv_addr.to_owned();

    let dsn = match dsn_name {
        "mem" => Dsn::Memory,
        "rocksdb" => build_rocksdb_dsn(pathes),
        "raftkv" => build_raftkv_dsn(matches, &mut cfg, pathes, pd_addr),
        n => panic!("unrecognized dns name: {}", n),
    };
    Storage::new(dsn).unwrap()
}

/// Only directory is accepted. Same directoy can not be specified twice.
fn parse_directory(mut path: Vec<String>) -> Vec<String> {
    let mut parsed = HashSet::with_capacity(path.len());
    for origin_path in path.drain(..) {
        let p = Path::new(&origin_path);
        if p.exists() && p.is_file() {
            panic!("{} is not a directory!", origin_path);
        }
        if !p.exists() {
            fs::create_dir_all(p).unwrap();
        }
        let absolute_path = p.canonicalize().unwrap();
        let final_path = format!("{}", absolute_path.display());
        if parsed.contains(&final_path) {
            panic!("{} has been specified twice.", origin_path);
        }
        parsed.insert(final_path);
    }
    let mut res = Vec::with_capacity(parsed.len());
    res.extend(parsed.drain());
    res
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("P", "port", "set listening port", "default is 6102");
    opts.optopt("H", "host", "set listening host", "default is 0.0.0.0");
    opts.optopt("L",
                "log",
                "set log level",
                "log level: trace, debug, info, warn, error, off");
    opts.optflag("h", "help", "print this help menu");
    // TODO: support loading config file
    // opts.optopt("C", "config", "set configuration file", "file path");
    opts.optmulti("s",
                  "store",
                  "set the path to rocksdb directory",
                  "when specified multiple times, will use each as a rocksdb storage.");
    opts.optopt("S",
                "dsn",
                "set which dsn to use, default is mem",
                "dsn: mem, rocksdb, raftkv");
    opts.optopt("I", "cluster-id", "set cluster id", "must greater than 0.");
    opts.optopt("R", "raft", "set raftserver address", "host:port");
    opts.optopt("", "pd", "set pd address", "host:port");
    let matches = opts.parse(&args[1..]).expect("opts parse failed");
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    initial_log(&matches);

    let dsn_name = matches.opt_str("S").unwrap_or_else(|| DEFAULT_DSN.to_owned());
    let pathes = parse_directory(matches.opt_strs("s"));
    let pd_addr = matches.opt_str("pd").unwrap_or("".to_owned());

    let mut kv_addr = matches.opt_str("H").unwrap_or_else(|| DEFAULT_HOST.to_owned());
    let kv_port = matches.opt_str("P").unwrap_or_else(|| DEFAULT_PORT.to_owned());
    kv_addr.push_str(":");
    kv_addr.push_str(&kv_port);

    let store = build_store(&matches,
                            dsn_name.as_ref(),
                            &pathes,
                            pd_addr.as_ref(),
                            kv_addr.as_ref());

    info!("Start listening on {}...", kv_addr);
    run(&kv_addr, store);
}
<commit_msg>bin: Address comment.<commit_after>#![feature(plugin)]
#![plugin(clippy)]

extern crate tikv;
extern crate getopts;
#[macro_use]
extern crate log;

use tikv::storage::{Storage, Dsn};
use tikv::kvserver::server::run::run;
use tikv::util::{self, logger};
use tikv::storage::RaftKvConfig;
use getopts::{Options, Matches};
use std::env;
use std::fs;
use std::path::Path;
use std::collections::HashSet;
use log::LogLevelFilter;

const DEFAULT_HOST: &'static str = "127.0.0.1";
const DEFAULT_PORT: &'static str = "6102";
const DEFAULT_DSN: &'static str = "mem";

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn initial_log(matches: &Matches) {
    let log_filter = match matches.opt_str("L") {
        Some(level) => logger::get_level_by_string(&level),
        None => LogLevelFilter::Info,
    };
    util::init_log(log_filter).unwrap();
}

fn build_rocksdb_dsn(dirs: &[String]) -> Dsn {
    if dirs.is_empty() {
        panic!("use rocksdb dsn but no rocksdb directory is specified!");
    }
    Dsn::RocksDBPath(dirs[0].as_ref())
}

fn build_raftkv_dsn<'a>(matches: &Matches,
                        cfg: &'a mut RaftKvConfig,
                        dirs: &'a [String],
                        pd_addr: &'a str)
                        -> Dsn<'a> {
    assert!(dirs.len() > 0,
            "use raftkv dsn but no rocksdb directory is specified!");
    cfg.store_pathes = dirs.to_vec();
    if pd_addr.len() == 0 {
        panic!("pd_addr is required when using raftkv.");
    }
    let raftserver_addr = matches.opt_str("R").expect("raftkv dsn require raftserver addr");
    cfg.server_cfg.addr = raftserver_addr;
    let cluster_id = matches.opt_str("I").expect("raftkv dsn require cluster id");
    cfg.server_cfg.cluster_id = u64::from_str_radix(&cluster_id, 10).expect("invalid cluster id");
    if cfg.server_cfg.cluster_id == 0 {
        panic!("cluster should not be 0!");
    }
    Dsn::RaftKv(cfg, pd_addr)
}

fn build_store(matches: &Matches,
               dsn_name: &str,
               pathes: &[String],
               pd_addr: &str,
               kv_addr: &str)
               -> Storage {
    let mut cfg = RaftKvConfig::default();
    cfg.server_cfg.client_addr = kv_addr.to_owned();

    let dsn = match dsn_name {
        "mem" => Dsn::Memory,
        "rocksdb" => build_rocksdb_dsn(pathes),
        "raftkv" => build_raftkv_dsn(matches, &mut cfg, pathes, pd_addr),
        n => panic!("unrecognized dns name: {}", n),
    };
    Storage::new(dsn).unwrap()
}

/// Only directory is accepted. Same directoy can not be specified twice.
fn parse_directory(mut path: Vec<String>) -> Vec<String> {
    let mut parsed = HashSet::with_capacity(path.len());
    for origin_path in path.drain(..) {
        let p = Path::new(&origin_path);
        if p.exists() && p.is_file() {
            panic!("{} is not a directory!", origin_path);
        }
        if !p.exists() {
            fs::create_dir_all(p).unwrap();
        }
        let absolute_path = p.canonicalize().unwrap();
        let final_path = format!("{}", absolute_path.display());
        if parsed.contains(&final_path) {
            panic!("{} has been specified twice.", origin_path);
        }
        parsed.insert(final_path);
    }
    let mut res = Vec::with_capacity(parsed.len());
    res.extend(parsed.drain());
    res
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("P", "port", "set listening port", "default is 6102");
    opts.optopt("H", "host", "set listening host", "default is 0.0.0.0");
    opts.optopt("L",
                "log",
                "set log level",
                "log level: trace, debug, info, warn, error, off");
    opts.optflag("h", "help", "print this help menu");
    // TODO: support loading config file
    // opts.optopt("C", "config", "set configuration file", "file path");
    opts.optmulti("s",
                  "store",
                  "set the path to rocksdb directory",
                  "when specified multiple times, will use each as a rocksdb storage.");
    opts.optopt("S",
                "dsn",
                "set which dsn to use, default is mem",
                "dsn: mem, rocksdb, raftkv");
    opts.optopt("I", "cluster-id", "set cluster id", "must greater than 0.");
    opts.optopt("R", "raft", "set raftserver address", "host:port");
    opts.optopt("", "pd", "set pd address", "host:port");
    let matches = opts.parse(&args[1..]).expect("opts parse failed");
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    initial_log(&matches);

    let dsn_name = matches.opt_str("S").unwrap_or_else(|| DEFAULT_DSN.to_owned());
    let pathes = parse_directory(matches.opt_strs("s"));
    let pd_addr = matches.opt_str("pd").unwrap_or("".to_owned());

    // TODO: in production, we should not allow 127.0.0.1 or 0.0.0.0.
    let mut kv_addr = matches.opt_str("H").unwrap_or_else(|| DEFAULT_HOST.to_owned());
    let kv_port = matches.opt_str("P").unwrap_or_else(|| DEFAULT_PORT.to_owned());
    kv_addr.push_str(":");
    kv_addr.push_str(&kv_port);

    let store = build_store(&matches,
                            dsn_name.as_ref(),
                            &pathes,
                            pd_addr.as_ref(),
                            kv_addr.as_ref());

    info!("Start listening on {}...", kv_addr);
    run(&kv_addr, store);
}
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::net::IpAddr;
use std::str::FromStr;

/// Validates whether the given string is an IP V4
#[must_use]
pub fn validate_ip_v4<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    match IpAddr::from_str(val.into().as_ref()) {
        Ok(i) => match i {
            IpAddr::V4(_) => true,
            IpAddr::V6(_) => false,
        },
        Err(_) => false,
    }
}

/// Validates whether the given string is an IP V6
#[must_use]
pub fn validate_ip_v6<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    match IpAddr::from_str(val.into().as_ref()) {
        Ok(i) => match i {
            IpAddr::V4(_) => false,
            IpAddr::V6(_) => true,
        },
        Err(_) => false,
    }
}

/// Validates whether the given string is an IP
#[must_use]
pub fn validate_ip<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    IpAddr::from_str(val.into().as_ref()).is_ok()
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;

    use super::{validate_ip, validate_ip_v4, validate_ip_v6};

    #[test]
    fn test_validate_ip() {
        let tests = vec![
            ("1.1.1.1", true),
            ("255.0.0.0", true),
            ("0.0.0.0", true),
            ("256.1.1.1", false),
            ("25.1.1.", false),
            ("25,1,1,1", false),
            ("fe80::223:6cff:fe8a:2e8a", true),
            ("::ffff:254.42.16.14", true),
            ("2a02::223:6cff :fe8a:2e8a", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_cow() {
        let test: Cow<'static, str> = "1.1.1.1".into();
        assert_eq!(validate_ip(test), true);
        let test: Cow<'static, str> = String::from("1.1.1.1").into();
        assert_eq!(validate_ip(test), true);
        let test: Cow<'static, str> = "2a02::223:6cff :fe8a:2e8a".into();
        assert_eq!(validate_ip(test), false);
        let test: Cow<'static, str> = String::from("2a02::223:6cff :fe8a:2e8a").into();
        assert_eq!(validate_ip(test), false);
    }

    #[test]
    fn test_validate_ip_v4() {
        let tests = vec![
            ("1.1.1.1", true),
            ("255.0.0.0", true),
            ("0.0.0.0", true),
            ("256.1.1.1", false),
            ("25.1.1.", false),
            ("25,1,1,1", false),
            ("25.1 .1.1", false),
            ("1.1.1.1\n", false),
            ("٧.2٥.3٣.243", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip_v4(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_v4_cow() {
        let test: Cow<'static, str> = "1.1.1.1".into();
        assert_eq!(validate_ip_v4(test), true);
        let test: Cow<'static, str> = String::from("1.1.1.1").into();
        assert_eq!(validate_ip_v4(test), true);
        let test: Cow<'static, str> = "٧.2٥.3٣.243".into();
        assert_eq!(validate_ip_v4(test), false);
        let test: Cow<'static, str> = String::from("٧.2٥.3٣.243").into();
        assert_eq!(validate_ip_v4(test), false);
    }

    #[test]
    fn test_validate_ip_v6() {
        let tests = vec![
            ("fe80::223:6cff:fe8a:2e8a", true),
            ("2a02::223:6cff:fe8a:2e8a", true),
            ("1::2:3:4:5:6:7", true),
            ("::", true),
            ("::a", true),
            ("2::", true),
            ("::ffff:254.42.16.14", true),
            ("::ffff:0a0a:0a0a", true),
            ("::254.42.16.14", true),
            ("::0a0a:0a0a", true),
            ("foo", false),
            ("127.0.0.1", false),
            ("12345::", false),
            ("1::2::3::4", false),
            ("1::zzz", false),
            ("1:2", false),
            ("fe80::223: 6cff:fe8a:2e8a", false),
            ("2a02::223:6cff :fe8a:2e8a", false),
            ("::ffff:999.42.16.14", false),
            ("::ffff:zzzz:0a0a", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip_v6(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_v6_cow() {
        let test: Cow<'static, str> = "fe80::223:6cff:fe8a:2e8a".into();
        assert_eq!(validate_ip_v6(test), true);
        let test: Cow<'static, str> = String::from("fe80::223:6cff:fe8a:2e8a").into();
        assert_eq!(validate_ip_v6(test), true);
        let test: Cow<'static, str> = "::ffff:zzzz:0a0a".into();
        assert_eq!(validate_ip_v6(test), false);
        let test: Cow<'static, str> = String::from("::ffff:zzzz:0a0a").into();
        assert_eq!(validate_ip_v6(test), false);
    }
}
<commit_msg>Simplify validate_ip_v{4,6} methods (#147)<commit_after>use std::borrow::Cow;
use std::net::IpAddr;
use std::str::FromStr;

/// Validates whether the given string is an IP V4
#[must_use]
pub fn validate_ip_v4<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    IpAddr::from_str(val.into().as_ref()).map_or(false, |i| i.is_ipv4())
}

/// Validates whether the given string is an IP V6
#[must_use]
pub fn validate_ip_v6<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    IpAddr::from_str(val.into().as_ref()).map_or(false, |i| i.is_ipv6())
}

/// Validates whether the given string is an IP
#[must_use]
pub fn validate_ip<'a, T>(val: T) -> bool
where
    T: Into<Cow<'a, str>>,
{
    IpAddr::from_str(val.into().as_ref()).is_ok()
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;

    use super::{validate_ip, validate_ip_v4, validate_ip_v6};

    #[test]
    fn test_validate_ip() {
        let tests = vec![
            ("1.1.1.1", true),
            ("255.0.0.0", true),
            ("0.0.0.0", true),
            ("256.1.1.1", false),
            ("25.1.1.", false),
            ("25,1,1,1", false),
            ("fe80::223:6cff:fe8a:2e8a", true),
            ("::ffff:254.42.16.14", true),
            ("2a02::223:6cff :fe8a:2e8a", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_cow() {
        let test: Cow<'static, str> = "1.1.1.1".into();
        assert_eq!(validate_ip(test), true);
        let test: Cow<'static, str> = String::from("1.1.1.1").into();
        assert_eq!(validate_ip(test), true);
        let test: Cow<'static, str> = "2a02::223:6cff :fe8a:2e8a".into();
        assert_eq!(validate_ip(test), false);
        let test: Cow<'static, str> = String::from("2a02::223:6cff :fe8a:2e8a").into();
        assert_eq!(validate_ip(test), false);
    }

    #[test]
    fn test_validate_ip_v4() {
        let tests = vec![
            ("1.1.1.1", true),
            ("255.0.0.0", true),
            ("0.0.0.0", true),
            ("256.1.1.1", false),
            ("25.1.1.", false),
            ("25,1,1,1", false),
            ("25.1 .1.1", false),
            ("1.1.1.1\n", false),
            ("٧.2٥.3٣.243", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip_v4(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_v4_cow() {
        let test: Cow<'static, str> = "1.1.1.1".into();
        assert_eq!(validate_ip_v4(test), true);
        let test: Cow<'static, str> = String::from("1.1.1.1").into();
        assert_eq!(validate_ip_v4(test), true);
        let test: Cow<'static, str> = "٧.2٥.3٣.243".into();
        assert_eq!(validate_ip_v4(test), false);
        let test: Cow<'static, str> = String::from("٧.2٥.3٣.243").into();
        assert_eq!(validate_ip_v4(test), false);
    }

    #[test]
    fn test_validate_ip_v6() {
        let tests = vec![
            ("fe80::223:6cff:fe8a:2e8a", true),
            ("2a02::223:6cff:fe8a:2e8a", true),
            ("1::2:3:4:5:6:7", true),
            ("::", true),
            ("::a", true),
            ("2::", true),
            ("::ffff:254.42.16.14", true),
            ("::ffff:0a0a:0a0a", true),
            ("::254.42.16.14", true),
            ("::0a0a:0a0a", true),
            ("foo", false),
            ("127.0.0.1", false),
            ("12345::", false),
            ("1::2::3::4", false),
            ("1::zzz", false),
            ("1:2", false),
            ("fe80::223: 6cff:fe8a:2e8a", false),
            ("2a02::223:6cff :fe8a:2e8a", false),
            ("::ffff:999.42.16.14", false),
            ("::ffff:zzzz:0a0a", false),
        ];

        for (input, expected) in tests {
            assert_eq!(validate_ip_v6(input), expected);
        }
    }

    #[test]
    fn test_validate_ip_v6_cow() {
        let test: Cow<'static, str> = "fe80::223:6cff:fe8a:2e8a".into();
        assert_eq!(validate_ip_v6(test), true);
        let test: Cow<'static, str> = String::from("fe80::223:6cff:fe8a:2e8a").into();
        assert_eq!(validate_ip_v6(test), true);
        let test: Cow<'static, str> = "::ffff:zzzz:0a0a".into();
        assert_eq!(validate_ip_v6(test), false);
        let test: Cow<'static, str> = String::from("::ffff:zzzz:0a0a").into();
        assert_eq!(validate_ip_v6(test), false);
    }
}
<|endoftext|>