style(rust): Format all Rust code with rustfmt
Change-Id: Iab7e00cc26a4f9727d3ab98691ef379921a33052 Reviewed-on: https://cl.tvl.fyi/c/depot/+/5240 Tested-by: BuildkiteCI Reviewed-by: kanepyork <rikingcoding@gmail.com> Reviewed-by: Profpatsch <mail@profpatsch.de> Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
3318982f81
commit
3d8ee62087
42 changed files with 1253 additions and 876 deletions
|
@ -4,17 +4,17 @@ use std::rc::Rc;
|
|||
use std::sync::RwLock;
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
}
|
||||
|
||||
// Only added this for Go-syntax familiarity ;-)
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
Defer { f }
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,9 @@ type ErrorHandle<T> = Rc<RwLock<Option<T>>>;
|
|||
///////////////////
|
||||
|
||||
#[derive(Debug)] // Debug trait for some default way to print the type.
|
||||
enum Error { DropError }
|
||||
enum Error {
|
||||
DropError,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create a place to store the error.
|
||||
|
@ -60,7 +62,7 @@ fn main() {
|
|||
|
||||
match *drop_err.read().unwrap() {
|
||||
Some(ref err) => println!("Oh no, an error occured: {:?}!", err),
|
||||
None => println!("Phew, everything went well.")
|
||||
None => println!("Phew, everything went well."),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Go's defer in Rust!
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
}
|
||||
|
||||
// Only added this for Go-syntax familiarity ;-)
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
Defer { f }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Go's defer in Rust, with a little twist!
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::prelude::*;
|
||||
use failure::format_err;
|
||||
use failure::Error;
|
||||
use failure::{format_err, Error};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Maximum number of times we'll follow a `see: ` pointer.
|
||||
|
|
|
@ -7,8 +7,7 @@ use crate::cfg::Config;
|
|||
use crate::keyword::KeywordDetails;
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use failure::format_err;
|
||||
use failure::Error;
|
||||
use failure::{format_err, Error};
|
||||
use irc::client::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{debug, info, warn};
|
||||
|
@ -153,8 +152,9 @@ impl App {
|
|||
// Use `nick` here, so things like "grfn: see glittershark" work.
|
||||
let val = if let Some(last) = chan_lastmsgs.get(nick_to_grab) {
|
||||
if last.starts_with("\x01ACTION ") {
|
||||
// Yes, this is inefficient, but it's better than writing some hacky CTCP parsing code
|
||||
// I guess (also, characters are hard, so just blindly slicing seems like a bad idea)
|
||||
// Yes, this is inefficient, but it's better than writing some hacky CTCP parsing
|
||||
// code I guess (also, characters are hard, so just blindly slicing
|
||||
// seems like a bad idea)
|
||||
format!(
|
||||
"* {} {}",
|
||||
nick_to_grab,
|
||||
|
|
|
@ -67,23 +67,24 @@
|
|||
//!
|
||||
//! [JWKS]: https://tools.ietf.org/html/rfc7517
|
||||
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate base64;
|
||||
extern crate openssl;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use base64::{URL_SAFE_NO_PAD, Config, DecodeError};
|
||||
use base64::{Config, DecodeError, URL_SAFE_NO_PAD};
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{Public, PKey};
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::sign::Verifier;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use std::time::{UNIX_EPOCH, Duration, SystemTime};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -101,12 +102,16 @@ fn jwt_forgiving() -> Config {
|
|||
/// JWT algorithm used. The only supported algorithm is currently
|
||||
/// RS256.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyAlgorithm { RS256 }
|
||||
enum KeyAlgorithm {
|
||||
RS256,
|
||||
}
|
||||
|
||||
/// Type of key contained in a JWT. The only supported key type is
|
||||
/// currently RSA.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyType { RSA }
|
||||
enum KeyType {
|
||||
RSA,
|
||||
}
|
||||
|
||||
/// Representation of a single JSON Web Key. See [RFC
|
||||
/// 7517](https://tools.ietf.org/html/rfc7517#section-4).
|
||||
|
@ -146,7 +151,7 @@ impl JWKS {
|
|||
|
||||
/// Representation of an undecoded JSON Web Token. See [RFC
|
||||
/// 7519](https://tools.ietf.org/html/rfc7519).
|
||||
struct JWT<'a> (&'a str);
|
||||
struct JWT<'a>(&'a str);
|
||||
|
||||
/// Representation of a decoded and validated JSON Web Token.
|
||||
///
|
||||
|
@ -217,15 +222,21 @@ pub enum ValidationError {
|
|||
type JWTResult<T> = Result<T, ValidationError>;
|
||||
|
||||
impl From<ErrorStack> for ValidationError {
|
||||
fn from(err: ErrorStack) -> Self { ValidationError::OpenSSL(err) }
|
||||
fn from(err: ErrorStack) -> Self {
|
||||
ValidationError::OpenSSL(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ValidationError {
|
||||
fn from(err: serde_json::Error) -> Self { ValidationError::JSON(err) }
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
ValidationError::JSON(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for ValidationError {
|
||||
fn from(err: DecodeError) -> Self { ValidationError::InvalidBase64(err) }
|
||||
fn from(err: DecodeError) -> Self {
|
||||
ValidationError::InvalidBase64(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to extract the `kid`-claim out of a JWT's header claims.
|
||||
|
@ -266,9 +277,7 @@ pub fn token_kid(token: &str) -> JWTResult<Option<String>> {
|
|||
///
|
||||
/// It is the user's task to ensure that the correct JWK is passed in
|
||||
/// for validation.
|
||||
pub fn validate(token: &str,
|
||||
jwk: &JWK,
|
||||
validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
pub fn validate(token: &str, jwk: &JWK, validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
let jwt = JWT(token);
|
||||
let public_key = public_key_from_jwk(&jwk)?;
|
||||
validate_jwt_signature(&jwt, public_key)?;
|
||||
|
@ -279,7 +288,7 @@ pub fn validate(token: &str,
|
|||
if parts.len() != 3 {
|
||||
// This is unlikely considering that validation has already
|
||||
// been performed at this point, but better safe than sorry.
|
||||
return Err(ValidationError::InvalidComponents)
|
||||
return Err(ValidationError::InvalidComponents);
|
||||
}
|
||||
|
||||
// Perform claim validations before constructing the valid token:
|
||||
|
@ -351,7 +360,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
verifier.update(data.as_bytes())?;
|
||||
|
||||
match verifier.verify(&sig)? {
|
||||
true => Ok(()),
|
||||
true => Ok(()),
|
||||
false => Err(ValidationError::InvalidSignature),
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +371,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
#[serde(untagged)]
|
||||
enum Audience {
|
||||
Single(String),
|
||||
Multi(Vec<String>)
|
||||
Multi(Vec<String>),
|
||||
}
|
||||
|
||||
/// Internal helper struct for claims that are relevant for claim
|
||||
|
@ -376,15 +385,14 @@ struct PartialClaims {
|
|||
}
|
||||
|
||||
/// Apply a single validation to the claim set of a token.
|
||||
fn apply_validation(claims: &PartialClaims,
|
||||
validation: Validation) -> Result<(), &'static str> {
|
||||
fn apply_validation(claims: &PartialClaims, validation: Validation) -> Result<(), &'static str> {
|
||||
match validation {
|
||||
// Validate that an 'iss' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Issuer(iss) => {
|
||||
match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => if *claim == iss {
|
||||
Validation::Issuer(iss) => match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => {
|
||||
if *claim == iss {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'iss' claim does not match")
|
||||
|
@ -394,15 +402,17 @@ fn apply_validation(claims: &PartialClaims,
|
|||
|
||||
// Validate that an 'aud' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Audience(aud) => {
|
||||
match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => if *claim == aud {
|
||||
Validation::Audience(aud) => match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => {
|
||||
if *claim == aud {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
},
|
||||
Some(Audience::Multi(ref claims)) => if claims.contains(&aud) {
|
||||
}
|
||||
}
|
||||
Some(Audience::Multi(ref claims)) => {
|
||||
if claims.contains(&aud) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
|
@ -447,12 +457,12 @@ fn apply_validation(claims: &PartialClaims,
|
|||
}
|
||||
|
||||
/// Apply all requested validations to a partial claim set.
|
||||
fn validate_claims(claims: PartialClaims,
|
||||
validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations.into_iter()
|
||||
fn validate_claims(claims: PartialClaims, validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations
|
||||
.into_iter()
|
||||
.map(|v| apply_validation(&claims, v))
|
||||
.filter_map(|result| match result {
|
||||
Ok(_) => None,
|
||||
Ok(_) => None,
|
||||
Err(err) => Some(err),
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -21,14 +21,19 @@ fn test_fragment_decoding() {
|
|||
let bignum = decode_fragment(fragment).expect("Failed to decode fragment");
|
||||
|
||||
let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289";
|
||||
assert_eq!(expected, format!("{}", bignum), "Decoded fragment should match ");
|
||||
assert_eq!(
|
||||
expected,
|
||||
format!("{}", bignum),
|
||||
"Decoded fragment should match "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_find_jwks() {
|
||||
let json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"mUjI\\/rIMLLtung35BKZfdbrqtlEAAYJ4JX\\/SKvnLxJc=\",\"n\":\"ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ\",\"e\":\"GK7oLCDbNPAF59LhvyseqcG04hDnPs58qGYolr_HHmaR4lulWJ90ozx6e4Ut363yKG2p9vwvivR5UIC-aLPtqT2qr-OtjhBFzUFVaMGZ6mPCvMKk0AgMYdOHvWTgBSqQtNJTvl1yYLnhcWyoE2fLQhoEbY9qUyCBCEOScXOZRDpnmBtz5I8q5yYMV6a920J24T_IYbxHgkGcEU2SGg-b1cOMD7Rja7vCfV---CQ2pR4leQ0jufzudDoe7z3mziJm-Ihcdrz2Ujy5kPEMdz6R55prJ-ENKrkD_X4u5aSlSRaetwmHS3oAVkjr1JwUNbqnpM-kOqieqHEp8LUmez-Znw\"}]}";
|
||||
let jwks: JWKS = serde_json::from_str(json).expect("Failed to decode JWKS");
|
||||
let jwk = jwks.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
let jwk = jwks
|
||||
.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
public_key_from_jwk(&jwk).expect("Failed to construct public key from JWK");
|
||||
|
@ -39,18 +44,21 @@ fn test_token_kid() {
|
|||
let jwt = "eyJraWQiOiI4ckRxOFB3MEZaY2FvWFdURVZRbzcrVGYyWXpTTDFmQnhOS1BDZWJhYWk0PSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoLnRlc3QuYXByaWxhLm5vIiwiaWF0IjoxNTM2MDUwNjkzLCJleHAiOjE1MzYwNTQyOTMsInN1YiI6IjQyIiwiZXh0Ijoic21va2V0ZXN0IiwicHJ2IjoiYXJpc3RpIiwic2NwIjoicHJvY2VzcyJ9.gOLsv98109qLkmRK6Dn7WWRHLW7o8W78WZcWvFZoxPLzVO0qvRXXRLYc9h5chpfvcWreLZ4f1cOdvxv31_qnCRSQQPOeQ7r7hj_sPEDzhKjk-q2aoNHaGGJg1vabI--9EFkFsGQfoS7UbMMssS44dgR68XEnKtjn0Vys-Vzbvz_CBSCH6yQhRLik2SU2jR2L7BoFvh4LGZ6EKoQWzm8Z-CHXLGLUs4Hp5aPhF46dGzgAzwlPFW4t9G4DciX1uB4vv1XnfTc5wqJch6ltjKMde1GZwLR757a8dJSBcmGWze3UNE2YH_VLD7NCwH2kkqr3gh8rn7lWKG4AUIYPxsw9CB";
|
||||
|
||||
let kid = token_kid(&jwt).expect("Failed to extract token KID");
|
||||
assert_eq!(Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid, "Extracted KID did not match expected KID");
|
||||
assert_eq!(
|
||||
Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid,
|
||||
"Extracted KID did not match expected KID"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_jwt() {
|
||||
let jwks_json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=\",\"n\":\"l4UTgk1zr-8C8utt0E57DtBV6qqAPWzVRrIuQS2j0_hp2CviaNl5XzGRDnB8gwk0Hx95YOhJupAe6RNq5ok3fDdxL7DLvppJNRLz3Ag9CsmDLcbXgNEQys33fBJaPw1v3GcaFC4tisU5p-o1f5RfWwvwdBtdBfGiwT1GRvbc5sFx6M4iYjg9uv1lNKW60PqSJW4iDYrfqzZmB0zF1SJ0BL_rnQZ1Wi_UkFmNe9arM8W9tI9T3Ie59HITFuyVSTCt6qQEtSfa1e5PiBaVuV3qoFI2jPBiVZQ6LPGBWEDyz4QtrHLdECPPoTF30NN6TSVwwlRbCuUUrdNdXdjYe2dMFQ\",\"e\":\"DhaD5zC7mzaDvHO192wKT_9sfsVmdy8w8T8C9VG17_b1jG2srd3cmc6Ycw-0blDf53Wrpi9-KGZXKHX6_uIuJK249WhkP7N1SHrTJxO0sUJ8AhK482PLF09Qtu6cUfJqY1X1y1S2vACJZItU4Vjr3YAfiVGQXeA8frAf7Sm4O1CBStCyg6yCcIbGojII0jfh2vSB-GD9ok1F69Nmk-R-bClyqMCV_Oq-5a0gqClVS8pDyGYMgKTww2RHgZaFSUcG13KeLMQsG2UOB2OjSC8FkOXK00NBlAjU3d0Vv-IamaLIszO7FQBY3Oh0uxNOvIE9ofQyCOpB-xIK6V9CTTphxw\"}]}";
|
||||
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json)
|
||||
.expect("Failed to decode JWKS");
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json).expect("Failed to decode JWKS");
|
||||
|
||||
let jwk = jwks.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
let jwk = jwks
|
||||
.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
let pkey = public_key_from_jwk(&jwk).expect("Failed to construct public key");
|
||||
|
|
|
@ -33,9 +33,12 @@
|
|||
//! use crimp::Request;
|
||||
//!
|
||||
//! let response = Request::get("http://httpbin.org/get")
|
||||
//! .user_agent("crimp test suite").unwrap()
|
||||
//! .send().unwrap()
|
||||
//! .as_string().unwrap();
|
||||
//! .user_agent("crimp test suite")
|
||||
//! .unwrap()
|
||||
//! .send()
|
||||
//! .unwrap()
|
||||
//! .as_string()
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! println!("Status: {}\nBody: {}", response.status, response.body);
|
||||
//! # assert_eq!(response.status, 200);
|
||||
|
@ -54,10 +57,9 @@
|
|||
//!
|
||||
//! All optional features are enabled by default.
|
||||
//!
|
||||
//! * `json`: Adds `Request::json` and `Response::as_json` methods
|
||||
//! which can be used for convenient serialisation of
|
||||
//! request/response bodies using `serde_json`. This feature adds a
|
||||
//! dependency on the `serde` and `serde_json` crates.
|
||||
//! * `json`: Adds `Request::json` and `Response::as_json` methods which can be used for convenient
|
||||
//! serialisation of request/response bodies using `serde_json`. This feature adds a dependency on
|
||||
//! the `serde` and `serde_json` crates.
|
||||
//!
|
||||
//! ## Initialisation
|
||||
//!
|
||||
|
@ -72,32 +74,42 @@
|
|||
|
||||
extern crate curl;
|
||||
|
||||
#[cfg(feature = "json")] extern crate serde;
|
||||
#[cfg(feature = "json")] extern crate serde_json;
|
||||
#[cfg(feature = "json")]
|
||||
extern crate serde;
|
||||
#[cfg(feature = "json")]
|
||||
extern crate serde_json;
|
||||
|
||||
pub use curl::init;
|
||||
|
||||
use curl::easy::{Auth, Easy, Form, List, Transfer, ReadError, WriteError};
|
||||
use curl::easy::{Auth, Easy, Form, List, ReadError, Transfer, WriteError};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::string::{FromUtf8Error, ToString};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "json")] use serde::Serialize;
|
||||
#[cfg(feature = "json")] use serde::de::DeserializeOwned;
|
||||
#[cfg(feature = "json")]
|
||||
use serde::de::DeserializeOwned;
|
||||
#[cfg(feature = "json")]
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// HTTP method to use for the request.
|
||||
enum Method {
|
||||
Get, Post, Put, Patch, Delete
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Patch,
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Certificate types for client-certificate key pairs.
|
||||
pub enum CertType {
|
||||
P12, PEM, DER
|
||||
P12,
|
||||
PEM,
|
||||
DER,
|
||||
}
|
||||
|
||||
/// Builder structure for an HTTP request.
|
||||
|
@ -145,7 +157,7 @@ pub struct Response<T> {
|
|||
pub body: T,
|
||||
}
|
||||
|
||||
impl <'a> Request<'a> {
|
||||
impl<'a> Request<'a> {
|
||||
/// Initiate an HTTP request with the given method and URL.
|
||||
fn new(method: Method, url: &'a str) -> Self {
|
||||
Request {
|
||||
|
@ -158,19 +170,29 @@ impl <'a> Request<'a> {
|
|||
}
|
||||
|
||||
/// Initiate a GET request with the given URL.
|
||||
pub fn get(url: &'a str) -> Self { Request::new(Method::Get, url) }
|
||||
pub fn get(url: &'a str) -> Self {
|
||||
Request::new(Method::Get, url)
|
||||
}
|
||||
|
||||
/// Initiate a POST request with the given URL.
|
||||
pub fn post(url: &'a str) -> Self { Request::new(Method::Post, url) }
|
||||
pub fn post(url: &'a str) -> Self {
|
||||
Request::new(Method::Post, url)
|
||||
}
|
||||
|
||||
/// Initiate a PUT request with the given URL.
|
||||
pub fn put(url: &'a str) -> Self { Request::new(Method::Put, url) }
|
||||
pub fn put(url: &'a str) -> Self {
|
||||
Request::new(Method::Put, url)
|
||||
}
|
||||
|
||||
/// Initiate a PATCH request with the given URL.
|
||||
pub fn patch(url: &'a str) -> Self { Request::new(Method::Patch, url) }
|
||||
pub fn patch(url: &'a str) -> Self {
|
||||
Request::new(Method::Patch, url)
|
||||
}
|
||||
|
||||
/// Initiate a DELETE request with the given URL.
|
||||
pub fn delete(url: &'a str) -> Self { Request::new(Method::Delete, url) }
|
||||
pub fn delete(url: &'a str) -> Self {
|
||||
Request::new(Method::Delete, url)
|
||||
}
|
||||
|
||||
/// Add an HTTP header to a request.
|
||||
pub fn header(mut self, k: &str, v: &str) -> Result<Self, curl::Error> {
|
||||
|
@ -188,7 +210,8 @@ impl <'a> Request<'a> {
|
|||
/// Set the `Authorization` header to a `Bearer` value with the
|
||||
/// supplied token.
|
||||
pub fn bearer_auth(mut self, token: &str) -> Result<Self, curl::Error> {
|
||||
self.headers.append(&format!("Authorization: Bearer {}", token))?;
|
||||
self.headers
|
||||
.append(&format!("Authorization: Bearer {}", token))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
@ -212,8 +235,11 @@ impl <'a> Request<'a> {
|
|||
/// Consult the documentation for the `ssl_cert` and `ssl_key`
|
||||
/// functions in `curl::easy::Easy2` for details on supported
|
||||
/// formats and defaults.
|
||||
pub fn tls_client_cert<P: AsRef<Path>>(mut self, cert_type: CertType, cert: P)
|
||||
-> Result<Self, curl::Error> {
|
||||
pub fn tls_client_cert<P: AsRef<Path>>(
|
||||
mut self,
|
||||
cert_type: CertType,
|
||||
cert: P,
|
||||
) -> Result<Self, curl::Error> {
|
||||
self.handle.ssl_cert(cert)?;
|
||||
self.handle.ssl_cert_type(match cert_type {
|
||||
CertType::P12 => "P12",
|
||||
|
@ -262,13 +288,17 @@ impl <'a> Request<'a> {
|
|||
/// ```
|
||||
/// # use crimp::Request;
|
||||
/// let response = Request::get("https://httpbin.org/get")
|
||||
/// .with_handle(|mut handle| handle.referer("Example-Referer")).unwrap()
|
||||
/// .send().unwrap();
|
||||
/// .with_handle(|mut handle| handle.referer("Example-Referer"))
|
||||
/// .unwrap()
|
||||
/// .send()
|
||||
/// .unwrap();
|
||||
/// #
|
||||
/// # assert!(response.is_success());
|
||||
/// ```
|
||||
pub fn with_handle<F>(mut self, function: F) -> Result<Self, curl::Error>
|
||||
where F: FnOnce(&mut Easy) -> Result<(), curl::Error> {
|
||||
where
|
||||
F: FnOnce(&mut Easy) -> Result<(), curl::Error>,
|
||||
{
|
||||
function(&mut self.handle)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -293,12 +323,15 @@ impl <'a> Request<'a> {
|
|||
/// let mut form = Form::new();
|
||||
/// form.part("some-name")
|
||||
/// .contents("some-data".as_bytes())
|
||||
/// .add().unwrap();
|
||||
/// .add()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let response = Request::post("https://httpbin.org/post")
|
||||
/// .user_agent("crimp test suite").unwrap()
|
||||
/// .user_agent("crimp test suite")
|
||||
/// .unwrap()
|
||||
/// .form(form)
|
||||
/// .send().unwrap();
|
||||
/// .send()
|
||||
/// .unwrap();
|
||||
/// #
|
||||
/// # assert_eq!(200, response.status, "form POST should succeed");
|
||||
/// # assert_eq!(
|
||||
|
@ -330,10 +363,10 @@ impl <'a> Request<'a> {
|
|||
self.handle.url(self.url)?;
|
||||
|
||||
match self.method {
|
||||
Method::Get => self.handle.get(true)?,
|
||||
Method::Post => self.handle.post(true)?,
|
||||
Method::Put => self.handle.put(true)?,
|
||||
Method::Patch => self.handle.custom_request("PATCH")?,
|
||||
Method::Get => self.handle.get(true)?,
|
||||
Method::Post => self.handle.post(true)?,
|
||||
Method::Put => self.handle.put(true)?,
|
||||
Method::Patch => self.handle.custom_request("PATCH")?,
|
||||
Method::Delete => self.handle.custom_request("DELETE")?,
|
||||
}
|
||||
|
||||
|
@ -351,21 +384,22 @@ impl <'a> Request<'a> {
|
|||
|
||||
// Optionally set content type if a body payload is configured
|
||||
// and configure the expected body size (or form payload).
|
||||
match self.body {
|
||||
match self.body {
|
||||
Body::Bytes { content_type, data } => {
|
||||
self.handle.post_field_size(data.len() as u64)?;
|
||||
self.headers.append(&format!("Content-Type: {}", content_type))?;
|
||||
},
|
||||
self.headers
|
||||
.append(&format!("Content-Type: {}", content_type))?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
Body::Json(ref data) => {
|
||||
self.handle.post_field_size(data.len() as u64)?;
|
||||
self.headers.append("Content-Type: application/json")?;
|
||||
},
|
||||
}
|
||||
|
||||
// Do not set content-type header at all if there is no
|
||||
// body, or if the form handler was invoked above.
|
||||
_ => (),
|
||||
// Do not set content-type header at all if there is no
|
||||
// body, or if the form handler was invoked above.
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// Configure headers on the request:
|
||||
|
@ -407,9 +441,7 @@ impl <'a> Request<'a> {
|
|||
return true;
|
||||
}
|
||||
|
||||
headers.insert(
|
||||
split[0].trim().to_string(), split[1].trim().to_string()
|
||||
);
|
||||
headers.insert(split[0].trim().to_string(), split[1].trim().to_string());
|
||||
true
|
||||
})?;
|
||||
|
||||
|
@ -427,7 +459,7 @@ impl <'a> Request<'a> {
|
|||
Ok(Response {
|
||||
status: self.handle.response_code()?,
|
||||
headers,
|
||||
body
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -438,13 +470,14 @@ impl <'a> Request<'a> {
|
|||
///
|
||||
/// As we manually set the expected upload size, cURL will call the
|
||||
/// read callback repeatedly until it has all the data it needs.
|
||||
fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>,
|
||||
data: &'data [u8]) -> Result<(), curl::Error> {
|
||||
fn chunked_read_function<'easy, 'data>(
|
||||
transfer: &mut Transfer<'easy, 'data>,
|
||||
data: &'data [u8],
|
||||
) -> Result<(), curl::Error> {
|
||||
let mut data = data;
|
||||
|
||||
transfer.read_function(move |mut into| {
|
||||
let written = into.write(data)
|
||||
.map_err(|_| ReadError::Abort)?;
|
||||
let written = into.write(data).map_err(|_| ReadError::Abort)?;
|
||||
|
||||
data = &data[written..];
|
||||
|
||||
|
@ -452,7 +485,7 @@ fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>,
|
|||
})
|
||||
}
|
||||
|
||||
impl <T> Response<T> {
|
||||
impl<T> Response<T> {
|
||||
/// Check whether the status code of this HTTP response is a
|
||||
/// success (i.e. in the 200-299 range).
|
||||
pub fn is_success(&self) -> bool {
|
||||
|
@ -466,9 +499,11 @@ impl <T> Response<T> {
|
|||
/// This function exists for convenience to avoid having to write
|
||||
/// repetitive `if !response.is_success() { ... }` blocks.
|
||||
pub fn error_for_status<F, E>(self, closure: F) -> Result<Self, E>
|
||||
where F: FnOnce(Self) -> E {
|
||||
where
|
||||
F: FnOnce(Self) -> E,
|
||||
{
|
||||
if !self.is_success() {
|
||||
return Err(closure(self))
|
||||
return Err(closure(self));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// docker run --rm -p 4662:80 kennethreitz/httpbin
|
||||
|
||||
use super::*;
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
// These tests check whether the correct HTTP method is used in the
|
||||
// requests.
|
||||
|
@ -14,7 +14,8 @@ use serde_json::{Value, json};
|
|||
#[test]
|
||||
fn test_http_get() {
|
||||
let resp = Request::get("http://127.0.0.1:4662/get")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(resp.is_success(), "request should have succeeded");
|
||||
}
|
||||
|
@ -22,7 +23,8 @@ fn test_http_get() {
|
|||
#[test]
|
||||
fn test_http_delete() {
|
||||
let resp = Request::delete("http://127.0.0.1:4662/delete")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -30,7 +32,8 @@ fn test_http_delete() {
|
|||
#[test]
|
||||
fn test_http_put() {
|
||||
let resp = Request::put("http://127.0.0.1:4662/put")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -38,7 +41,8 @@ fn test_http_put() {
|
|||
#[test]
|
||||
fn test_http_patch() {
|
||||
let resp = Request::patch("http://127.0.0.1:4662/patch")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -50,18 +54,25 @@ fn test_http_patch() {
|
|||
fn test_http_post() {
|
||||
let body = "test body";
|
||||
let response = Request::post("http://127.0.0.1:4662/post")
|
||||
.user_agent("crimp test suite").expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5)).expect("failed to set request timeout")
|
||||
.user_agent("crimp test suite")
|
||||
.expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5))
|
||||
.expect("failed to set request timeout")
|
||||
.body("text/plain", &body.as_bytes())
|
||||
.send().expect("failed to send request")
|
||||
.as_json::<Value>().expect("failed to deserialize response");
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.as_json::<Value>()
|
||||
.expect("failed to deserialize response");
|
||||
|
||||
let data = response.body;
|
||||
|
||||
assert_eq!(200, response.status, "response status should be 200 OK");
|
||||
|
||||
assert_eq!(data.get("data").unwrap(), &json!("test body"),
|
||||
"test body should have been POSTed");
|
||||
assert_eq!(
|
||||
data.get("data").unwrap(),
|
||||
&json!("test body"),
|
||||
"test body should have been POSTed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
data.get("headers").unwrap().get("Content-Type").unwrap(),
|
||||
|
@ -70,26 +81,34 @@ fn test_http_post() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")] #[test]
|
||||
#[cfg(feature = "json")]
|
||||
#[test]
|
||||
fn test_http_post_json() {
|
||||
let body = json!({
|
||||
"purpose": "testing!"
|
||||
});
|
||||
|
||||
let response = Request::post("http://127.0.0.1:4662/post")
|
||||
.user_agent("crimp test suite").expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5)).expect("failed to set request timeout")
|
||||
.json(&body).expect("request serialization failed")
|
||||
.send().expect("failed to send request")
|
||||
.as_json::<Value>().expect("failed to deserialize response");
|
||||
|
||||
.user_agent("crimp test suite")
|
||||
.expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5))
|
||||
.expect("failed to set request timeout")
|
||||
.json(&body)
|
||||
.expect("request serialization failed")
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.as_json::<Value>()
|
||||
.expect("failed to deserialize response");
|
||||
|
||||
let data = response.body;
|
||||
|
||||
assert_eq!(200, response.status, "response status should be 200 OK");
|
||||
|
||||
assert_eq!(data.get("json").unwrap(), &body,
|
||||
"test body should have been POSTed");
|
||||
assert_eq!(
|
||||
data.get("json").unwrap(),
|
||||
&body,
|
||||
"test body should have been POSTed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
data.get("headers").unwrap().get("Content-Type").unwrap(),
|
||||
|
@ -104,8 +123,10 @@ fn test_http_post_json() {
|
|||
#[test]
|
||||
fn test_bearer_auth() {
|
||||
let response = Request::get("http://127.0.0.1:4662/bearer")
|
||||
.bearer_auth("some-token").expect("failed to set auth header")
|
||||
.send().expect("failed to send request");
|
||||
.bearer_auth("some-token")
|
||||
.expect("failed to set auth header")
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(response.is_success(), "authorized request should succeed");
|
||||
}
|
||||
|
@ -115,8 +136,10 @@ fn test_basic_auth() {
|
|||
let request = Request::get("http://127.0.0.1:4662/basic-auth/alan_watts/oneness");
|
||||
|
||||
let response = request
|
||||
.basic_auth("alan_watts", "oneness").expect("failed to set auth header")
|
||||
.send().expect("failed to send request");
|
||||
.basic_auth("alan_watts", "oneness")
|
||||
.expect("failed to set auth header")
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(response.is_success(), "authorized request should succeed");
|
||||
}
|
||||
|
@ -129,14 +152,20 @@ fn test_large_body() {
|
|||
|
||||
let resp = Request::post("http://127.0.0.1:4662/post")
|
||||
.body("application/octet-stream", &[0; BODY_SIZE])
|
||||
.send().expect("sending request")
|
||||
.as_json::<Value>().expect("JSON deserialisation");
|
||||
.send()
|
||||
.expect("sending request")
|
||||
.as_json::<Value>()
|
||||
.expect("JSON deserialisation");
|
||||
|
||||
// httpbin returns the uploaded data as a string in the `data`
|
||||
// field.
|
||||
let data = resp.body.get("data").unwrap().as_str().unwrap();
|
||||
|
||||
assert_eq!(BODY_SIZE, data.len(), "uploaded data length should be correct");
|
||||
assert_eq!(
|
||||
BODY_SIZE,
|
||||
data.len(),
|
||||
"uploaded data length should be correct"
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for various other features.
|
||||
|
@ -144,9 +173,13 @@ fn test_large_body() {
|
|||
#[test]
|
||||
fn test_error_for_status() {
|
||||
let response = Request::get("http://127.0.0.1:4662/patch")
|
||||
.send().expect("failed to send request")
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.error_for_status(|resp| format!("Response error code: {}", resp.status));
|
||||
|
||||
assert_eq!(Err("Response error code: 405".into()), response,
|
||||
"returned error should be converted into Result::Err");
|
||||
assert_eq!(
|
||||
Err("Response error code: 405".into()),
|
||||
response,
|
||||
"returned error should be converted into Result::Err"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
extern crate serde_json;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||
use std::io::{Error, ErrorKind, Write, stdout, stderr};
|
||||
use std::io::{stderr, stdout, Error, ErrorKind, Write};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::process::Command;
|
||||
use std::convert::{TryFrom};
|
||||
|
||||
fn render_nix_string(s: &OsString) -> OsString {
|
||||
let mut rendered = Vec::new();
|
||||
|
@ -16,8 +16,8 @@ fn render_nix_string(s: &OsString) -> OsString {
|
|||
match char::from(*b) {
|
||||
'\"' => rendered.extend(b"\\\""),
|
||||
'\\' => rendered.extend(b"\\\\"),
|
||||
'$' => rendered.extend(b"\\$"),
|
||||
_ => rendered.push(*b),
|
||||
'$' => rendered.extend(b"\\$"),
|
||||
_ => rendered.push(*b),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,17 +48,14 @@ fn render_nix_list(arr: &[OsString]) -> OsString {
|
|||
macro_rules! handle_set_output {
|
||||
($map_name:ident, $output_name:ident) => {
|
||||
match $map_name.get(stringify!($output_name)) {
|
||||
Some(Value::String(s)) =>
|
||||
$output_name().write_all(s.as_bytes()),
|
||||
Some(_) => Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Attribute {} must be a string!", stringify!($output_name)),
|
||||
)
|
||||
),
|
||||
Some(Value::String(s)) => $output_name().write_all(s.as_bytes()),
|
||||
Some(_) => Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Attribute {} must be a string!", stringify!($output_name)),
|
||||
)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
|
@ -83,7 +80,7 @@ fn main() -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
if in_args {
|
||||
match(arg.to_str()) {
|
||||
match (arg.to_str()) {
|
||||
Some("--arg") | Some("--argstr") => {
|
||||
nix_args.push(arg);
|
||||
nix_args.push(args.next().unwrap());
|
||||
|
@ -116,9 +113,7 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
nix_args.push(argv[0].clone());
|
||||
|
||||
let run = Command::new("nix-instantiate")
|
||||
.args(nix_args)
|
||||
.output()?;
|
||||
let run = Command::new("nix-instantiate").args(nix_args).output()?;
|
||||
|
||||
match serde_json::from_slice(&run.stdout[..]) {
|
||||
Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
|
||||
|
@ -132,25 +127,23 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
match code {
|
||||
Some(i) => std::process::exit(i),
|
||||
None => Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
"Attribute exit is not an i32"
|
||||
)
|
||||
),
|
||||
None => {
|
||||
Err(Error::new(ErrorKind::Other, "Attribute exit is not an i32"))
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(_) => Err(
|
||||
Error::new(ErrorKind::Other, "exit must be a number")
|
||||
),
|
||||
}
|
||||
Some(_) => Err(Error::new(ErrorKind::Other, "exit must be a number")),
|
||||
None => Ok(()),
|
||||
}
|
||||
},
|
||||
Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string or an object")),
|
||||
}
|
||||
Ok(_) => Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"output must be a string or an object",
|
||||
)),
|
||||
_ => {
|
||||
stderr().write_all(&run.stderr[..]);
|
||||
Err(Error::new(ErrorKind::Other, "internal nix error"))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
extern crate pkg_config;
|
||||
|
||||
fn main() {
|
||||
pkg_config::probe_library("libsystemd")
|
||||
.expect("Could not probe libsystemd");
|
||||
pkg_config::probe_library("libsystemd").expect("Could not probe libsystemd");
|
||||
}
|
||||
|
|
|
@ -31,11 +31,16 @@
|
|||
//! `GOOGLE_APPLICATION_CREDENTIALS`, `GOOGLE_CLOUD_PROJECT` and
|
||||
//! `LOG_NAME` environment variables.
|
||||
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use] extern crate serde_json;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate chrono;
|
||||
extern crate env_logger;
|
||||
|
@ -48,13 +53,11 @@ use chrono::offset::LocalResult;
|
|||
use chrono::prelude::{DateTime, TimeZone, Utc};
|
||||
use failure::ResultExt;
|
||||
use serde_json::{from_str, Value};
|
||||
use std::env;
|
||||
use std::fs::{self, File, rename};
|
||||
use std::io::{self, Read, ErrorKind, Write};
|
||||
use std::mem;
|
||||
use std::fs::{self, rename, File};
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, mem, process};
|
||||
use systemd::journal::{Journal, JournalFiles, JournalRecord, JournalSeek};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -62,10 +65,12 @@ mod tests;
|
|||
|
||||
const LOGGING_SERVICE: &str = "https://logging.googleapis.com/google.logging.v2.LoggingServiceV2";
|
||||
const ENTRIES_WRITE_URL: &str = "https://logging.googleapis.com/v2/entries:write";
|
||||
const METADATA_TOKEN_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
||||
const METADATA_TOKEN_URL: &str =
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
||||
const METADATA_ID_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/id";
|
||||
const METADATA_ZONE_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/zone";
|
||||
const METADATA_PROJECT_URL: &str = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
|
||||
const METADATA_PROJECT_URL: &str =
|
||||
"http://metadata.google.internal/computeMetadata/v1/project/project-id";
|
||||
|
||||
/// Convenience type alias for results using failure's `Error` type.
|
||||
type Result<T> = std::result::Result<T, failure::Error>;
|
||||
|
@ -134,14 +139,17 @@ fn get_metadata(url: &str) -> Result<String> {
|
|||
|
||||
if response.ok() {
|
||||
// Whitespace is trimmed to remove newlines from responses.
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.context("Failed to decode metadata response")?
|
||||
.trim().to_string();
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(body)
|
||||
} else {
|
||||
let status = response.status_line().to_string();
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.unwrap_or_else(|e| format!("Metadata body error: {}", e));
|
||||
bail!("Metadata failure: {} ({})", body, status)
|
||||
}
|
||||
|
@ -186,11 +194,9 @@ fn determine_monitored_resource() -> Value {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
let instance_id = get_metadata(METADATA_ID_URL)
|
||||
.expect("Could not determine instance ID");
|
||||
let instance_id = get_metadata(METADATA_ID_URL).expect("Could not determine instance ID");
|
||||
|
||||
let zone = get_metadata(METADATA_ZONE_URL)
|
||||
.expect("Could not determine instance zone");
|
||||
let zone = get_metadata(METADATA_ZONE_URL).expect("Could not determine instance zone");
|
||||
|
||||
json!({
|
||||
"type": "gce_instance",
|
||||
|
@ -253,7 +259,8 @@ fn sign_service_account_token(credentials: &Credentials) -> Result<Token> {
|
|||
use medallion::{Algorithm, Header, Payload};
|
||||
|
||||
let iat = Utc::now();
|
||||
let exp = iat.checked_add_signed(chrono::Duration::seconds(3600))
|
||||
let exp = iat
|
||||
.checked_add_signed(chrono::Duration::seconds(3600))
|
||||
.ok_or_else(|| format_err!("Failed to calculate token expiry"))?;
|
||||
|
||||
let header = Header {
|
||||
|
@ -323,7 +330,9 @@ enum Payload {
|
|||
/// text format.
|
||||
fn message_to_payload(message: Option<String>) -> Payload {
|
||||
match message {
|
||||
None => Payload::TextPayload { text_payload: "empty log entry".into() },
|
||||
None => Payload::TextPayload {
|
||||
text_payload: "empty log entry".into(),
|
||||
},
|
||||
Some(text_payload) => {
|
||||
// Attempt to deserialize the text payload as a generic
|
||||
// JSON value.
|
||||
|
@ -333,7 +342,7 @@ fn message_to_payload(message: Option<String>) -> Payload {
|
|||
// expect other types of JSON payload) and return it
|
||||
// in that case.
|
||||
if json_payload.is_object() {
|
||||
return Payload::JsonPayload { json_payload }
|
||||
return Payload::JsonPayload { json_payload };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,9 +459,7 @@ impl From<JournalRecord> for LogEntry {
|
|||
// Journald uses syslogd's concept of priority. No idea if this is
|
||||
// always present, but it's optional in the Stackdriver API, so we just
|
||||
// omit it if we can't find or parse it.
|
||||
let severity = record
|
||||
.remove("PRIORITY")
|
||||
.and_then(priority_to_severity);
|
||||
let severity = record.remove("PRIORITY").and_then(priority_to_severity);
|
||||
|
||||
LogEntry {
|
||||
payload,
|
||||
|
@ -468,8 +475,7 @@ impl From<JournalRecord> for LogEntry {
|
|||
|
||||
/// Attempt to read from the journal. If no new entry is present,
|
||||
/// await the next one up to the specified timeout.
|
||||
fn receive_next_record(timeout: Duration, journal: &mut Journal)
|
||||
-> Result<Option<JournalRecord>> {
|
||||
fn receive_next_record(timeout: Duration, journal: &mut Journal) -> Result<Option<JournalRecord>> {
|
||||
let next_record = journal.next_record()?;
|
||||
if next_record.is_some() {
|
||||
return Ok(next_record);
|
||||
|
@ -525,11 +531,10 @@ fn persist_cursor(cursor: String) -> Result<()> {
|
|||
if cursor.is_empty() {
|
||||
error!("Received empty journald cursor position, refusing to persist!");
|
||||
error!("Please report this message at https://github.com/tazjin/journaldriver/issues/2");
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut file = File::create(&*CURSOR_TMP_FILE)
|
||||
.context("Failed to create cursor file")?;
|
||||
let mut file = File::create(&*CURSOR_TMP_FILE).context("Failed to create cursor file")?;
|
||||
|
||||
write!(file, "{}", cursor).context("Failed to write cursor file")?;
|
||||
|
||||
|
@ -547,9 +552,7 @@ fn persist_cursor(cursor: String) -> Result<()> {
|
|||
///
|
||||
/// If flushing is successful the last cursor position will be
|
||||
/// persisted to disk.
|
||||
fn flush(token: &mut Token,
|
||||
entries: Vec<LogEntry>,
|
||||
cursor: String) -> Result<()> {
|
||||
fn flush(token: &mut Token, entries: Vec<LogEntry>, cursor: String) -> Result<()> {
|
||||
if token.is_expired() {
|
||||
debug!("Refreshing Google metadata access token");
|
||||
let new_token = get_token()?;
|
||||
|
@ -598,7 +601,8 @@ fn write_entries(token: &Token, request: Value) -> Result<()> {
|
|||
Ok(())
|
||||
} else {
|
||||
let status = response.status_line().to_string();
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "no response body".into());
|
||||
bail!("Write failure: {} ({})", body, status)
|
||||
}
|
||||
|
@ -624,14 +628,12 @@ fn initial_cursor() -> Result<JournalSeek> {
|
|||
Err(ref err) if err.kind() == ErrorKind::NotFound => {
|
||||
info!("No previous cursor position, reading from journal tail");
|
||||
Ok(JournalSeek::Tail)
|
||||
},
|
||||
Err(err) => {
|
||||
(Err(err).context("Could not read cursor position"))?
|
||||
}
|
||||
Err(err) => (Err(err).context("Could not read cursor position"))?,
|
||||
}
|
||||
}
|
||||
|
||||
fn main () {
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// The directory in which cursor positions are persisted should
|
||||
|
@ -641,17 +643,17 @@ fn main () {
|
|||
process::exit(1);
|
||||
}
|
||||
|
||||
let cursor_position_dir = CURSOR_FILE.parent()
|
||||
let cursor_position_dir = CURSOR_FILE
|
||||
.parent()
|
||||
.expect("Invalid cursor position file path");
|
||||
|
||||
fs::create_dir_all(cursor_position_dir)
|
||||
.expect("Could not create directory to store cursor position in");
|
||||
|
||||
let mut journal = Journal::open(JournalFiles::All, false, true)
|
||||
.expect("Failed to open systemd journal");
|
||||
let mut journal =
|
||||
Journal::open(JournalFiles::All, false, true).expect("Failed to open systemd journal");
|
||||
|
||||
let seek_position = initial_cursor()
|
||||
.expect("Failed to determine initial cursor position");
|
||||
let seek_position = initial_cursor().expect("Failed to determine initial cursor position");
|
||||
|
||||
match journal.seek(seek_position) {
|
||||
Ok(cursor) => info!("Opened journal at cursor '{}'", cursor),
|
||||
|
|
|
@ -15,7 +15,10 @@ fn test_text_entry_serialization() {
|
|||
let expected = "{\"labels\":null,\"textPayload\":\"test entry\"}";
|
||||
let result = to_string(&entry).expect("serialization failed");
|
||||
|
||||
assert_eq!(expected, result, "Plain text payload should serialize correctly")
|
||||
assert_eq!(
|
||||
expected, result,
|
||||
"Plain text payload should serialize correctly"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -26,7 +29,7 @@ fn test_json_entry_serialization() {
|
|||
payload: Payload::JsonPayload {
|
||||
json_payload: json!({
|
||||
"message": "JSON test"
|
||||
})
|
||||
}),
|
||||
},
|
||||
severity: None,
|
||||
};
|
||||
|
@ -45,7 +48,10 @@ fn test_plain_text_payload() {
|
|||
text_payload: "plain text payload".into(),
|
||||
};
|
||||
|
||||
assert_eq!(expected, payload, "Plain text payload should be detected correctly");
|
||||
assert_eq!(
|
||||
expected, payload,
|
||||
"Plain text payload should be detected correctly"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -55,7 +61,10 @@ fn test_empty_payload() {
|
|||
text_payload: "empty log entry".into(),
|
||||
};
|
||||
|
||||
assert_eq!(expected, payload, "Empty payload should be handled correctly");
|
||||
assert_eq!(
|
||||
expected, payload,
|
||||
"Empty payload should be handled correctly"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -66,10 +75,13 @@ fn test_json_payload() {
|
|||
json_payload: json!({
|
||||
"someKey": "someValue",
|
||||
"otherKey": 42
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(expected, payload, "JSON payload should be detected correctly");
|
||||
assert_eq!(
|
||||
expected, payload,
|
||||
"JSON payload should be detected correctly"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -82,14 +94,16 @@ fn test_json_no_object() {
|
|||
text_payload: "42".into(),
|
||||
};
|
||||
|
||||
assert_eq!(expected, payload, "Non-object JSON payload should be plain text");
|
||||
assert_eq!(
|
||||
expected, payload,
|
||||
"Non-object JSON payload should be plain text"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_microseconds() {
|
||||
let input: String = "1529175149291187".into();
|
||||
let expected: DateTime<Utc> = "2018-06-16T18:52:29.291187Z"
|
||||
.to_string().parse().unwrap();
|
||||
let expected: DateTime<Utc> = "2018-06-16T18:52:29.291187Z".to_string().parse().unwrap();
|
||||
|
||||
assert_eq!(Some(expected), parse_microseconds(input));
|
||||
}
|
||||
|
|
|
@ -1,36 +1,38 @@
|
|||
extern crate clap;
|
||||
extern crate posix_mq;
|
||||
extern crate libc;
|
||||
extern crate nix;
|
||||
extern crate posix_mq;
|
||||
|
||||
use clap::{App, SubCommand, Arg, ArgMatches, AppSettings};
|
||||
use posix_mq::{Name, Queue, Message};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use posix_mq::{Message, Name, Queue};
|
||||
use std::fs::{read_dir, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::exit;
|
||||
|
||||
fn run_ls() {
|
||||
let mqueues = read_dir("/dev/mqueue")
|
||||
.expect("Could not read message queues");
|
||||
let mqueues = read_dir("/dev/mqueue").expect("Could not read message queues");
|
||||
|
||||
for queue in mqueues {
|
||||
let path = queue.unwrap().path();
|
||||
let status = {
|
||||
let mut file = File::open(&path)
|
||||
.expect("Could not open queue file");
|
||||
let mut file = File::open(&path).expect("Could not open queue file");
|
||||
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content).expect("Could not read queue file");
|
||||
file.read_to_string(&mut content)
|
||||
.expect("Could not read queue file");
|
||||
|
||||
content
|
||||
};
|
||||
|
||||
let queue_name = path.components().last().unwrap()
|
||||
let queue_name = path
|
||||
.components()
|
||||
.last()
|
||||
.unwrap()
|
||||
.as_os_str()
|
||||
.to_string_lossy();
|
||||
|
||||
println!("/{}: {}", queue_name, status)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn run_inspect(queue_name: &str) {
|
||||
|
@ -47,8 +49,7 @@ fn run_create(cmd: &ArgMatches) {
|
|||
set_rlimit(rlimit.parse().expect("Invalid rlimit value"));
|
||||
}
|
||||
|
||||
let name = Name::new(cmd.value_of("queue").unwrap())
|
||||
.expect("Invalid queue name");
|
||||
let name = Name::new(cmd.value_of("queue").unwrap()).expect("Invalid queue name");
|
||||
|
||||
let max_pending: i64 = cmd.value_of("max-pending").unwrap().parse().unwrap();
|
||||
let max_size: i64 = cmd.value_of("max-size").unwrap().parse().unwrap();
|
||||
|
@ -56,11 +57,11 @@ fn run_create(cmd: &ArgMatches) {
|
|||
let queue = Queue::create(name, max_pending, max_size * 1024);
|
||||
|
||||
match queue {
|
||||
Ok(_) => println!("Queue created successfully"),
|
||||
Ok(_) => println!("Queue created successfully"),
|
||||
Err(e) => {
|
||||
writeln!(io::stderr(), "Could not create queue: {}", e).ok();
|
||||
exit(1);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -120,7 +121,12 @@ fn run_rlimit() {
|
|||
};
|
||||
|
||||
if errno != 0 {
|
||||
writeln!(io::stderr(), "Could not get message queue rlimit: {}", errno).ok();
|
||||
writeln!(
|
||||
io::stderr(),
|
||||
"Could not get message queue rlimit: {}",
|
||||
errno
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
println!("Message queue rlimit:");
|
||||
println!("Current limit: {}", rlimit.rlim_cur);
|
||||
|
@ -170,16 +176,20 @@ fn main() {
|
|||
.about("Create a new queue")
|
||||
.arg(&queue_arg)
|
||||
.arg(&rlimit_arg)
|
||||
.arg(Arg::with_name("max-size")
|
||||
.help("maximum message size (in kB)")
|
||||
.long("max-size")
|
||||
.required(true)
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("max-pending")
|
||||
.help("maximum # of pending messages")
|
||||
.long("max-pending")
|
||||
.required(true)
|
||||
.takes_value(true));
|
||||
.arg(
|
||||
Arg::with_name("max-size")
|
||||
.help("maximum message size (in kB)")
|
||||
.long("max-size")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-pending")
|
||||
.help("maximum # of pending messages")
|
||||
.long("max-pending")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
);
|
||||
|
||||
let receive = SubCommand::with_name("receive")
|
||||
.about("Receive a message from a queue")
|
||||
|
@ -188,9 +198,11 @@ fn main() {
|
|||
let send = SubCommand::with_name("send")
|
||||
.about("Send a message to a queue")
|
||||
.arg(&queue_arg)
|
||||
.arg(Arg::with_name("message")
|
||||
.help("the message to send")
|
||||
.required(true));
|
||||
.arg(
|
||||
Arg::with_name("message")
|
||||
.help("the message to send")
|
||||
.required(true),
|
||||
);
|
||||
|
||||
let rlimit = SubCommand::with_name("rlimit")
|
||||
.about("Get the message queue rlimit")
|
||||
|
@ -211,13 +223,13 @@ fn main() {
|
|||
match matches.subcommand() {
|
||||
("ls", _) => run_ls(),
|
||||
("inspect", Some(cmd)) => run_inspect(cmd.value_of("queue").unwrap()),
|
||||
("create", Some(cmd)) => run_create(cmd),
|
||||
("create", Some(cmd)) => run_create(cmd),
|
||||
("receive", Some(cmd)) => run_receive(cmd.value_of("queue").unwrap()),
|
||||
("send", Some(cmd)) => run_send(
|
||||
("send", Some(cmd)) => run_send(
|
||||
cmd.value_of("queue").unwrap(),
|
||||
cmd.value_of("message").unwrap()
|
||||
cmd.value_of("message").unwrap(),
|
||||
),
|
||||
("rlimit", _) => run_rlimit(),
|
||||
("rlimit", _) => run_rlimit(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use nix;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::num;
|
||||
use std::{error, fmt, io, num};
|
||||
|
||||
/// This module implements a simple error type to match the errors that can be thrown from the C
|
||||
/// functions as well as some extra errors resulting from internal validations.
|
||||
|
|
|
@ -4,8 +4,7 @@ use super::*;
|
|||
fn test_open_delete() {
|
||||
// Simple test with default queue settings
|
||||
let name = Name::new("/test-queue").unwrap();
|
||||
let queue = Queue::open_or_create(name)
|
||||
.expect("Opening queue failed");
|
||||
let queue = Queue::open_or_create(name).expect("Opening queue failed");
|
||||
|
||||
let message = Message {
|
||||
data: "test-message".as_bytes().to_vec(),
|
||||
|
|
|
@ -28,14 +28,19 @@ fn main() {
|
|||
|
||||
// Otherwise ask Nix to build it and inject the result.
|
||||
let output = Command::new("nix-build")
|
||||
.arg("-A").arg("third_party.bat_syntaxes")
|
||||
.arg("-A")
|
||||
.arg("third_party.bat_syntaxes")
|
||||
// ... assuming cheddar is at //tools/cheddar ...
|
||||
.arg("../..")
|
||||
.output()
|
||||
.expect(ERROR_MESSAGE);
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("{}\nNix output: {}", ERROR_MESSAGE, String::from_utf8_lossy(&output.stderr));
|
||||
eprintln!(
|
||||
"{}\nNix output: {}",
|
||||
ERROR_MESSAGE,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
//! 2. As a long-running HTTP server that handles rendering requests
|
||||
//! (matching the SourceGraph protocol).
|
||||
use clap::{App, Arg};
|
||||
use rouille::Response;
|
||||
use rouille::{router, try_or_400};
|
||||
use rouille::{router, try_or_400, Response};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use cheddar::{THEMES, format_code, format_markdown};
|
||||
use cheddar::{format_code, format_markdown, THEMES};
|
||||
|
||||
// Server endpoint for rendering the syntax of source code. This
|
||||
// replaces the 'syntect_server' component of Sourcegraph.
|
||||
|
|
|
@ -8,12 +8,10 @@ use lazy_static::lazy_static;
|
|||
use regex::Regex;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::io::BufRead;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
use std::path::Path;
|
||||
use std::{env, io};
|
||||
use syntect::dumps::from_binary;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{Theme, ThemeSet};
|
||||
|
|
|
@ -14,42 +14,89 @@ use std::io::Write;
|
|||
fn main() {
|
||||
let mut args = std::env::args_os();
|
||||
let file = args.nth(1).expect("security advisory md file is $1");
|
||||
let crate_version =
|
||||
args.nth(0).expect("crate version is $2")
|
||||
.into_string().expect("crate version string not utf8")
|
||||
;
|
||||
let crate_version = semver::Version::parse(&crate_version).expect(&format!("this is not a semver version: {}", &crate_version));
|
||||
let crate_version = args
|
||||
.nth(0)
|
||||
.expect("crate version is $2")
|
||||
.into_string()
|
||||
.expect("crate version string not utf8");
|
||||
let crate_version = semver::Version::parse(&crate_version)
|
||||
.expect(&format!("this is not a semver version: {}", &crate_version));
|
||||
let filename = file.to_string_lossy();
|
||||
|
||||
let content = std::fs::read(&file).expect(&format!("could not read {}", filename));
|
||||
let content =
|
||||
std::str::from_utf8(&content).expect(&format!("file {} was not encoded as utf-8", filename));
|
||||
let content = std::str::from_utf8(&content)
|
||||
.expect(&format!("file {} was not encoded as utf-8", filename));
|
||||
let content = content.trim_start();
|
||||
|
||||
let toml_start = content
|
||||
.strip_prefix("```toml").expect(&format!("file did not start with ```toml: {}", filename));
|
||||
let toml_end_index = toml_start.find("```").expect(&format!("the toml section did not end, no `` found: {}", filename));
|
||||
.strip_prefix("```toml")
|
||||
.expect(&format!("file did not start with ```toml: {}", filename));
|
||||
let toml_end_index = toml_start.find("```").expect(&format!(
|
||||
"the toml section did not end, no `` found: {}",
|
||||
filename
|
||||
));
|
||||
let toml = &toml_start[..toml_end_index];
|
||||
let toml : toml::Value = toml::de::from_slice(toml.as_bytes()).expect(&format!("could not parse toml: {}", filename));
|
||||
let toml: toml::Value = toml::de::from_slice(toml.as_bytes())
|
||||
.expect(&format!("could not parse toml: {}", filename));
|
||||
|
||||
let versions = toml
|
||||
.as_table().expect(&format!("the toml is not a table: {}", filename))
|
||||
.get("versions").expect(&format!("the toml does not contain the versions field: {}", filename))
|
||||
.as_table().expect(&format!("the toml versions field must be a table: {}", filename));
|
||||
.as_table()
|
||||
.expect(&format!("the toml is not a table: {}", filename))
|
||||
.get("versions")
|
||||
.expect(&format!(
|
||||
"the toml does not contain the versions field: {}",
|
||||
filename
|
||||
))
|
||||
.as_table()
|
||||
.expect(&format!(
|
||||
"the toml versions field must be a table: {}",
|
||||
filename
|
||||
));
|
||||
|
||||
let unaffected = match versions.get("unaffected") {
|
||||
Some(u) => u
|
||||
.as_array().expect(&format!("the toml versions.unaffected field must be a list of semvers: {}", filename))
|
||||
.as_array()
|
||||
.expect(&format!(
|
||||
"the toml versions.unaffected field must be a list of semvers: {}",
|
||||
filename
|
||||
))
|
||||
.iter()
|
||||
.map(|v| semver::VersionReq::parse(v.as_str().expect(&format!("the version field {} is not a string", v))).expect(&format!("the version field {} is not a valid semver VersionReq", v)))
|
||||
.map(|v| {
|
||||
semver::VersionReq::parse(
|
||||
v.as_str()
|
||||
.expect(&format!("the version field {} is not a string", v)),
|
||||
)
|
||||
.expect(&format!(
|
||||
"the version field {} is not a valid semver VersionReq",
|
||||
v
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
None => vec![]
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let mut patched : Vec<semver::VersionReq> = versions.get("patched").expect(&format!("the toml versions.patched field must exist: {}", filename))
|
||||
.as_array().expect(&format!("the toml versions.patched field must be a list of semvers: {}", filename))
|
||||
let mut patched: Vec<semver::VersionReq> = versions
|
||||
.get("patched")
|
||||
.expect(&format!(
|
||||
"the toml versions.patched field must exist: {}",
|
||||
filename
|
||||
))
|
||||
.as_array()
|
||||
.expect(&format!(
|
||||
"the toml versions.patched field must be a list of semvers: {}",
|
||||
filename
|
||||
))
|
||||
.iter()
|
||||
.map(|v| semver::VersionReq::parse(v.as_str().expect(&format!("the version field {} is not a string", v))).expect(&format!("the version field {} is not a valid semver VersionReq", v)))
|
||||
.map(|v| {
|
||||
semver::VersionReq::parse(
|
||||
v.as_str()
|
||||
.expect(&format!("the version field {} is not a string", v)),
|
||||
)
|
||||
.expect(&format!(
|
||||
"the version field {} is not a valid semver VersionReq",
|
||||
v
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
patched.extend_from_slice(&unaffected[..]);
|
||||
|
@ -59,9 +106,14 @@ fn main() {
|
|||
std::process::exit(0);
|
||||
} else {
|
||||
if std::env::var_os("PRINT_ADVISORY").is_some() {
|
||||
write!(std::io::stderr(), "Advisory {} matched!\n{}\n", filename, content).unwrap();
|
||||
write!(
|
||||
std::io::stderr(),
|
||||
"Advisory {} matched!\n{}\n",
|
||||
filename,
|
||||
content
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ pub enum NixResult {
|
|||
|
||||
#[cfg(test)]
|
||||
mod integration_tests {
|
||||
use std::{collections::VecDeque, io::Write};
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Write;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use std::os::unix::process::CommandExt;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
pub fn no_args(current_prog_name: &str) -> () {
|
||||
let mut args = std::env::args_os();
|
||||
// remove argv[0]
|
||||
let _ = args.nth(0);
|
||||
if args.len() > 0 {
|
||||
die_user_error(current_prog_name, format!("Expected no arguments, got {:?}", args.collect::<Vec<_>>()))
|
||||
die_user_error(
|
||||
current_prog_name,
|
||||
format!("Expected no arguments, got {:?}", args.collect::<Vec<_>>()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,31 +19,46 @@ pub fn args(current_prog_name: &str, no_of_positional_args: usize) -> Vec<Vec<u8
|
|||
// remove argv[0]
|
||||
let _ = args.nth(0);
|
||||
if args.len() != no_of_positional_args {
|
||||
die_user_error(current_prog_name, format!("Expected {} arguments, got {}, namely {:?}", no_of_positional_args, args.len(), args.collect::<Vec<_>>()))
|
||||
die_user_error(
|
||||
current_prog_name,
|
||||
format!(
|
||||
"Expected {} arguments, got {}, namely {:?}",
|
||||
no_of_positional_args,
|
||||
args.len(),
|
||||
args.collect::<Vec<_>>()
|
||||
),
|
||||
)
|
||||
}
|
||||
args.map(|arg| arg.into_vec()).collect()
|
||||
}
|
||||
|
||||
pub fn args_for_exec(current_prog_name: &str, no_of_positional_args: usize) -> (Vec<Vec<u8>>, Vec<Vec<u8>>) {
|
||||
pub fn args_for_exec(
|
||||
current_prog_name: &str,
|
||||
no_of_positional_args: usize,
|
||||
) -> (Vec<Vec<u8>>, Vec<Vec<u8>>) {
|
||||
let mut args = std::env::args_os();
|
||||
// remove argv[0]
|
||||
let _ = args.nth(0);
|
||||
let mut args = args.map(|arg| arg.into_vec());
|
||||
let mut pos_args = vec![];
|
||||
// get positional args
|
||||
for i in 1..no_of_positional_args+1 {
|
||||
pos_args.push(
|
||||
args.nth(0).expect(
|
||||
&format!("{}: expects {} positional args, only got {}", current_prog_name, no_of_positional_args, i))
|
||||
);
|
||||
for i in 1..no_of_positional_args + 1 {
|
||||
pos_args.push(args.nth(0).expect(&format!(
|
||||
"{}: expects {} positional args, only got {}",
|
||||
current_prog_name, no_of_positional_args, i
|
||||
)));
|
||||
}
|
||||
// prog... is the rest of the iterator
|
||||
let prog : Vec<Vec<u8>> = args.collect();
|
||||
let prog: Vec<Vec<u8>> = args.collect();
|
||||
(pos_args, prog)
|
||||
}
|
||||
|
||||
pub fn exec_into_args<'a, 'b, Args, Arg, Env, Key, Val>(current_prog_name: &str, args: Args, env_additions: Env) -> !
|
||||
where
|
||||
pub fn exec_into_args<'a, 'b, Args, Arg, Env, Key, Val>(
|
||||
current_prog_name: &str,
|
||||
args: Args,
|
||||
env_additions: Env,
|
||||
) -> !
|
||||
where
|
||||
Args: IntoIterator<Item = Arg>,
|
||||
Arg: AsRef<[u8]>,
|
||||
Env: IntoIterator<Item = (Key, Val)>,
|
||||
|
@ -50,27 +68,40 @@ pub fn exec_into_args<'a, 'b, Args, Arg, Env, Key, Val>(current_prog_name: &str,
|
|||
// TODO: is this possible without collecting into a Vec first, just leaving it an IntoIterator?
|
||||
let args = args.into_iter().collect::<Vec<Arg>>();
|
||||
let mut args = args.iter().map(|v| OsStr::from_bytes(v.as_ref()));
|
||||
let prog = args.nth(0).expect(&format!("{}: first argument must be an executable", current_prog_name));
|
||||
let prog = args.nth(0).expect(&format!(
|
||||
"{}: first argument must be an executable",
|
||||
current_prog_name
|
||||
));
|
||||
// TODO: same here
|
||||
let env = env_additions.into_iter().collect::<Vec<(Key, Val)>>();
|
||||
let env = env.iter().map(|(k,v)| (OsStr::from_bytes(k.as_ref()), OsStr::from_bytes(v.as_ref())));
|
||||
let env = env
|
||||
.iter()
|
||||
.map(|(k, v)| (OsStr::from_bytes(k.as_ref()), OsStr::from_bytes(v.as_ref())));
|
||||
let err = std::process::Command::new(prog).args(args).envs(env).exec();
|
||||
die_missing_executable(current_prog_name, format!("exec failed: {}, while trying to execing into {:?}", err, prog));
|
||||
die_missing_executable(
|
||||
current_prog_name,
|
||||
format!(
|
||||
"exec failed: {}, while trying to execing into {:?}",
|
||||
err, prog
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Exit 1 to signify a generic expected error
|
||||
/// (e.g. something that sometimes just goes wrong, like a nix build).
|
||||
pub fn die_expected_error<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(1, current_prog_name, msg)
|
||||
die_with(1, current_prog_name, msg)
|
||||
}
|
||||
|
||||
/// Exit 100 to signify a user error (“the user is holding it wrong”).
|
||||
/// This is a permanent error, if the program is executed the same way
|
||||
/// it should crash with 100 again.
|
||||
pub fn die_user_error<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(100, current_prog_name, msg)
|
||||
}
|
||||
|
@ -78,14 +109,16 @@ where S: AsRef<str>
|
|||
/// Exit 101 to signify an unexpected crash (failing assertion or panic).
|
||||
/// This is the same exit code that `panic!()` emits.
|
||||
pub fn die_panic<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(101, current_prog_name, msg)
|
||||
}
|
||||
|
||||
/// Exit 111 to signify a temporary error (such as resource exhaustion)
|
||||
pub fn die_temporary<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(111, current_prog_name, msg)
|
||||
}
|
||||
|
@ -93,20 +126,23 @@ where S: AsRef<str>
|
|||
/// Exit 126 to signify an environment problem
|
||||
/// (the user has set up stuff incorrectly so the program cannot work)
|
||||
pub fn die_environment_problem<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(126, current_prog_name, msg)
|
||||
}
|
||||
|
||||
/// Exit 127 to signify a missing executable.
|
||||
pub fn die_missing_executable<S>(current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
die_with(127, current_prog_name, msg)
|
||||
}
|
||||
|
||||
fn die_with<S>(status: i32, current_prog_name: &str, msg: S) -> !
|
||||
where S: AsRef<str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
eprintln!("{}: {}", current_prog_name, msg.as_ref());
|
||||
std::process::exit(status)
|
||||
|
|
|
@ -2,7 +2,7 @@ extern crate git2;
|
|||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_BRANCH : &str = "refs/heads/main";
|
||||
const DEFAULT_BRANCH: &str = "refs/heads/main";
|
||||
|
||||
fn main() {
|
||||
let git_db_dir = std::env::var_os("GIT_DB_DIR").expect("set GIT_DB_DIR");
|
||||
|
@ -16,8 +16,12 @@ fn main() {
|
|||
.bare(true)
|
||||
.mkpath(true)
|
||||
.description("git-db database")
|
||||
.initial_head(DEFAULT_BRANCH)
|
||||
).expect(&format!("unable to create or open bare git repo at {}", &git_db.display()));
|
||||
.initial_head(DEFAULT_BRANCH),
|
||||
)
|
||||
.expect(&format!(
|
||||
"unable to create or open bare git repo at {}",
|
||||
&git_db.display()
|
||||
));
|
||||
|
||||
let mut index = repo.index().expect("cannot get the git index file");
|
||||
eprintln!("{:#?}", index.version());
|
||||
|
@ -34,8 +38,9 @@ fn main() {
|
|||
|
||||
let data = "hi, it’s me".as_bytes();
|
||||
|
||||
index.add_frombuffer(
|
||||
&git2::IndexEntry {
|
||||
index
|
||||
.add_frombuffer(
|
||||
&git2::IndexEntry {
|
||||
mtime: now_git_time,
|
||||
ctime: now_git_time,
|
||||
// don’t make sense
|
||||
|
@ -50,25 +55,26 @@ fn main() {
|
|||
flags_extended: 0,
|
||||
path: "hi.txt".as_bytes().to_owned(),
|
||||
},
|
||||
data
|
||||
).expect("could not add data to index");
|
||||
data,
|
||||
)
|
||||
.expect("could not add data to index");
|
||||
|
||||
let oid = index.write_tree().expect("could not write index tree");
|
||||
|
||||
let to_add_tree = repo.find_tree(oid)
|
||||
let to_add_tree = repo
|
||||
.find_tree(oid)
|
||||
.expect("we just created this tree, where did it go?");
|
||||
|
||||
let parent_commits = match repo.find_reference(DEFAULT_BRANCH) {
|
||||
Ok(ref_) => vec![
|
||||
ref_
|
||||
.peel_to_commit()
|
||||
.expect(&format!("reference {} does not point to a commit", DEFAULT_BRANCH))
|
||||
],
|
||||
Ok(ref_) => vec![ref_.peel_to_commit().expect(&format!(
|
||||
"reference {} does not point to a commit",
|
||||
DEFAULT_BRANCH
|
||||
))],
|
||||
Err(err) => match err.code() {
|
||||
// no commit exists yet
|
||||
git2::ErrorCode::NotFound => vec![],
|
||||
_ => panic!("could not read latest commit from {}", DEFAULT_BRANCH),
|
||||
}
|
||||
},
|
||||
};
|
||||
repo.commit(
|
||||
Some(DEFAULT_BRANCH),
|
||||
|
@ -79,7 +85,6 @@ fn main() {
|
|||
I wonder if it supports extended commit descriptions?\n",
|
||||
&to_add_tree,
|
||||
&parent_commits.iter().collect::<Vec<_>>()[..],
|
||||
).expect("could not commit the index we just wrote");
|
||||
|
||||
|
||||
)
|
||||
.expect("could not commit the index we just wrote");
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
extern crate exec_helpers;
|
||||
// extern crate arglib_netencode;
|
||||
// extern crate netencode;
|
||||
extern crate imap;
|
||||
extern crate epoll;
|
||||
extern crate imap;
|
||||
|
||||
// use netencode::dec;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{FromRawFd, AsRawFd, RawFd};
|
||||
use std::time::Duration;
|
||||
use imap::extensions::idle::SetReadTimeout;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Implements an UCSPI client that wraps fd 6 & 7
|
||||
/// and implements Write and Read with a timeout.
|
||||
|
@ -33,7 +33,7 @@ impl UcspiClient {
|
|||
read: File::from_raw_fd(6),
|
||||
read_epoll_fd,
|
||||
read_timeout: None,
|
||||
write: File::from_raw_fd(7)
|
||||
write: File::from_raw_fd(7),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -54,21 +54,23 @@ impl SetReadTimeout for UcspiClient {
|
|||
impl Read for UcspiClient {
|
||||
// TODO: test the epoll code with a short timeout
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
const NO_DATA : u64 = 0;
|
||||
const NO_DATA: u64 = 0;
|
||||
// in order to implement the read_timeout,
|
||||
// we use epoll to wait for either data or time out
|
||||
epoll::ctl(
|
||||
self.read_epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.read.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA)
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA),
|
||||
)?;
|
||||
let UNUSED = epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA);
|
||||
let wait = epoll::wait(
|
||||
self.read_epoll_fd,
|
||||
match self.read_timeout {
|
||||
Some(duration) => i32::try_from(duration.as_millis()).expect("duration too big for epoll"),
|
||||
None => -1 // infinite
|
||||
Some(duration) => {
|
||||
i32::try_from(duration.as_millis()).expect("duration too big for epoll")
|
||||
}
|
||||
None => -1, // infinite
|
||||
},
|
||||
// event that was generated; but we don’t care
|
||||
&mut vec![UNUSED; 1][..],
|
||||
|
@ -79,11 +81,14 @@ impl Read for UcspiClient {
|
|||
self.read_epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
self.read.as_raw_fd(),
|
||||
UNUSED
|
||||
UNUSED,
|
||||
)?;
|
||||
match wait {
|
||||
// timeout happened (0 events)
|
||||
Ok(0) => Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "ucspi read timeout")),
|
||||
Ok(0) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"ucspi read timeout",
|
||||
)),
|
||||
// its ready for reading, we can read
|
||||
Ok(_) => self.read.read(buf),
|
||||
// error
|
||||
|
@ -110,18 +115,21 @@ fn main() {
|
|||
let username = std::env::var("IMAP_USERNAME").expect("username");
|
||||
let password = std::env::var("IMAP_PASSWORD").expect("password");
|
||||
|
||||
let net = unsafe {
|
||||
UcspiClient::new_from_6_and_7().expect("no ucspi client for you")
|
||||
};
|
||||
let net = unsafe { UcspiClient::new_from_6_and_7().expect("no ucspi client for you") };
|
||||
let client = imap::Client::new(net);
|
||||
let mut session = client.login(username, password).map_err(|(err, _)| err).expect("unable to login");
|
||||
let mut session = client
|
||||
.login(username, password)
|
||||
.map_err(|(err, _)| err)
|
||||
.expect("unable to login");
|
||||
eprintln!("{:#?}", session);
|
||||
let list = session.list(None, Some("*"));
|
||||
eprintln!("{:#?}", list);
|
||||
let mailbox = session.examine("INBOX");
|
||||
eprintln!("{:#?}", mailbox);
|
||||
fn now() -> String {
|
||||
String::from_utf8_lossy(&std::process::Command::new("date").output().unwrap().stdout).trim_right().to_string()
|
||||
String::from_utf8_lossy(&std::process::Command::new("date").output().unwrap().stdout)
|
||||
.trim_right()
|
||||
.to_string()
|
||||
}
|
||||
loop {
|
||||
eprintln!("{}: idling on INBOX", now());
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
extern crate netencode;
|
||||
extern crate mustache;
|
||||
extern crate arglib_netencode;
|
||||
extern crate mustache;
|
||||
extern crate netencode;
|
||||
|
||||
use mustache::{Data};
|
||||
use netencode::{T};
|
||||
use mustache::Data;
|
||||
use netencode::T;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::ffi::{OsStrExt};
|
||||
use std::io::{Read};
|
||||
use std::io::Read;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
fn netencode_to_mustache_data_dwim(t: T) -> Data {
|
||||
match t {
|
||||
|
@ -25,27 +25,26 @@ fn netencode_to_mustache_data_dwim(t: T) -> Data {
|
|||
T::Record(xs) => Data::Map(
|
||||
xs.into_iter()
|
||||
.map(|(key, val)| (key, netencode_to_mustache_data_dwim(val)))
|
||||
.collect::<HashMap<_,_>>()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
),
|
||||
T::List(xs) => Data::Vec(
|
||||
xs.into_iter()
|
||||
.map(|x| netencode_to_mustache_data_dwim(x))
|
||||
.collect::<Vec<_>>()
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_stdin() -> () {
|
||||
let data = netencode_to_mustache_data_dwim(
|
||||
arglib_netencode::arglib_netencode("netencode-mustache", Some(std::ffi::OsStr::new("TEMPLATE_DATA")))
|
||||
);
|
||||
let data = netencode_to_mustache_data_dwim(arglib_netencode::arglib_netencode(
|
||||
"netencode-mustache",
|
||||
Some(std::ffi::OsStr::new("TEMPLATE_DATA")),
|
||||
));
|
||||
let mut stdin = String::new();
|
||||
std::io::stdin().read_to_string(&mut stdin).unwrap();
|
||||
mustache::compile_str(&stdin)
|
||||
.and_then(|templ| templ.render_data(
|
||||
&mut std::io::stdout(),
|
||||
&data
|
||||
)).unwrap()
|
||||
.and_then(|templ| templ.render_data(&mut std::io::stdout(), &data))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
extern crate nom;
|
||||
extern crate exec_helpers;
|
||||
extern crate nom;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Write, Read};
|
||||
use std::fmt::{Display, Debug};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum T {
|
||||
|
@ -46,22 +46,19 @@ impl T {
|
|||
T::I7(i) => U::I7(*i),
|
||||
T::Text(t) => U::Text(t.as_str()),
|
||||
T::Binary(v) => U::Binary(v),
|
||||
T::Sum(Tag { tag, val }) => U::Sum(
|
||||
Tag { tag: tag.as_str(), val: Box::new(val.to_u()) }
|
||||
),
|
||||
T::Record(map) => U::Record(
|
||||
map.iter().map(|(k, v)| (k.as_str(), v.to_u())).collect()
|
||||
),
|
||||
T::List(l) => U::List(
|
||||
l.iter().map(|v| v.to_u()).collect::<Vec<U<'a>>>()
|
||||
),
|
||||
T::Sum(Tag { tag, val }) => U::Sum(Tag {
|
||||
tag: tag.as_str(),
|
||||
val: Box::new(val.to_u()),
|
||||
}),
|
||||
T::Record(map) => U::Record(map.iter().map(|(k, v)| (k.as_str(), v.to_u())).collect()),
|
||||
T::List(l) => U::List(l.iter().map(|v| v.to_u()).collect::<Vec<U<'a>>>()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode<'a>(&'a self) -> Vec<u8> {
|
||||
match self {
|
||||
// TODO: don’t go via U, inefficient
|
||||
o => o.to_u().encode()
|
||||
o => o.to_u().encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,15 +107,16 @@ impl<'a> U<'a> {
|
|||
U::I7(i) => T::I7(*i),
|
||||
U::Text(t) => T::Text((*t).to_owned()),
|
||||
U::Binary(v) => T::Binary((*v).to_owned()),
|
||||
U::Sum(Tag { tag, val }) => T::Sum(
|
||||
Tag { tag: (*tag).to_owned(), val: Box::new(val.to_t()) }
|
||||
),
|
||||
U::Sum(Tag { tag, val }) => T::Sum(Tag {
|
||||
tag: (*tag).to_owned(),
|
||||
val: Box::new(val.to_t()),
|
||||
}),
|
||||
U::Record(map) => T::Record(
|
||||
map.iter().map(|(k, v)| ((*k).to_owned(), v.to_t())).collect::<HashMap<String, T>>()
|
||||
),
|
||||
U::List(l) => T::List(
|
||||
l.iter().map(|v| v.to_t()).collect::<Vec<T>>()
|
||||
map.iter()
|
||||
.map(|(k, v)| ((*k).to_owned(), v.to_t()))
|
||||
.collect::<HashMap<String, T>>(),
|
||||
),
|
||||
U::List(l) => T::List(l.iter().map(|v| v.to_t()).collect::<Vec<T>>()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,16 +125,18 @@ impl<'a> U<'a> {
|
|||
pub struct Tag<S, A> {
|
||||
// TODO: make into &str
|
||||
pub tag: S,
|
||||
pub val: Box<A>
|
||||
pub val: Box<A>,
|
||||
}
|
||||
|
||||
impl<S, A> Tag<S, A> {
|
||||
fn map<F, B>(self, f: F) -> Tag<S, B>
|
||||
where F: Fn(A) -> B {
|
||||
Tag {
|
||||
tag: self.tag,
|
||||
val: Box::new(f(*self.val))
|
||||
}
|
||||
where
|
||||
F: Fn(A) -> B,
|
||||
{
|
||||
Tag {
|
||||
tag: self.tag,
|
||||
val: Box::new(f(*self.val)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,45 +147,51 @@ fn encode_tag<W: Write>(w: &mut W, tag: &str, val: &U) -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
pub fn encode<W: Write>(w: &mut W, u: &U) -> std::io::Result<()> {
|
||||
match u {
|
||||
U::Unit => write!(w, "u,"),
|
||||
U::N1(b) => if *b { write!(w, "n1:1,") } else { write!(w, "n1:0,") },
|
||||
U::N3(n) => write!(w, "n3:{},", n),
|
||||
U::N6(n) => write!(w, "n6:{},", n),
|
||||
U::N7(n) => write!(w, "n7:{},", n),
|
||||
U::I3(i) => write!(w, "i3:{},", i),
|
||||
U::I6(i) => write!(w, "i6:{},", i),
|
||||
U::I7(i) => write!(w, "i7:{},", i),
|
||||
U::Text(s) => {
|
||||
write!(w, "t{}:", s.len());
|
||||
w.write_all(s.as_bytes());
|
||||
write!(w, ",")
|
||||
}
|
||||
U::Binary(s) => {
|
||||
write!(w, "b{}:", s.len());
|
||||
w.write_all(&s);
|
||||
write!(w, ",")
|
||||
},
|
||||
U::Sum(Tag{tag, val}) => encode_tag(w, tag, val),
|
||||
U::Record(m) => {
|
||||
let mut c = std::io::Cursor::new(vec![]);
|
||||
for (k, v) in m {
|
||||
encode_tag(&mut c, k, v)?;
|
||||
}
|
||||
write!(w, "{{{}:", c.get_ref().len())?;
|
||||
w.write_all(c.get_ref())?;
|
||||
write!(w, "}}")
|
||||
},
|
||||
U::List(l) => {
|
||||
let mut c = std::io::Cursor::new(vec![]);
|
||||
for u in l {
|
||||
encode(&mut c, u)?;
|
||||
}
|
||||
write!(w, "[{}:", c.get_ref().len())?;
|
||||
w.write_all(c.get_ref())?;
|
||||
write!(w, "]")
|
||||
}
|
||||
}
|
||||
match u {
|
||||
U::Unit => write!(w, "u,"),
|
||||
U::N1(b) => {
|
||||
if *b {
|
||||
write!(w, "n1:1,")
|
||||
} else {
|
||||
write!(w, "n1:0,")
|
||||
}
|
||||
}
|
||||
U::N3(n) => write!(w, "n3:{},", n),
|
||||
U::N6(n) => write!(w, "n6:{},", n),
|
||||
U::N7(n) => write!(w, "n7:{},", n),
|
||||
U::I3(i) => write!(w, "i3:{},", i),
|
||||
U::I6(i) => write!(w, "i6:{},", i),
|
||||
U::I7(i) => write!(w, "i7:{},", i),
|
||||
U::Text(s) => {
|
||||
write!(w, "t{}:", s.len());
|
||||
w.write_all(s.as_bytes());
|
||||
write!(w, ",")
|
||||
}
|
||||
U::Binary(s) => {
|
||||
write!(w, "b{}:", s.len());
|
||||
w.write_all(&s);
|
||||
write!(w, ",")
|
||||
}
|
||||
U::Sum(Tag { tag, val }) => encode_tag(w, tag, val),
|
||||
U::Record(m) => {
|
||||
let mut c = std::io::Cursor::new(vec![]);
|
||||
for (k, v) in m {
|
||||
encode_tag(&mut c, k, v)?;
|
||||
}
|
||||
write!(w, "{{{}:", c.get_ref().len())?;
|
||||
w.write_all(c.get_ref())?;
|
||||
write!(w, "}}")
|
||||
}
|
||||
U::List(l) => {
|
||||
let mut c = std::io::Cursor::new(vec![]);
|
||||
for u in l {
|
||||
encode(&mut c, u)?;
|
||||
}
|
||||
write!(w, "[{}:", c.get_ref().len())?;
|
||||
w.write_all(c.get_ref())?;
|
||||
write!(w, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(s: String) -> T {
|
||||
|
@ -197,27 +203,36 @@ pub fn u_from_stdin_or_die_user_error<'a>(prog_name: &'_ str, stdin_buf: &'a mut
|
|||
let u = match parse::u_u(stdin_buf) {
|
||||
Ok((rest, u)) => match rest {
|
||||
b"" => u,
|
||||
_ => exec_helpers::die_user_error(prog_name, format!("stdin contained some soup after netencode value: {:?}", String::from_utf8_lossy(rest)))
|
||||
_ => exec_helpers::die_user_error(
|
||||
prog_name,
|
||||
format!(
|
||||
"stdin contained some soup after netencode value: {:?}",
|
||||
String::from_utf8_lossy(rest)
|
||||
),
|
||||
),
|
||||
},
|
||||
Err(err) => exec_helpers::die_user_error(prog_name, format!("unable to parse netencode from stdin: {:?}", err))
|
||||
Err(err) => exec_helpers::die_user_error(
|
||||
prog_name,
|
||||
format!("unable to parse netencode from stdin: {:?}", err),
|
||||
),
|
||||
};
|
||||
u
|
||||
}
|
||||
|
||||
pub mod parse {
|
||||
use super::{T, Tag, U};
|
||||
use super::{Tag, T, U};
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::ops::Neg;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Neg;
|
||||
use std::str::FromStr;
|
||||
|
||||
use nom::{IResult};
|
||||
use nom::branch::{alt};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::streaming::{tag, take};
|
||||
use nom::character::streaming::{digit1, char};
|
||||
use nom::sequence::{tuple};
|
||||
use nom::combinator::{map, map_res, flat_map, map_parser, opt};
|
||||
use nom::character::streaming::{char, digit1};
|
||||
use nom::combinator::{flat_map, map, map_parser, map_res, opt};
|
||||
use nom::error::{context, ErrorKind, ParseError};
|
||||
use nom::sequence::tuple;
|
||||
use nom::IResult;
|
||||
|
||||
fn unit_t(s: &[u8]) -> IResult<&[u8], ()> {
|
||||
let (s, _) = context("unit", tag("u,"))(s)?;
|
||||
|
@ -227,9 +242,9 @@ pub mod parse {
|
|||
fn usize_t(s: &[u8]) -> IResult<&[u8], usize> {
|
||||
context(
|
||||
"usize",
|
||||
map_res(
|
||||
map_res(digit1, |n| std::str::from_utf8(n)),
|
||||
|s| s.parse::<usize>())
|
||||
map_res(map_res(digit1, |n| std::str::from_utf8(n)), |s| {
|
||||
s.parse::<usize>()
|
||||
}),
|
||||
)(s)
|
||||
}
|
||||
|
||||
|
@ -238,87 +253,77 @@ pub mod parse {
|
|||
// This is the point where we check the descriminator;
|
||||
// if the beginning char does not match, we can immediately return.
|
||||
let (s, _) = char(begin)(s)?;
|
||||
let (s, (len, _)) = tuple((
|
||||
usize_t,
|
||||
char(':')
|
||||
))(s)?;
|
||||
let (s, (res, _)) = tuple((
|
||||
take(len),
|
||||
char(end)
|
||||
))(s)?;
|
||||
let (s, (len, _)) = tuple((usize_t, char(':')))(s)?;
|
||||
let (s, (res, _)) = tuple((take(len), char(end)))(s)?;
|
||||
Ok((s, res))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn uint_t<'a, I: FromStr + 'a>(t: &'static str) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], I> {
|
||||
move |s: &'a [u8]| {
|
||||
let (s, (_, _, int, _)) = tuple((
|
||||
tag(t.as_bytes()),
|
||||
char(':'),
|
||||
map_res(
|
||||
map_res(digit1, |n: &[u8]| std::str::from_utf8(n)),
|
||||
|s| s.parse::<I>()
|
||||
),
|
||||
char(',')
|
||||
map_res(map_res(digit1, |n: &[u8]| std::str::from_utf8(n)), |s| {
|
||||
s.parse::<I>()
|
||||
}),
|
||||
char(','),
|
||||
))(s)?;
|
||||
Ok((s, int))
|
||||
}
|
||||
}
|
||||
|
||||
fn bool_t<'a>() -> impl Fn(&'a [u8]) -> IResult<&'a [u8], bool> {
|
||||
context("bool", alt((
|
||||
map(tag("n1:0,"), |_| false),
|
||||
map(tag("n1:1,"), |_| true),
|
||||
)))
|
||||
context(
|
||||
"bool",
|
||||
alt((map(tag("n1:0,"), |_| false), map(tag("n1:1,"), |_| true))),
|
||||
)
|
||||
}
|
||||
|
||||
fn int_t<'a, I: FromStr + Neg<Output=I>>(t: &'static str) -> impl Fn(&'a [u8]) -> IResult<&[u8], I> {
|
||||
context(
|
||||
t,
|
||||
move |s: &'a [u8]| {
|
||||
let (s, (_, _, neg, int, _)) = tuple((
|
||||
tag(t.as_bytes()),
|
||||
char(':'),
|
||||
opt(char('-')),
|
||||
map_res(
|
||||
map_res(digit1, |n: &[u8]| std::str::from_utf8(n)),
|
||||
|s| s.parse::<I>()
|
||||
),
|
||||
char(',')
|
||||
))(s)?;
|
||||
let res = match neg {
|
||||
Some(_) => -int,
|
||||
None => int,
|
||||
};
|
||||
Ok((s, res))
|
||||
}
|
||||
)
|
||||
fn int_t<'a, I: FromStr + Neg<Output = I>>(
|
||||
t: &'static str,
|
||||
) -> impl Fn(&'a [u8]) -> IResult<&[u8], I> {
|
||||
context(t, move |s: &'a [u8]| {
|
||||
let (s, (_, _, neg, int, _)) = tuple((
|
||||
tag(t.as_bytes()),
|
||||
char(':'),
|
||||
opt(char('-')),
|
||||
map_res(map_res(digit1, |n: &[u8]| std::str::from_utf8(n)), |s| {
|
||||
s.parse::<I>()
|
||||
}),
|
||||
char(','),
|
||||
))(s)?;
|
||||
let res = match neg {
|
||||
Some(_) => -int,
|
||||
None => int,
|
||||
};
|
||||
Ok((s, res))
|
||||
})
|
||||
}
|
||||
|
||||
fn tag_t(s: &[u8]) -> IResult<&[u8], Tag<String, T>> {
|
||||
// recurses into the main parser
|
||||
map(tag_g(t_t),
|
||||
|Tag {tag, val}|
|
||||
Tag {
|
||||
tag: tag.to_string(),
|
||||
val
|
||||
})(s)
|
||||
map(tag_g(t_t), |Tag { tag, val }| Tag {
|
||||
tag: tag.to_string(),
|
||||
val,
|
||||
})(s)
|
||||
}
|
||||
|
||||
fn tag_g<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Tag<&'a str, O>>
|
||||
where
|
||||
P: Fn(&'a [u8]) -> IResult<&'a [u8], O>
|
||||
P: Fn(&'a [u8]) -> IResult<&'a [u8], O>,
|
||||
{
|
||||
move |s: &[u8]| {
|
||||
let (s, tag) = sized('<', '|')(s)?;
|
||||
let (s, val) = inner(s)?;
|
||||
Ok((s, Tag {
|
||||
tag: std::str::from_utf8(tag)
|
||||
.map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?,
|
||||
val: Box::new(val)
|
||||
}))
|
||||
|
||||
Ok((
|
||||
s,
|
||||
Tag {
|
||||
tag: std::str::from_utf8(tag)
|
||||
.map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?,
|
||||
val: Box::new(val),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,9 +335,9 @@ pub mod parse {
|
|||
|
||||
fn text_g(s: &[u8]) -> IResult<&[u8], &str> {
|
||||
let (s, res) = sized('t', ',')(s)?;
|
||||
Ok((s,
|
||||
std::str::from_utf8(res)
|
||||
.map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?,
|
||||
Ok((
|
||||
s,
|
||||
std::str::from_utf8(res).map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -374,22 +379,24 @@ pub mod parse {
|
|||
{
|
||||
map_parser(
|
||||
sized('[', ']'),
|
||||
nom::multi::many0(inner_no_empty_string(inner))
|
||||
nom::multi::many0(inner_no_empty_string(inner)),
|
||||
)
|
||||
}
|
||||
|
||||
fn record_t<'a>(s: &'a [u8]) -> IResult<&'a [u8], HashMap<String, T>> {
|
||||
let (s, r) = record_g(t_t)(s)?;
|
||||
Ok((s,
|
||||
Ok((
|
||||
s,
|
||||
r.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect::<HashMap<_,_>>()))
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn record_g<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], HashMap<&'a str, O>>
|
||||
where
|
||||
O: Clone,
|
||||
P: Fn(&'a [u8]) -> IResult<&'a [u8], O>
|
||||
P: Fn(&'a [u8]) -> IResult<&'a [u8], O>,
|
||||
{
|
||||
move |s: &'a [u8]| {
|
||||
let (s, map) = map_parser(
|
||||
|
@ -397,19 +404,19 @@ pub mod parse {
|
|||
nom::multi::fold_many0(
|
||||
inner_no_empty_string(tag_g(&inner)),
|
||||
HashMap::new(),
|
||||
|mut acc: HashMap<_,_>, Tag { tag, mut val }| {
|
||||
|mut acc: HashMap<_, _>, Tag { tag, mut val }| {
|
||||
// ignore duplicated tag names that appear later
|
||||
// according to netencode spec
|
||||
if ! acc.contains_key(tag) {
|
||||
if !acc.contains_key(tag) {
|
||||
acc.insert(tag, *val);
|
||||
}
|
||||
acc
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)(s)?;
|
||||
if map.is_empty() {
|
||||
// records must not be empty, according to the spec
|
||||
Err(nom::Err::Failure((s,nom::error::ErrorKind::Many1)))
|
||||
Err(nom::Err::Failure((s, nom::error::ErrorKind::Many1)))
|
||||
} else {
|
||||
Ok((s, map))
|
||||
}
|
||||
|
@ -424,7 +431,6 @@ pub mod parse {
|
|||
map(tag_g(u_u), |t| U::Sum(t)),
|
||||
map(list_g(u_u), U::List),
|
||||
map(record_g(u_u), U::Record),
|
||||
|
||||
map(bool_t(), |u| U::N1(u)),
|
||||
map(uint_t("n3"), |u| U::N3(u)),
|
||||
map(uint_t("n6"), |u| U::N6(u)),
|
||||
|
@ -432,7 +438,6 @@ pub mod parse {
|
|||
map(int_t("i3"), |u| U::I3(u)),
|
||||
map(int_t("i6"), |u| U::I6(u)),
|
||||
map(int_t("i7"), |u| U::I7(u)),
|
||||
|
||||
// less common
|
||||
map(uint_t("n2"), |u| U::N3(u)),
|
||||
map(uint_t("n4"), |u| U::N6(u)),
|
||||
|
@ -445,7 +450,7 @@ pub mod parse {
|
|||
))(s)
|
||||
}
|
||||
|
||||
pub fn t_t(s: &[u8]) -> IResult<&[u8], T> {
|
||||
pub fn t_t(s: &[u8]) -> IResult<&[u8], T> {
|
||||
alt((
|
||||
text,
|
||||
binary(),
|
||||
|
@ -453,7 +458,6 @@ pub mod parse {
|
|||
map(tag_t, |t| T::Sum(t)),
|
||||
map(list_t, |l| T::List(l)),
|
||||
map(record_t, |p| T::Record(p)),
|
||||
|
||||
map(bool_t(), |u| T::N1(u)),
|
||||
// 8, 64 and 128 bit
|
||||
map(uint_t("n3"), |u| T::N3(u)),
|
||||
|
@ -462,7 +466,6 @@ pub mod parse {
|
|||
map(int_t("i3"), |u| T::I3(u)),
|
||||
map(int_t("i6"), |u| T::I6(u)),
|
||||
map(int_t("i7"), |u| T::I7(u)),
|
||||
|
||||
// less common
|
||||
map(uint_t("n2"), |u| T::N3(u)),
|
||||
map(uint_t("n4"), |u| T::N6(u)),
|
||||
|
@ -481,30 +484,18 @@ pub mod parse {
|
|||
|
||||
#[test]
|
||||
fn test_parse_unit_t() {
|
||||
assert_eq!(
|
||||
unit_t("u,".as_bytes()),
|
||||
Ok(("".as_bytes(), ()))
|
||||
);
|
||||
assert_eq!(unit_t("u,".as_bytes()), Ok(("".as_bytes(), ())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_bool_t() {
|
||||
assert_eq!(
|
||||
bool_t()("n1:0,".as_bytes()),
|
||||
Ok(("".as_bytes(), false))
|
||||
);
|
||||
assert_eq!(
|
||||
bool_t()("n1:1,".as_bytes()),
|
||||
Ok(("".as_bytes(), true))
|
||||
);
|
||||
assert_eq!(bool_t()("n1:0,".as_bytes()), Ok(("".as_bytes(), false)));
|
||||
assert_eq!(bool_t()("n1:1,".as_bytes()), Ok(("".as_bytes(), true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_usize_t() {
|
||||
assert_eq!(
|
||||
usize_t("32foo".as_bytes()),
|
||||
Ok(("foo".as_bytes(), 32))
|
||||
);
|
||||
assert_eq!(usize_t("32foo".as_bytes()), Ok(("foo".as_bytes(), 32)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -515,7 +506,10 @@ pub mod parse {
|
|||
);
|
||||
assert_eq!(
|
||||
uint_t::<u8>("n3")("n3:1024,abc".as_bytes()),
|
||||
Err(nom::Err::Error(("1024,abc".as_bytes(), nom::error::ErrorKind::MapRes)))
|
||||
Err(nom::Err::Error((
|
||||
"1024,abc".as_bytes(),
|
||||
nom::error::ErrorKind::MapRes
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
int_t::<i64>("i6")("i6:-23,abc".as_bytes()),
|
||||
|
@ -544,18 +538,21 @@ pub mod parse {
|
|||
assert_eq!(
|
||||
text("t5:hello,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Text("hello".to_owned()))),
|
||||
"{}", r"t5:hello,"
|
||||
"{}",
|
||||
r"t5:hello,"
|
||||
);
|
||||
assert_eq!(
|
||||
text("t4:fo".as_bytes()),
|
||||
// The content of the text should be 4 long
|
||||
Err(nom::Err::Incomplete(nom::Needed::Size(4))),
|
||||
"{}", r"t4:fo,"
|
||||
"{}",
|
||||
r"t4:fo,"
|
||||
);
|
||||
assert_eq!(
|
||||
text("t9:今日は,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Text("今日は".to_owned()))),
|
||||
"{}", r"t9:今日は,"
|
||||
"{}",
|
||||
r"t9:今日は,"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -564,24 +561,28 @@ pub mod parse {
|
|||
assert_eq!(
|
||||
binary()("b5:hello,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Binary(Vec::from("hello".to_owned())))),
|
||||
"{}", r"b5:hello,"
|
||||
"{}",
|
||||
r"b5:hello,"
|
||||
);
|
||||
assert_eq!(
|
||||
binary()("b4:fo".as_bytes()),
|
||||
// The content of the byte should be 4 long
|
||||
Err(nom::Err::Incomplete(nom::Needed::Size(4))),
|
||||
"{}", r"b4:fo,"
|
||||
"{}",
|
||||
r"b4:fo,"
|
||||
);
|
||||
assert_eq!(
|
||||
binary()("b4:foob".as_bytes()),
|
||||
// The content is 4 bytes now, but the finishing , is missing
|
||||
Err(nom::Err::Incomplete(nom::Needed::Size(1))),
|
||||
"{}", r"b4:fo,"
|
||||
);
|
||||
"{}",
|
||||
r"b4:fo,"
|
||||
);
|
||||
assert_eq!(
|
||||
binary()("b9:今日は,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Binary(Vec::from("今日は".as_bytes())))),
|
||||
"{}", r"b9:今日は,"
|
||||
"{}",
|
||||
r"b9:今日は,"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -590,25 +591,23 @@ pub mod parse {
|
|||
assert_eq!(
|
||||
list_t("[0:]".as_bytes()),
|
||||
Ok(("".as_bytes(), vec![])),
|
||||
"{}", r"[0:]"
|
||||
"{}",
|
||||
r"[0:]"
|
||||
);
|
||||
assert_eq!(
|
||||
list_t("[6:u,u,u,]".as_bytes()),
|
||||
Ok(("".as_bytes(), vec![
|
||||
T::Unit,
|
||||
T::Unit,
|
||||
T::Unit,
|
||||
])),
|
||||
"{}", r"[6:u,u,u,]"
|
||||
Ok(("".as_bytes(), vec![T::Unit, T::Unit, T::Unit,])),
|
||||
"{}",
|
||||
r"[6:u,u,u,]"
|
||||
);
|
||||
assert_eq!(
|
||||
list_t("[15:u,[7:t3:foo,]u,]".as_bytes()),
|
||||
Ok(("".as_bytes(), vec![
|
||||
T::Unit,
|
||||
T::List(vec![T::Text("foo".to_owned())]),
|
||||
T::Unit,
|
||||
])),
|
||||
"{}", r"[15:u,[7:t3:foo,]u,]"
|
||||
Ok((
|
||||
"".as_bytes(),
|
||||
vec![T::Unit, T::List(vec![T::Text("foo".to_owned())]), T::Unit,]
|
||||
)),
|
||||
"{}",
|
||||
r"[15:u,[7:t3:foo,]u,]"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -616,27 +615,40 @@ pub mod parse {
|
|||
fn test_record() {
|
||||
assert_eq!(
|
||||
record_t("{21:<1:a|u,<1:b|u,<1:c|u,}".as_bytes()),
|
||||
Ok(("".as_bytes(), vec![
|
||||
("a".to_owned(), T::Unit),
|
||||
("b".to_owned(), T::Unit),
|
||||
("c".to_owned(), T::Unit),
|
||||
].into_iter().collect::<HashMap<String, T>>())),
|
||||
"{}", r"{21:<1:a|u,<1:b|u,<1:c|u,}"
|
||||
Ok((
|
||||
"".as_bytes(),
|
||||
vec![
|
||||
("a".to_owned(), T::Unit),
|
||||
("b".to_owned(), T::Unit),
|
||||
("c".to_owned(), T::Unit),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<String, T>>()
|
||||
)),
|
||||
"{}",
|
||||
r"{21:<1:a|u,<1:b|u,<1:c|u,}"
|
||||
);
|
||||
// duplicated keys are ignored (first is taken)
|
||||
assert_eq!(
|
||||
record_t("{25:<1:a|u,<1:b|u,<1:a|i1:-1,}".as_bytes()),
|
||||
Ok(("".as_bytes(), vec![
|
||||
("a".to_owned(), T::Unit),
|
||||
("b".to_owned(), T::Unit),
|
||||
].into_iter().collect::<HashMap<_,_>>())),
|
||||
"{}", r"{25:<1:a|u,<1:b|u,<1:a|i1:-1,}"
|
||||
Ok((
|
||||
"".as_bytes(),
|
||||
vec![("a".to_owned(), T::Unit), ("b".to_owned(), T::Unit),]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
)),
|
||||
"{}",
|
||||
r"{25:<1:a|u,<1:b|u,<1:a|i1:-1,}"
|
||||
);
|
||||
// empty records are not allowed
|
||||
assert_eq!(
|
||||
record_t("{0:}".as_bytes()),
|
||||
Err(nom::Err::Failure(("".as_bytes(), nom::error::ErrorKind::Many1))),
|
||||
"{}", r"{0:}"
|
||||
Err(nom::Err::Failure((
|
||||
"".as_bytes(),
|
||||
nom::error::ErrorKind::Many1
|
||||
))),
|
||||
"{}",
|
||||
r"{0:}"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -645,37 +657,62 @@ pub mod parse {
|
|||
assert_eq!(
|
||||
t_t("n3:255,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::N3(255))),
|
||||
"{}", r"n3:255,"
|
||||
"{}",
|
||||
r"n3:255,"
|
||||
);
|
||||
assert_eq!(
|
||||
t_t("t6:halloo,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Text("halloo".to_owned()))),
|
||||
"{}", r"t6:halloo,"
|
||||
"{}",
|
||||
r"t6:halloo,"
|
||||
);
|
||||
assert_eq!(
|
||||
t_t("<3:foo|t6:halloo,".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Sum (Tag {
|
||||
tag: "foo".to_owned(),
|
||||
val: Box::new(T::Text("halloo".to_owned()))
|
||||
}))),
|
||||
"{}", r"<3:foo|t6:halloo,"
|
||||
Ok((
|
||||
"".as_bytes(),
|
||||
T::Sum(Tag {
|
||||
tag: "foo".to_owned(),
|
||||
val: Box::new(T::Text("halloo".to_owned()))
|
||||
})
|
||||
)),
|
||||
"{}",
|
||||
r"<3:foo|t6:halloo,"
|
||||
);
|
||||
// { a: Unit
|
||||
// , foo: List <A: Unit | B: List i3> }
|
||||
assert_eq!(
|
||||
t_t("{52:<1:a|u,<3:foo|[33:<1:A|u,<1:A|n1:1,<1:B|[7:i3:127,]]}".as_bytes()),
|
||||
Ok(("".as_bytes(), T::Record(vec![
|
||||
("a".to_owned(), T::Unit),
|
||||
("foo".to_owned(), T::List(vec![
|
||||
T::Sum(Tag { tag: "A".to_owned(), val: Box::new(T::Unit) }),
|
||||
T::Sum(Tag { tag: "A".to_owned(), val: Box::new(T::N1(true)) }),
|
||||
T::Sum(Tag { tag: "B".to_owned(), val: Box::new(T::List(vec![T::I3(127)])) }),
|
||||
]))
|
||||
].into_iter().collect::<HashMap<String, T>>()))),
|
||||
"{}", r"{52:<1:a|u,<3:foo|[33:<1:A|u,<1:A|n1:1,<1:B|[7:i3:127,]]}"
|
||||
Ok((
|
||||
"".as_bytes(),
|
||||
T::Record(
|
||||
vec![
|
||||
("a".to_owned(), T::Unit),
|
||||
(
|
||||
"foo".to_owned(),
|
||||
T::List(vec![
|
||||
T::Sum(Tag {
|
||||
tag: "A".to_owned(),
|
||||
val: Box::new(T::Unit)
|
||||
}),
|
||||
T::Sum(Tag {
|
||||
tag: "A".to_owned(),
|
||||
val: Box::new(T::N1(true))
|
||||
}),
|
||||
T::Sum(Tag {
|
||||
tag: "B".to_owned(),
|
||||
val: Box::new(T::List(vec![T::I3(127)]))
|
||||
}),
|
||||
])
|
||||
)
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<String, T>>()
|
||||
)
|
||||
)),
|
||||
"{}",
|
||||
r"{52:<1:a|u,<3:foo|[33:<1:A|u,<1:A|n1:1,<1:B|[7:i3:127,]]}"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -735,7 +772,10 @@ pub mod dec {
|
|||
fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> {
|
||||
match u {
|
||||
U::Binary(b) => Ok(b),
|
||||
other => Err(DecodeError(format!("Cannot decode {:?} into Binary", other))),
|
||||
other => Err(DecodeError(format!(
|
||||
"Cannot decode {:?} into Binary",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -766,16 +806,17 @@ pub mod dec {
|
|||
pub struct Record<T>(pub T);
|
||||
|
||||
impl<'a, Inner> Decoder<'a> for Record<Inner>
|
||||
where Inner: Decoder<'a>
|
||||
where
|
||||
Inner: Decoder<'a>,
|
||||
{
|
||||
type A = HashMap<&'a str, Inner::A>;
|
||||
fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> {
|
||||
match u {
|
||||
U::Record(map) =>
|
||||
map.into_iter()
|
||||
U::Record(map) => map
|
||||
.into_iter()
|
||||
.map(|(k, v)| self.0.dec(v).map(|v2| (k, v2)))
|
||||
.collect::<Result<Self::A, _>>(),
|
||||
o => Err(DecodeError(format!("Cannot decode {:?} into record", o)))
|
||||
o => Err(DecodeError(format!("Cannot decode {:?} into record", o))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -784,18 +825,22 @@ pub mod dec {
|
|||
#[derive(Clone, Copy)]
|
||||
pub struct RecordDot<'a, T> {
|
||||
pub field: &'a str,
|
||||
pub inner: T
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
impl <'a, Inner> Decoder<'a> for RecordDot<'_, Inner>
|
||||
where Inner: Decoder<'a> + Clone
|
||||
impl<'a, Inner> Decoder<'a> for RecordDot<'_, Inner>
|
||||
where
|
||||
Inner: Decoder<'a> + Clone,
|
||||
{
|
||||
type A = Inner::A;
|
||||
fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> {
|
||||
match Record(self.inner.clone()).dec(u) {
|
||||
Ok(mut map) => match map.remove(self.field) {
|
||||
Some(inner) => Ok(inner),
|
||||
None => Err(DecodeError(format!("Cannot find `{}` in record map", self.field))),
|
||||
None => Err(DecodeError(format!(
|
||||
"Cannot find `{}` in record map",
|
||||
self.field
|
||||
))),
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
|
@ -804,23 +849,27 @@ pub mod dec {
|
|||
|
||||
/// Equals one of the listed `A`s exactly, after decoding.
|
||||
#[derive(Clone)]
|
||||
pub struct OneOf<T, A>{
|
||||
pub struct OneOf<T, A> {
|
||||
pub inner: T,
|
||||
pub list: Vec<A>,
|
||||
}
|
||||
|
||||
impl <'a, Inner> Decoder<'a> for OneOf<Inner, Inner::A>
|
||||
where Inner: Decoder<'a>,
|
||||
Inner::A: Display + Debug + PartialEq
|
||||
impl<'a, Inner> Decoder<'a> for OneOf<Inner, Inner::A>
|
||||
where
|
||||
Inner: Decoder<'a>,
|
||||
Inner::A: Display + Debug + PartialEq,
|
||||
{
|
||||
type A = Inner::A;
|
||||
fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> {
|
||||
match self.inner.dec(u) {
|
||||
Ok(inner) => match self.list.iter().any(|x| x.eq(&inner)) {
|
||||
true => Ok(inner),
|
||||
false => Err(DecodeError(format!("{} is not one of {:?}", inner, self.list)))
|
||||
false => Err(DecodeError(format!(
|
||||
"{} is not one of {:?}",
|
||||
inner, self.list
|
||||
))),
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -829,16 +878,16 @@ pub mod dec {
|
|||
#[derive(Clone)]
|
||||
pub struct Try<T>(pub T);
|
||||
|
||||
impl <'a, Inner> Decoder<'a> for Try<Inner>
|
||||
where Inner: Decoder<'a>
|
||||
impl<'a, Inner> Decoder<'a> for Try<Inner>
|
||||
where
|
||||
Inner: Decoder<'a>,
|
||||
{
|
||||
type A = Option<Inner::A>;
|
||||
fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> {
|
||||
match self.0.dec(u) {
|
||||
Ok(inner) => Ok(Some(inner)),
|
||||
Err(err) => Ok(None)
|
||||
Err(err) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
extern crate netencode;
|
||||
|
||||
use netencode::{U, T, Tag};
|
||||
use netencode::{Tag, T, U};
|
||||
|
||||
pub enum Pretty {
|
||||
Single {
|
||||
|
@ -20,7 +20,7 @@ pub enum Pretty {
|
|||
r#type: char,
|
||||
length: String,
|
||||
vals: Vec<Pretty>,
|
||||
trailer: char
|
||||
trailer: char,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ impl Pretty {
|
|||
r#type: 't',
|
||||
length: format!("{}:", s.len()),
|
||||
val: s.to_string(),
|
||||
trailer: ','
|
||||
trailer: ',',
|
||||
},
|
||||
U::Binary(s) => Pretty::Single {
|
||||
r#type: 'b',
|
||||
|
@ -47,15 +47,18 @@ impl Pretty {
|
|||
// For pretty printing we want the string to be visible obviously.
|
||||
// Instead of not supporting binary, let’s use lossy conversion.
|
||||
val: String::from_utf8_lossy(s).into_owned(),
|
||||
trailer: ','
|
||||
trailer: ',',
|
||||
},
|
||||
U::Sum(Tag{tag, val}) => Self::pretty_tag(tag, Self::from_u(*val)),
|
||||
U::Sum(Tag { tag, val }) => Self::pretty_tag(tag, Self::from_u(*val)),
|
||||
U::Record(m) => Pretty::Multi {
|
||||
r#type: '{',
|
||||
// TODO: we are losing the size here, should we recompute it? Keep it?
|
||||
length: String::from(""),
|
||||
vals: m.into_iter().map(|(k, v)| Self::pretty_tag(k, Self::from_u(v))).collect(),
|
||||
trailer: '}'
|
||||
vals: m
|
||||
.into_iter()
|
||||
.map(|(k, v)| Self::pretty_tag(k, Self::from_u(v)))
|
||||
.collect(),
|
||||
trailer: '}',
|
||||
},
|
||||
U::List(l) => Pretty::Multi {
|
||||
r#type: '[',
|
||||
|
@ -68,13 +71,14 @@ impl Pretty {
|
|||
}
|
||||
|
||||
fn scalar<D>(r#type: char, length: &str, d: D) -> Pretty
|
||||
where D: std::fmt::Display
|
||||
where
|
||||
D: std::fmt::Display,
|
||||
{
|
||||
Pretty::Single {
|
||||
r#type,
|
||||
length: length.to_string(),
|
||||
val: format!("{}", d),
|
||||
trailer: ','
|
||||
trailer: ',',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,43 +93,62 @@ impl Pretty {
|
|||
}
|
||||
|
||||
pub fn print_multiline<W>(&self, mut w: &mut W) -> std::io::Result<()>
|
||||
where W: std::io::Write
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
Self::go(&mut w, self, 0, true);
|
||||
write!(w, "\n")
|
||||
}
|
||||
|
||||
fn go<W>(mut w: &mut W, p: &Pretty, depth: usize, is_newline: bool) -> std::io::Result<()>
|
||||
where W: std::io::Write
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
const full : usize = 4;
|
||||
const half : usize = 2;
|
||||
let i = &vec![b' '; depth*full];
|
||||
let iandhalf = &vec![b' '; depth*full + half];
|
||||
let (i, iandhalf) = unsafe {(
|
||||
std::str::from_utf8_unchecked(i),
|
||||
std::str::from_utf8_unchecked(iandhalf),
|
||||
)};
|
||||
const full: usize = 4;
|
||||
const half: usize = 2;
|
||||
let i = &vec![b' '; depth * full];
|
||||
let iandhalf = &vec![b' '; depth * full + half];
|
||||
let (i, iandhalf) = unsafe {
|
||||
(
|
||||
std::str::from_utf8_unchecked(i),
|
||||
std::str::from_utf8_unchecked(iandhalf),
|
||||
)
|
||||
};
|
||||
if is_newline {
|
||||
write!(&mut w, "{}", i);
|
||||
}
|
||||
match p {
|
||||
Pretty::Single {r#type, length, val, trailer} =>
|
||||
write!(&mut w, "{} {}{}", r#type, val, trailer),
|
||||
Pretty::Tag { r#type, length, key, inner, val } => {
|
||||
Pretty::Single {
|
||||
r#type,
|
||||
length,
|
||||
val,
|
||||
trailer,
|
||||
} => write!(&mut w, "{} {}{}", r#type, val, trailer),
|
||||
Pretty::Tag {
|
||||
r#type,
|
||||
length,
|
||||
key,
|
||||
inner,
|
||||
val,
|
||||
} => {
|
||||
write!(&mut w, "{} {} {}", r#type, key, inner)?;
|
||||
Self::go::<W>(&mut w, val, depth, false)
|
||||
},
|
||||
}
|
||||
// if the length is 0 or 1, we print on one line,
|
||||
// only if there’s more than one element we split the resulting value.
|
||||
// we never break lines on arbitrary column sizes, since that is just silly.
|
||||
Pretty::Multi {r#type, length, vals, trailer} => match vals.len() {
|
||||
Pretty::Multi {
|
||||
r#type,
|
||||
length,
|
||||
vals,
|
||||
trailer,
|
||||
} => match vals.len() {
|
||||
0 => write!(&mut w, "{} {}", r#type, trailer),
|
||||
1 => {
|
||||
write!(&mut w, "{} ", r#type);
|
||||
Self::go::<W>(&mut w, &vals[0], depth, false)?;
|
||||
write!(&mut w, "{}", trailer)
|
||||
},
|
||||
}
|
||||
more => {
|
||||
write!(&mut w, "\n{}{} \n", iandhalf, r#type)?;
|
||||
for v in vals {
|
||||
|
|
|
@ -1,37 +1,35 @@
|
|||
extern crate httparse;
|
||||
extern crate netencode;
|
||||
extern crate arglib_netencode;
|
||||
extern crate ascii;
|
||||
extern crate exec_helpers;
|
||||
extern crate httparse;
|
||||
extern crate netencode;
|
||||
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use exec_helpers::{die_expected_error, die_temporary, die_user_error};
|
||||
use std::collections::HashMap;
|
||||
use exec_helpers::{die_user_error, die_expected_error, die_temporary};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
use netencode::{U, T, dec};
|
||||
use netencode::dec::Decoder;
|
||||
use netencode::{dec, T, U};
|
||||
|
||||
enum What {
|
||||
Request,
|
||||
Response
|
||||
Response,
|
||||
}
|
||||
|
||||
// reads a http request (stdin), and writes all headers to stdout, as netencoded record.
|
||||
// The keys are text, but can be lists of text iff headers appear multiple times, so beware.
|
||||
fn main() -> std::io::Result<()> {
|
||||
|
||||
exec_helpers::no_args("read-http");
|
||||
|
||||
let args = dec::RecordDot {
|
||||
field: "what",
|
||||
inner: dec::OneOf {
|
||||
list: vec!["request", "response"],
|
||||
inner: dec::Text
|
||||
}
|
||||
inner: dec::Text,
|
||||
},
|
||||
};
|
||||
let what : What = match args.dec(arglib_netencode::arglib_netencode("read-http", None).to_u()) {
|
||||
let what: What = match args.dec(arglib_netencode::arglib_netencode("read-http", None).to_u()) {
|
||||
Ok("request") => What::Request,
|
||||
Ok("response") => What::Response,
|
||||
Ok(v) => panic!("shouldn’t happen!, value was: {}", v),
|
||||
|
@ -39,7 +37,8 @@ fn main() -> std::io::Result<()> {
|
|||
};
|
||||
|
||||
fn read_stdin_to_complete<F>(mut parse: F) -> ()
|
||||
where F: FnMut(&[u8]) -> httparse::Result<usize>
|
||||
where
|
||||
F: FnMut(&[u8]) -> httparse::Result<usize>,
|
||||
{
|
||||
let mut res = httparse::Status::Partial;
|
||||
loop {
|
||||
|
@ -48,16 +47,22 @@ fn main() -> std::io::Result<()> {
|
|||
}
|
||||
let mut buf = [0; 2048];
|
||||
match std::io::stdin().read(&mut buf[..]) {
|
||||
Ok(size) => if size == 0 {
|
||||
break;
|
||||
},
|
||||
Err(err) => die_temporary("read-http", format!("could not read from stdin, {:?}", err))
|
||||
Ok(size) => {
|
||||
if size == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
die_temporary("read-http", format!("could not read from stdin, {:?}", err))
|
||||
}
|
||||
}
|
||||
match parse(&buf) {
|
||||
Ok(status) => {
|
||||
res = status;
|
||||
}
|
||||
Err(err) => die_temporary("read-http", format!("httparse parsing failed: {:#?}", err))
|
||||
Err(err) => {
|
||||
die_temporary("read-http", format!("httparse parsing failed: {:#?}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +71,10 @@ fn main() -> std::io::Result<()> {
|
|||
let mut res = HashMap::new();
|
||||
for httparse::Header { name, value } in headers {
|
||||
let val = ascii::AsciiStr::from_ascii(*value)
|
||||
.expect(&format!("read-http: we require header values to be ASCII, but the header {} was {:?}", name, value))
|
||||
.expect(&format!(
|
||||
"read-http: we require header values to be ASCII, but the header {} was {:?}",
|
||||
name, value
|
||||
))
|
||||
.as_str();
|
||||
// lowercase the header names, since the standard doesn’t care
|
||||
// and we want unique strings to match against
|
||||
|
@ -77,13 +85,13 @@ fn main() -> std::io::Result<()> {
|
|||
let name_lower = name.to_lowercase();
|
||||
let _ = res.insert(name_lower, U::List(vec![U::Text(t), U::Text(val)]));
|
||||
()
|
||||
},
|
||||
}
|
||||
Some(U::List(mut l)) => {
|
||||
let name_lower = name.to_lowercase();
|
||||
l.push(U::Text(val));
|
||||
let _ = res.insert(name_lower, U::List(l));
|
||||
()
|
||||
},
|
||||
}
|
||||
Some(o) => panic!("read-http: header not text nor list: {:?}", o),
|
||||
}
|
||||
}
|
||||
|
@ -98,12 +106,14 @@ fn main() -> std::io::Result<()> {
|
|||
match chonker.next() {
|
||||
Some(Ok(chunk)) => {
|
||||
buf.extend_from_slice(&chunk);
|
||||
if chunk.windows(4).any(|c| c == b"\r\n\r\n" ) {
|
||||
if chunk.windows(4).any(|c| c == b"\r\n\r\n") {
|
||||
return Some(());
|
||||
}
|
||||
},
|
||||
Some(Err(err)) => die_temporary("read-http", format!("error reading from stdin: {:?}", err)),
|
||||
None => return None
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
die_temporary("read-http", format!("error reading from stdin: {:?}", err))
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,66 +128,97 @@ fn main() -> std::io::Result<()> {
|
|||
let mut buf: Vec<u8> = vec![];
|
||||
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
||||
Some(()) => match req.parse(&buf) {
|
||||
Ok(httparse::Status::Complete(_body_start)) => {},
|
||||
Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"),
|
||||
Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err))
|
||||
Ok(httparse::Status::Complete(_body_start)) => {}
|
||||
Ok(httparse::Status::Partial) => {
|
||||
die_expected_error("read-http", "httparse should have gotten a full header")
|
||||
}
|
||||
Err(err) => die_expected_error(
|
||||
"read-http",
|
||||
format!("httparse response parsing failed: {:#?}", err),
|
||||
),
|
||||
},
|
||||
None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse request headers"))
|
||||
None => die_expected_error(
|
||||
"read-http",
|
||||
format!("httparse end of stdin reached before able to parse request headers"),
|
||||
),
|
||||
}
|
||||
let method = req.method.expect("method must be filled on complete parse");
|
||||
let path = req.path.expect("path must be filled on complete parse");
|
||||
write_dict_req(method, path, &normalize_headers(req.headers))
|
||||
},
|
||||
}
|
||||
Response => {
|
||||
let mut resp = httparse::Response::new(&mut headers);
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
||||
Some(()) => match resp.parse(&buf) {
|
||||
Ok(httparse::Status::Complete(_body_start)) => {},
|
||||
Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"),
|
||||
Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err))
|
||||
Ok(httparse::Status::Complete(_body_start)) => {}
|
||||
Ok(httparse::Status::Partial) => {
|
||||
die_expected_error("read-http", "httparse should have gotten a full header")
|
||||
}
|
||||
Err(err) => die_expected_error(
|
||||
"read-http",
|
||||
format!("httparse response parsing failed: {:#?}", err),
|
||||
),
|
||||
},
|
||||
None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse response headers"))
|
||||
None => die_expected_error(
|
||||
"read-http",
|
||||
format!("httparse end of stdin reached before able to parse response headers"),
|
||||
),
|
||||
}
|
||||
let code = resp.code.expect("code must be filled on complete parse");
|
||||
let reason = resp.reason.expect("reason must be filled on complete parse");
|
||||
let reason = resp
|
||||
.reason
|
||||
.expect("reason must be filled on complete parse");
|
||||
write_dict_resp(code, reason, &normalize_headers(resp.headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_dict_req<'a, 'buf>(method: &'buf str, path: &'buf str, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> {
|
||||
let mut http = vec![
|
||||
("method", U::Text(method)),
|
||||
("path", U::Text(path)),
|
||||
].into_iter().collect();
|
||||
fn write_dict_req<'a, 'buf>(
|
||||
method: &'buf str,
|
||||
path: &'buf str,
|
||||
headers: &'a HashMap<String, U<'a>>,
|
||||
) -> std::io::Result<()> {
|
||||
let mut http = vec![("method", U::Text(method)), ("path", U::Text(path))]
|
||||
.into_iter()
|
||||
.collect();
|
||||
write_dict(http, headers)
|
||||
}
|
||||
|
||||
fn write_dict_resp<'a, 'buf>(code: u16, reason: &'buf str, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> {
|
||||
fn write_dict_resp<'a, 'buf>(
|
||||
code: u16,
|
||||
reason: &'buf str,
|
||||
headers: &'a HashMap<String, U<'a>>,
|
||||
) -> std::io::Result<()> {
|
||||
let mut http = vec![
|
||||
("status", U::N6(code as u64)),
|
||||
("status-text", U::Text(reason)),
|
||||
].into_iter().collect();
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
write_dict(http, headers)
|
||||
}
|
||||
|
||||
|
||||
fn write_dict<'buf, 'a>(mut http: HashMap<&str, U<'a>>, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> {
|
||||
match http.insert("headers", U::Record(
|
||||
headers.iter().map(|(k,v)| (k.as_str(), v.clone())).collect()
|
||||
)) {
|
||||
fn write_dict<'buf, 'a>(
|
||||
mut http: HashMap<&str, U<'a>>,
|
||||
headers: &'a HashMap<String, U<'a>>,
|
||||
) -> std::io::Result<()> {
|
||||
match http.insert(
|
||||
"headers",
|
||||
U::Record(
|
||||
headers
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_str(), v.clone()))
|
||||
.collect(),
|
||||
),
|
||||
) {
|
||||
None => (),
|
||||
Some(_) => panic!("read-http: headers already in dict"),
|
||||
};
|
||||
netencode::encode(
|
||||
&mut std::io::stdout(),
|
||||
&U::Record(http)
|
||||
)?;
|
||||
netencode::encode(&mut std::io::stdout(), &U::Record(http))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// iter helper
|
||||
|
||||
struct Chunkyboi<T> {
|
||||
|
@ -188,10 +229,7 @@ struct Chunkyboi<T> {
|
|||
impl<R: Read> Chunkyboi<R> {
|
||||
fn new(inner: R, chunksize: usize) -> Self {
|
||||
let buf = vec![0; chunksize];
|
||||
Chunkyboi {
|
||||
inner,
|
||||
buf
|
||||
}
|
||||
Chunkyboi { inner, buf }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +243,7 @@ impl<R: Read> Iterator for Chunkyboi<R> {
|
|||
// clone a new buffer so we can reuse the internal one
|
||||
Some(Ok(self.buf[..read].to_owned()))
|
||||
}
|
||||
Err(err) => Some(Err(err))
|
||||
Err(err) => Some(Err(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
use clap::Clap;
|
||||
|
||||
use crate::codegen;
|
||||
use crate::interpreter;
|
||||
use crate::parser;
|
||||
use crate::tc;
|
||||
use crate::Result;
|
||||
use crate::{codegen, interpreter, parser, tc, Result};
|
||||
|
||||
/// Evaluate an expression and print its result
|
||||
#[derive(Clap)]
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use nom::alt;
|
||||
use nom::character::complete::{digit1, multispace0, multispace1};
|
||||
use nom::{
|
||||
call, char, complete, delimited, do_parse, flat_map, many0, map, named, opt, parse_to,
|
||||
alt, call, char, complete, delimited, do_parse, flat_map, many0, map, named, opt, parse_to,
|
||||
preceded, separated_list0, separated_list1, tag, tuple,
|
||||
};
|
||||
use pratt::{Affix, Associativity, PrattParser, Precedence};
|
||||
|
|
|
@ -13,11 +13,8 @@ use futures::Future;
|
|||
use metrics_exporter_prometheus::PrometheusBuilder;
|
||||
use nix::pty::Winsize;
|
||||
use pty::ChildHandle;
|
||||
use thrussh::ChannelId;
|
||||
use thrussh::{
|
||||
server::{self, Auth, Session},
|
||||
CryptoVec,
|
||||
};
|
||||
use thrussh::server::{self, Auth, Session};
|
||||
use thrussh::{ChannelId, CryptoVec};
|
||||
use thrussh_keys::decode_secret_key;
|
||||
use thrussh_keys::key::KeyPair;
|
||||
use tokio::fs::File;
|
||||
|
|
|
@ -6,8 +6,7 @@ use std::task::{Context, Poll};
|
|||
|
||||
use eyre::{bail, Result};
|
||||
use futures::Future;
|
||||
use nix::pty::forkpty;
|
||||
use nix::pty::Winsize;
|
||||
use nix::pty::{forkpty, Winsize};
|
||||
use nix::sys::termios::Termios;
|
||||
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
|
||||
use nix::unistd::{ForkResult, Pid};
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::io::BufReader;
|
|||
const PART_2: bool = true;
|
||||
|
||||
fn day01(is_part2: bool, numbers: &Vec<i64>) -> Result<String, anyhow::Error> {
|
||||
//println!("{:?}", numbers);
|
||||
// println!("{:?}", numbers);
|
||||
|
||||
for n1 in numbers.iter() {
|
||||
for n2 in numbers.iter() {
|
||||
|
@ -50,7 +50,7 @@ fn parse(filename: &str) -> Result<Vec<i64>, anyhow::Error> {
|
|||
fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
//println!("{:?}", args);
|
||||
// println!("{:?}", args);
|
||||
if args.len() != 2 {
|
||||
return Err(anyhow!("usage: day01 input_file"));
|
||||
}
|
||||
|
|
|
@ -28,14 +28,19 @@ fn main() {
|
|||
|
||||
// Otherwise ask Nix to build it and inject the result.
|
||||
let output = Command::new("nix-build")
|
||||
.arg("-A").arg("web.atward.indexHtml")
|
||||
.arg("-A")
|
||||
.arg("web.atward.indexHtml")
|
||||
// ... assuming atward is at //web/atward ...
|
||||
.arg("../..")
|
||||
.output()
|
||||
.expect(ERROR_MESSAGE);
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("{}\nNix output: {}", ERROR_MESSAGE, String::from_utf8_lossy(&output.stderr));
|
||||
eprintln!(
|
||||
"{}\nNix output: {}",
|
||||
ERROR_MESSAGE,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
//! This module implements the database executor, which holds the
|
||||
//! database connection and performs queries on it.
|
||||
|
||||
use actix::prelude::*;
|
||||
use diesel::{self, sql_query};
|
||||
use diesel::sql_types::Text;
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{Pool, ConnectionManager};
|
||||
use crate::models::*;
|
||||
use crate::errors::{ConverseError, Result};
|
||||
use crate::models::*;
|
||||
use actix::prelude::*;
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use diesel::sql_types::Text;
|
||||
use diesel::{self, sql_query};
|
||||
|
||||
/// Raw PostgreSQL query used to perform full-text search on posts
|
||||
/// with a supplied phrase. For now, the query language is hardcoded
|
||||
|
@ -50,14 +50,12 @@ pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>);
|
|||
|
||||
impl DbExecutor {
|
||||
/// Request a list of threads.
|
||||
//
|
||||
// TODO(tazjin): This should support pagination.
|
||||
pub fn list_threads(&self) -> Result<Vec<ThreadIndex>> {
|
||||
use crate::schema::thread_index::dsl::*;
|
||||
|
||||
let conn = self.0.get()?;
|
||||
let results = thread_index
|
||||
.load::<ThreadIndex>(&conn)?;
|
||||
let results = thread_index.load::<ThreadIndex>(&conn)?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
|
@ -69,9 +67,7 @@ impl DbExecutor {
|
|||
|
||||
let conn = self.0.get()?;
|
||||
|
||||
let opt_user = users
|
||||
.filter(email.eq(email))
|
||||
.first(&conn).optional()?;
|
||||
let opt_user = users.filter(email.eq(email)).first(&conn).optional()?;
|
||||
|
||||
if let Some(user) = opt_user {
|
||||
Ok(user)
|
||||
|
@ -93,12 +89,11 @@ impl DbExecutor {
|
|||
|
||||
/// Fetch a specific thread and return it with its posts.
|
||||
pub fn get_thread(&self, thread_id: i32) -> Result<(Thread, Vec<SimplePost>)> {
|
||||
use crate::schema::threads::dsl::*;
|
||||
use crate::schema::simple_posts::dsl::id;
|
||||
use crate::schema::threads::dsl::*;
|
||||
|
||||
let conn = self.0.get()?;
|
||||
let thread_result: Thread = threads
|
||||
.find(thread_id).first(&conn)?;
|
||||
let thread_result: Thread = threads.find(thread_id).first(&conn)?;
|
||||
|
||||
let post_list = SimplePost::belonging_to(&thread_result)
|
||||
.order_by(id.asc())
|
||||
|
@ -127,8 +122,7 @@ impl DbExecutor {
|
|||
|
||||
/// Create a new thread.
|
||||
pub fn create_thread(&self, new_thread: NewThread, post_text: String) -> Result<Thread> {
|
||||
use crate::schema::threads;
|
||||
use crate::schema::posts;
|
||||
use crate::schema::{posts, threads};
|
||||
|
||||
let conn = self.0.get()?;
|
||||
|
||||
|
@ -161,20 +155,21 @@ impl DbExecutor {
|
|||
|
||||
let closed: bool = {
|
||||
use crate::schema::threads::dsl::*;
|
||||
threads.select(closed)
|
||||
threads
|
||||
.select(closed)
|
||||
.find(new_post.thread_id)
|
||||
.first(&conn)?
|
||||
};
|
||||
|
||||
if closed {
|
||||
return Err(ConverseError::ThreadClosed {
|
||||
id: new_post.thread_id
|
||||
})
|
||||
id: new_post.thread_id,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(diesel::insert_into(posts::table)
|
||||
.values(&new_post)
|
||||
.get_result(&conn)?)
|
||||
.values(&new_post)
|
||||
.get_result(&conn)?)
|
||||
}
|
||||
|
||||
/// Search for posts.
|
||||
|
@ -197,7 +192,6 @@ impl DbExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Old actor implementation:
|
||||
|
||||
impl Actor for DbExecutor {
|
||||
|
@ -216,9 +210,7 @@ message!(LookupOrCreateUser, Result<User>);
|
|||
impl Handler<LookupOrCreateUser> for DbExecutor {
|
||||
type Result = <LookupOrCreateUser as Message>::Result;
|
||||
|
||||
fn handle(&mut self,
|
||||
_: LookupOrCreateUser,
|
||||
_: &mut Self::Context) -> Self::Result {
|
||||
fn handle(&mut self, _: LookupOrCreateUser, _: &mut Self::Context) -> Self::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +230,9 @@ impl Handler<GetThread> for DbExecutor {
|
|||
|
||||
/// Message used to fetch a specific post.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GetPost { pub id: i32 }
|
||||
pub struct GetPost {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
message!(GetPost, Result<SimplePost>);
|
||||
|
||||
|
@ -296,7 +290,9 @@ impl Handler<CreatePost> for DbExecutor {
|
|||
|
||||
/// Message used to search for posts
|
||||
#[derive(Deserialize)]
|
||||
pub struct SearchPosts { pub query: String }
|
||||
pub struct SearchPosts {
|
||||
pub query: String,
|
||||
}
|
||||
message!(SearchPosts, Result<Vec<SearchResult>>);
|
||||
|
||||
impl Handler<SearchPosts> for DbExecutor {
|
||||
|
|
|
@ -21,17 +21,12 @@
|
|||
//! are established in a similar way as was tradition in
|
||||
//! `error_chain`, albeit manually.
|
||||
|
||||
use std::result;
|
||||
use actix_web::{ResponseError, HttpResponse};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use std::result;
|
||||
|
||||
// Modules with foreign errors:
|
||||
use actix;
|
||||
use actix_web;
|
||||
use askama;
|
||||
use diesel;
|
||||
use r2d2;
|
||||
use tokio_timer;
|
||||
use {actix, actix_web, askama, diesel, r2d2, tokio_timer};
|
||||
|
||||
pub type Result<T> = result::Result<T, ConverseError>;
|
||||
pub type ConverseResult<T> = result::Result<T, ConverseError>;
|
||||
|
@ -96,7 +91,9 @@ impl From<askama::Error> for ConverseError {
|
|||
|
||||
impl From<actix::MailboxError> for ConverseError {
|
||||
fn from(error: actix::MailboxError) -> ConverseError {
|
||||
ConverseError::Actix { error: Box::new(error) }
|
||||
ConverseError::Actix {
|
||||
error: Box::new(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,7 +133,7 @@ impl ResponseError for ConverseError {
|
|||
.header("Location", format!("/thread/{}#post-reply", id))
|
||||
.finish(),
|
||||
_ => HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(format!("An error occured: {}", self))
|
||||
.body(format!("An error occured: {}", self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,22 +23,22 @@
|
|||
//! the tera templates stored in the `/templates` directory in the
|
||||
//! project root.
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_web::*;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::identity::RequestIdentity;
|
||||
use actix_web::middleware::{Started, Middleware};
|
||||
use actix_web;
|
||||
use crate::db::*;
|
||||
use crate::errors::{ConverseResult, ConverseError};
|
||||
use futures::Future;
|
||||
use crate::errors::{ConverseError, ConverseResult};
|
||||
use crate::models::*;
|
||||
use crate::oidc::*;
|
||||
use crate::render::*;
|
||||
use actix::prelude::*;
|
||||
use actix_web;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::identity::RequestIdentity;
|
||||
use actix_web::middleware::{Middleware, Started};
|
||||
use actix_web::*;
|
||||
use futures::Future;
|
||||
|
||||
use rouille::{Request, Response};
|
||||
|
||||
type ConverseResponse = Box<dyn Future<Item=HttpResponse, Error=ConverseError>>;
|
||||
type ConverseResponse = Box<dyn Future<Item = HttpResponse, Error = ConverseError>>;
|
||||
|
||||
const HTML: &'static str = "text/html";
|
||||
const ANONYMOUS: i32 = 1;
|
||||
|
@ -84,23 +84,31 @@ pub fn get_user_id_rouille(_req: &Request) -> i32 {
|
|||
ANONYMOUS
|
||||
}
|
||||
|
||||
pub fn forum_thread_rouille(req: &Request, db: &DbExecutor, thread_id: i32)
|
||||
-> ConverseResult<Response> {
|
||||
pub fn forum_thread_rouille(
|
||||
req: &Request,
|
||||
db: &DbExecutor,
|
||||
thread_id: i32,
|
||||
) -> ConverseResult<Response> {
|
||||
let user = get_user_id_rouille(&req);
|
||||
let thread = db.get_thread(thread_id)?;
|
||||
Ok(Response::html(thread_page(user, thread.0, thread.1)?))
|
||||
}
|
||||
|
||||
/// This handler retrieves and displays a single forum thread.
|
||||
pub fn forum_thread(_: State<AppState>,
|
||||
_: HttpRequest<AppState>,
|
||||
_: Path<i32>) -> ConverseResponse {
|
||||
pub fn forum_thread(
|
||||
_: State<AppState>,
|
||||
_: HttpRequest<AppState>,
|
||||
_: Path<i32>,
|
||||
) -> ConverseResponse {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// This handler presents the user with the "New Thread" form.
|
||||
pub fn new_thread(state: State<AppState>) -> ConverseResponse {
|
||||
state.renderer.send(NewThreadPage::default()).flatten()
|
||||
state
|
||||
.renderer
|
||||
.send(NewThreadPage::default())
|
||||
.flatten()
|
||||
.map(|res| HttpResponse::Ok().content_type(HTML).body(res))
|
||||
.responder()
|
||||
}
|
||||
|
@ -113,9 +121,9 @@ pub struct NewThreadForm {
|
|||
|
||||
/// This handler receives a "New thread"-form and redirects the user
|
||||
/// to the new thread after creation.
|
||||
pub fn submit_thread((state, input, req): (State<AppState>,
|
||||
Form<NewThreadForm>,
|
||||
HttpRequest<AppState>)) -> ConverseResponse {
|
||||
pub fn submit_thread(
|
||||
(state, input, req): (State<AppState>, Form<NewThreadForm>, HttpRequest<AppState>),
|
||||
) -> ConverseResponse {
|
||||
// Trim whitespace out of inputs:
|
||||
let input = NewThreadForm {
|
||||
title: input.title.trim().into(),
|
||||
|
@ -124,7 +132,8 @@ pub fn submit_thread((state, input, req): (State<AppState>,
|
|||
|
||||
// Perform simple validation and abort here if it fails:
|
||||
if input.title.is_empty() || input.post.is_empty() {
|
||||
return state.renderer
|
||||
return state
|
||||
.renderer
|
||||
.send(NewThreadPage {
|
||||
alerts: vec![NEW_THREAD_LENGTH_ERR],
|
||||
title: Some(input.title),
|
||||
|
@ -147,14 +156,19 @@ pub fn submit_thread((state, input, req): (State<AppState>,
|
|||
post: input.post,
|
||||
};
|
||||
|
||||
state.db.send(msg)
|
||||
state
|
||||
.db
|
||||
.send(msg)
|
||||
.from_err()
|
||||
.and_then(move |res| {
|
||||
let thread = res?;
|
||||
info!("Created new thread \"{}\" with ID {}", thread.title, thread.id);
|
||||
info!(
|
||||
"Created new thread \"{}\" with ID {}",
|
||||
thread.title, thread.id
|
||||
);
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.header("Location", format!("/thread/{}", thread.id))
|
||||
.finish())
|
||||
.header("Location", format!("/thread/{}", thread.id))
|
||||
.finish())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
@ -167,9 +181,11 @@ pub struct NewPostForm {
|
|||
|
||||
/// This handler receives a "Reply"-form and redirects the user to the
|
||||
/// new post after creation.
|
||||
pub fn reply_thread(state: State<AppState>,
|
||||
input: Form<NewPostForm>,
|
||||
req: HttpRequest<AppState>) -> ConverseResponse {
|
||||
pub fn reply_thread(
|
||||
state: State<AppState>,
|
||||
input: Form<NewPostForm>,
|
||||
req: HttpRequest<AppState>,
|
||||
) -> ConverseResponse {
|
||||
let user_id = get_user_id(&req);
|
||||
|
||||
let new_post = NewPost {
|
||||
|
@ -178,14 +194,19 @@ pub fn reply_thread(state: State<AppState>,
|
|||
body: input.post.trim().into(),
|
||||
};
|
||||
|
||||
state.db.send(CreatePost(new_post))
|
||||
state
|
||||
.db
|
||||
.send(CreatePost(new_post))
|
||||
.flatten()
|
||||
.from_err()
|
||||
.and_then(move |post| {
|
||||
info!("Posted reply {} to thread {}", post.id, post.thread_id);
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.header("Location", format!("/thread/{}#post-{}", post.thread_id, post.id))
|
||||
.finish())
|
||||
.header(
|
||||
"Location",
|
||||
format!("/thread/{}#post-{}", post.thread_id, post.id),
|
||||
)
|
||||
.finish())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
@ -194,12 +215,16 @@ pub fn reply_thread(state: State<AppState>,
|
|||
/// the user attempts to edit a post that they do not have access to,
|
||||
/// they are currently ungracefully redirected back to the post
|
||||
/// itself.
|
||||
pub fn edit_form(state: State<AppState>,
|
||||
req: HttpRequest<AppState>,
|
||||
query: Path<GetPost>) -> ConverseResponse {
|
||||
pub fn edit_form(
|
||||
state: State<AppState>,
|
||||
req: HttpRequest<AppState>,
|
||||
query: Path<GetPost>,
|
||||
) -> ConverseResponse {
|
||||
let user_id = get_user_id(&req);
|
||||
|
||||
state.db.send(query.into_inner())
|
||||
state
|
||||
.db
|
||||
.send(query.into_inner())
|
||||
.flatten()
|
||||
.from_err()
|
||||
.and_then(move |post| {
|
||||
|
@ -227,17 +252,21 @@ pub fn edit_form(state: State<AppState>,
|
|||
|
||||
/// This handler "executes" an edit to a post if the current user owns
|
||||
/// the edited post.
|
||||
pub fn edit_post(state: State<AppState>,
|
||||
req: HttpRequest<AppState>,
|
||||
update: Form<UpdatePost>) -> ConverseResponse {
|
||||
pub fn edit_post(
|
||||
state: State<AppState>,
|
||||
req: HttpRequest<AppState>,
|
||||
update: Form<UpdatePost>,
|
||||
) -> ConverseResponse {
|
||||
let user_id = get_user_id(&req);
|
||||
|
||||
state.db.send(GetPost { id: update.post_id })
|
||||
state
|
||||
.db
|
||||
.send(GetPost { id: update.post_id })
|
||||
.flatten()
|
||||
.from_err()
|
||||
.and_then(move |post| {
|
||||
if user_id != 1 && post.user_id == user_id {
|
||||
Ok(())
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConverseError::PostEditForbidden {
|
||||
user: user_id,
|
||||
|
@ -247,24 +276,34 @@ pub fn edit_post(state: State<AppState>,
|
|||
})
|
||||
.and_then(move |_| state.db.send(update.0).from_err())
|
||||
.flatten()
|
||||
.map(|updated| HttpResponse::SeeOther()
|
||||
.header("Location", format!("/thread/{}#post-{}",
|
||||
updated.thread_id, updated.id))
|
||||
.finish())
|
||||
.map(|updated| {
|
||||
HttpResponse::SeeOther()
|
||||
.header(
|
||||
"Location",
|
||||
format!("/thread/{}#post-{}", updated.thread_id, updated.id),
|
||||
)
|
||||
.finish()
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
||||
/// This handler executes a full-text search on the forum database and
|
||||
/// displays the results to the user.
|
||||
pub fn search_forum(state: State<AppState>,
|
||||
query: Query<SearchPosts>) -> ConverseResponse {
|
||||
pub fn search_forum(state: State<AppState>, query: Query<SearchPosts>) -> ConverseResponse {
|
||||
let query_string = query.query.clone();
|
||||
state.db.send(query.into_inner())
|
||||
state
|
||||
.db
|
||||
.send(query.into_inner())
|
||||
.flatten()
|
||||
.and_then(move |results| state.renderer.send(SearchResultPage {
|
||||
results,
|
||||
query: query_string,
|
||||
}).from_err())
|
||||
.and_then(move |results| {
|
||||
state
|
||||
.renderer
|
||||
.send(SearchResultPage {
|
||||
results,
|
||||
query: query_string,
|
||||
})
|
||||
.from_err()
|
||||
})
|
||||
.flatten()
|
||||
.map(|res| HttpResponse::Ok().content_type(HTML).body(res))
|
||||
.responder()
|
||||
|
@ -272,11 +311,15 @@ pub fn search_forum(state: State<AppState>,
|
|||
|
||||
/// This handler initiates an OIDC login.
|
||||
pub fn login(state: State<AppState>) -> ConverseResponse {
|
||||
state.oidc.send(GetLoginUrl)
|
||||
state
|
||||
.oidc
|
||||
.send(GetLoginUrl)
|
||||
.from_err()
|
||||
.and_then(|url| Ok(HttpResponse::TemporaryRedirect()
|
||||
.header("Location", url)
|
||||
.finish()))
|
||||
.and_then(|url| {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.header("Location", url)
|
||||
.finish())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
||||
|
@ -286,21 +329,26 @@ pub fn login(state: State<AppState>) -> ConverseResponse {
|
|||
/// provider and a user lookup is performed. If a user with a matching
|
||||
/// email-address is found in the database, it is logged in -
|
||||
/// otherwise a new user is created.
|
||||
pub fn callback(state: State<AppState>,
|
||||
data: Form<CodeResponse>,
|
||||
req: HttpRequest<AppState>) -> ConverseResponse {
|
||||
state.oidc.send(RetrieveToken(data.0)).flatten()
|
||||
pub fn callback(
|
||||
state: State<AppState>,
|
||||
data: Form<CodeResponse>,
|
||||
req: HttpRequest<AppState>,
|
||||
) -> ConverseResponse {
|
||||
state
|
||||
.oidc
|
||||
.send(RetrieveToken(data.0))
|
||||
.flatten()
|
||||
.map(|author| LookupOrCreateUser {
|
||||
email: author.email,
|
||||
name: author.name,
|
||||
})
|
||||
.and_then(move |msg| state.db.send(msg).from_err()).flatten()
|
||||
.and_then(move |msg| state.db.send(msg).from_err())
|
||||
.flatten()
|
||||
.and_then(move |user| {
|
||||
info!("Completed login for user {} ({})", user.email, user.id);
|
||||
req.remember(user.id.to_string());
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.header("Location", "/")
|
||||
.finish())})
|
||||
Ok(HttpResponse::SeeOther().header("Location", "/").finish())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
||||
|
@ -317,9 +365,7 @@ impl EmbeddedFile for App<AppState> {
|
|||
fn static_file(self, path: &'static str, content: &'static [u8]) -> Self {
|
||||
self.route(path, Method::GET, move |_: HttpRequest<_>| {
|
||||
let mime = format!("{}", mime_guess::from_path(path).first_or_octet_stream());
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime.as_str())
|
||||
.body(content)
|
||||
HttpResponse::Ok().content_type(mime.as_str()).body(content)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -327,7 +373,7 @@ impl EmbeddedFile for App<AppState> {
|
|||
/// Middleware used to enforce logins unceremoniously.
|
||||
pub struct RequireLogin;
|
||||
|
||||
impl <S> Middleware<S> for RequireLogin {
|
||||
impl<S> Middleware<S> for RequireLogin {
|
||||
fn start(&self, req: &HttpRequest<S>) -> actix_web::Result<Started> {
|
||||
let logged_in = req.identity().is_some();
|
||||
let is_oidc_req = req.path().starts_with("/oidc");
|
||||
|
@ -336,7 +382,7 @@ impl <S> Middleware<S> for RequireLogin {
|
|||
Ok(Started::Response(
|
||||
HttpResponse::SeeOther()
|
||||
.header("Location", "/oidc/login")
|
||||
.finish()
|
||||
.finish(),
|
||||
))
|
||||
} else {
|
||||
Ok(Started::Done)
|
||||
|
|
|
@ -30,7 +30,6 @@ extern crate log;
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate rouille;
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate chrono;
|
||||
|
@ -44,6 +43,7 @@ extern crate md5;
|
|||
extern crate mime_guess;
|
||||
extern crate r2d2;
|
||||
extern crate rand;
|
||||
extern crate rouille;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate tokio;
|
||||
|
@ -58,7 +58,7 @@ macro_rules! message {
|
|||
impl Message for $t {
|
||||
type Result = $r;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod db;
|
||||
|
@ -69,18 +69,18 @@ pub mod oidc;
|
|||
pub mod render;
|
||||
pub mod schema;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_web::*;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||
use crate::db::*;
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use crate::handlers::*;
|
||||
use crate::oidc::OidcExecutor;
|
||||
use rand::{OsRng, Rng};
|
||||
use crate::render::Renderer;
|
||||
use actix::prelude::*;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::*;
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use rand::{OsRng, Rng};
|
||||
use std::env;
|
||||
|
||||
fn config(name: &str) -> String {
|
||||
|
@ -96,16 +96,18 @@ fn start_db_executor() -> Addr<DbExecutor> {
|
|||
let db_url = config("DATABASE_URL");
|
||||
|
||||
let manager = ConnectionManager::<PgConnection>::new(db_url);
|
||||
let pool = Pool::builder().build(manager).expect("Failed to initialise DB pool");
|
||||
let pool = Pool::builder()
|
||||
.build(manager)
|
||||
.expect("Failed to initialise DB pool");
|
||||
|
||||
SyncArbiter::start(2, move || DbExecutor(pool.clone()))
|
||||
}
|
||||
|
||||
fn schedule_search_refresh(db: Addr<DbExecutor>) {
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::prelude::*;
|
||||
use tokio::timer::Interval;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::thread;
|
||||
|
||||
let task = Interval::new(Instant::now(), Duration::from_secs(60))
|
||||
.from_err()
|
||||
|
@ -118,8 +120,8 @@ fn schedule_search_refresh(db: Addr<DbExecutor>) {
|
|||
fn start_oidc_executor(base_url: &str) -> Addr<OidcExecutor> {
|
||||
info!("Initialising OIDC integration ...");
|
||||
let oidc_url = config("OIDC_DISCOVERY_URL");
|
||||
let oidc_config = oidc::load_oidc(&oidc_url)
|
||||
.expect("Failed to retrieve OIDC discovery document");
|
||||
let oidc_config =
|
||||
oidc::load_oidc(&oidc_url).expect("Failed to retrieve OIDC discovery document");
|
||||
|
||||
let oidc = oidc::OidcExecutor {
|
||||
oidc_config,
|
||||
|
@ -132,7 +134,7 @@ fn start_oidc_executor(base_url: &str) -> Addr<OidcExecutor> {
|
|||
}
|
||||
|
||||
fn start_renderer() -> Addr<Renderer> {
|
||||
let comrak = comrak::ComrakOptions{
|
||||
let comrak = comrak::ComrakOptions {
|
||||
github_pre_lang: true,
|
||||
ext_strikethrough: true,
|
||||
ext_table: true,
|
||||
|
@ -143,22 +145,23 @@ fn start_renderer() -> Addr<Renderer> {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
Renderer{ comrak }.start()
|
||||
Renderer { comrak }.start()
|
||||
}
|
||||
|
||||
fn gen_session_key() -> [u8; 64] {
|
||||
let mut key_bytes = [0; 64];
|
||||
let mut rng = OsRng::new()
|
||||
.expect("Failed to retrieve RNG for key generation");
|
||||
let mut rng = OsRng::new().expect("Failed to retrieve RNG for key generation");
|
||||
rng.fill_bytes(&mut key_bytes);
|
||||
|
||||
key_bytes
|
||||
}
|
||||
|
||||
fn start_http_server(base_url: String,
|
||||
db_addr: Addr<DbExecutor>,
|
||||
oidc_addr: Addr<OidcExecutor>,
|
||||
renderer_addr: Addr<Renderer>) {
|
||||
fn start_http_server(
|
||||
base_url: String,
|
||||
db_addr: Addr<DbExecutor>,
|
||||
oidc_addr: Addr<OidcExecutor>,
|
||||
renderer_addr: Addr<Renderer>,
|
||||
) {
|
||||
info!("Initialising HTTP server ...");
|
||||
let bind_host = config_default("CONVERSE_BIND_HOST", "127.0.0.1:4567");
|
||||
let key = gen_session_key();
|
||||
|
@ -175,7 +178,7 @@ fn start_http_server(base_url: String,
|
|||
CookieIdentityPolicy::new(&key)
|
||||
.name("converse_auth")
|
||||
.path("/")
|
||||
.secure(base_url.starts_with("https"))
|
||||
.secure(base_url.starts_with("https")),
|
||||
);
|
||||
|
||||
let app = App::with_state(state)
|
||||
|
@ -183,25 +186,37 @@ fn start_http_server(base_url: String,
|
|||
.middleware(identity)
|
||||
.resource("/", |r| r.method(Method::GET).with(forum_index))
|
||||
.resource("/thread/new", |r| r.method(Method::GET).with(new_thread))
|
||||
.resource("/thread/submit", |r| r.method(Method::POST).with(submit_thread))
|
||||
.resource("/thread/reply", |r| r.method(Method::POST).with(reply_thread))
|
||||
.resource("/thread/submit", |r| {
|
||||
r.method(Method::POST).with(submit_thread)
|
||||
})
|
||||
.resource("/thread/reply", |r| {
|
||||
r.method(Method::POST).with(reply_thread)
|
||||
})
|
||||
.resource("/thread/{id}", |r| r.method(Method::GET).with(forum_thread))
|
||||
.resource("/post/{id}/edit", |r| r.method(Method::GET).with(edit_form))
|
||||
.resource("/post/edit", |r| r.method(Method::POST).with(edit_post))
|
||||
.resource("/search", |r| r.method(Method::GET).with(search_forum))
|
||||
.resource("/oidc/login", |r| r.method(Method::GET).with(login))
|
||||
.resource("/oidc/callback", |r| r.method(Method::POST).with(callback))
|
||||
.static_file("/static/highlight.css", include_bytes!("../static/highlight.css"))
|
||||
.static_file("/static/highlight.js", include_bytes!("../static/highlight.js"))
|
||||
.static_file(
|
||||
"/static/highlight.css",
|
||||
include_bytes!("../static/highlight.css"),
|
||||
)
|
||||
.static_file(
|
||||
"/static/highlight.js",
|
||||
include_bytes!("../static/highlight.js"),
|
||||
)
|
||||
.static_file("/static/styles.css", include_bytes!("../static/styles.css"));
|
||||
|
||||
if require_login {
|
||||
app.middleware(RequireLogin)
|
||||
} else {
|
||||
app
|
||||
}})
|
||||
.bind(&bind_host).expect(&format!("Could not bind on '{}'", bind_host))
|
||||
.start();
|
||||
}
|
||||
})
|
||||
.bind(&bind_host)
|
||||
.expect(&format!("Could not bind on '{}'", bind_host))
|
||||
.start();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
// along with this program. If not, see
|
||||
// <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::schema::{posts, simple_posts, threads, users};
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use crate::schema::{users, threads, posts, simple_posts};
|
||||
use diesel::sql_types::{Text, Integer};
|
||||
use diesel::sql_types::{Integer, Text};
|
||||
|
||||
/// Represents a single user in the Converse database. Converse does
|
||||
/// not handle logins itself, but rather looks them up based on the
|
||||
|
@ -85,21 +85,21 @@ pub struct ThreadIndex {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
#[table_name="threads"]
|
||||
#[table_name = "threads"]
|
||||
pub struct NewThread {
|
||||
pub title: String,
|
||||
pub user_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
#[table_name="users"]
|
||||
#[table_name = "users"]
|
||||
pub struct NewUser {
|
||||
pub email: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
#[table_name="posts"]
|
||||
#[table_name = "posts"]
|
||||
pub struct NewPost {
|
||||
pub thread_id: i32,
|
||||
pub body: String,
|
||||
|
|
|
@ -22,12 +22,12 @@
|
|||
//! Currently Converse only supports a single OIDC provider. Note that
|
||||
//! this has so far only been tested with Office365.
|
||||
|
||||
use actix::prelude::*;
|
||||
use crate::errors::*;
|
||||
use actix::prelude::*;
|
||||
use crimp::Request;
|
||||
use curl::easy::Form;
|
||||
use url::Url;
|
||||
use url_serde;
|
||||
use curl::easy::Form;
|
||||
|
||||
/// This structure represents the contents of an OIDC discovery
|
||||
/// document.
|
||||
|
@ -114,20 +114,30 @@ impl Handler<RetrieveToken> for OidcExecutor {
|
|||
debug!("Received OAuth2 code, requesting access_token");
|
||||
|
||||
let mut form = Form::new();
|
||||
form.part("client_id").contents(&self.client_id.as_bytes())
|
||||
.add().expect("critical error: invalid form data");
|
||||
form.part("client_id")
|
||||
.contents(&self.client_id.as_bytes())
|
||||
.add()
|
||||
.expect("critical error: invalid form data");
|
||||
|
||||
form.part("client_secret").contents(&self.client_secret.as_bytes())
|
||||
.add().expect("critical error: invalid form data");
|
||||
form.part("client_secret")
|
||||
.contents(&self.client_secret.as_bytes())
|
||||
.add()
|
||||
.expect("critical error: invalid form data");
|
||||
|
||||
form.part("grant_type").contents("authorization_code".as_bytes())
|
||||
.add().expect("critical error: invalid form data");
|
||||
form.part("grant_type")
|
||||
.contents("authorization_code".as_bytes())
|
||||
.add()
|
||||
.expect("critical error: invalid form data");
|
||||
|
||||
form.part("code").contents(&msg.0.code.as_bytes())
|
||||
.add().expect("critical error: invalid form data");
|
||||
form.part("code")
|
||||
.contents(&msg.0.code.as_bytes())
|
||||
.add()
|
||||
.expect("critical error: invalid form data");
|
||||
|
||||
form.part("redirect_uri").contents(&self.redirect_uri.as_bytes())
|
||||
.add().expect("critical error: invalid form data");
|
||||
form.part("redirect_uri")
|
||||
.contents(&self.redirect_uri.as_bytes())
|
||||
.add()
|
||||
.expect("critical error: invalid form data");
|
||||
|
||||
let response = Request::post(&self.oidc_config.token_endpoint)
|
||||
.user_agent(concat!("converse-", env!("CARGO_PKG_VERSION")))?
|
||||
|
@ -142,7 +152,8 @@ impl Handler<RetrieveToken> for OidcExecutor {
|
|||
.user_agent(concat!("converse-", env!("CARGO_PKG_VERSION")))?
|
||||
.header("Authorization", &bearer)?
|
||||
.send()?
|
||||
.as_json()?.body;
|
||||
.as_json()?
|
||||
.body;
|
||||
|
||||
Ok(Author {
|
||||
name: user.name,
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
//! data into whatever format is needed by the templates and rendering
|
||||
//! them.
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::models::*;
|
||||
use actix::prelude::*;
|
||||
use askama::Template;
|
||||
use crate::errors::*;
|
||||
use std::fmt;
|
||||
use md5;
|
||||
use crate::models::*;
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use comrak::{ComrakOptions, markdown_to_html};
|
||||
use comrak::{markdown_to_html, ComrakOptions};
|
||||
use md5;
|
||||
use std::fmt;
|
||||
|
||||
pub struct Renderer {
|
||||
pub comrak: ComrakOptions,
|
||||
|
@ -101,7 +101,9 @@ pub enum EditingMode {
|
|||
}
|
||||
|
||||
impl Default for EditingMode {
|
||||
fn default() -> EditingMode { EditingMode::NewThread }
|
||||
fn default() -> EditingMode {
|
||||
EditingMode::NewThread
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the template used for rendering the new thread, edit post
|
||||
|
@ -215,19 +217,22 @@ pub fn index_page(threads: Vec<ThreadIndex>) -> Result<String> {
|
|||
|
||||
// Render the page of a given thread.
|
||||
pub fn thread_page(user: i32, thread: Thread, posts: Vec<SimplePost>) -> Result<String> {
|
||||
let posts = posts.into_iter().map(|post| {
|
||||
let editable = user != 1 && post.user_id == user;
|
||||
let posts = posts
|
||||
.into_iter()
|
||||
.map(|post| {
|
||||
let editable = user != 1 && post.user_id == user;
|
||||
|
||||
let comrak = ComrakOptions::default(); // TODO(tazjin): cheddar
|
||||
RenderablePost {
|
||||
id: post.id,
|
||||
body: markdown_to_html(&post.body, &comrak),
|
||||
posted: FormattedDate(post.posted),
|
||||
author_name: post.author_name.clone(),
|
||||
author_gravatar: md5_hex(post.author_email.as_bytes()),
|
||||
editable,
|
||||
}
|
||||
}).collect();
|
||||
let comrak = ComrakOptions::default(); // TODO(tazjin): cheddar
|
||||
RenderablePost {
|
||||
id: post.id,
|
||||
body: markdown_to_html(&post.body, &comrak),
|
||||
posted: FormattedDate(post.posted),
|
||||
author_name: post.author_name.clone(),
|
||||
author_gravatar: md5_hex(post.author_email.as_bytes()),
|
||||
editable,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let renderable = RenderableThreadPage {
|
||||
posts,
|
||||
|
|
|
@ -80,9 +80,4 @@ joinable!(posts -> users (user_id));
|
|||
joinable!(threads -> users (user_id));
|
||||
joinable!(simple_posts -> threads (thread_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
posts,
|
||||
threads,
|
||||
users,
|
||||
simple_posts,
|
||||
);
|
||||
allow_tables_to_appear_in_same_query!(posts, threads, users, simple_posts,);
|
||||
|
|
Loading…
Reference in a new issue