Browse Source

Initial commit

master
Colin Reeder 4 years ago
commit
d571431b9d
4 changed files with 1273 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +1103
    -0
      Cargo.lock
  3. +15
    -0
      Cargo.toml
  4. +153
    -0
      src/main.rs

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
/target
**/*.rs.bk

+ 1103
- 0
Cargo.lock
File diff suppressed because it is too large
View File


+ 15
- 0
Cargo.toml View File

@@ -0,0 +1,15 @@
[package]
name = "crepo"
version = "0.1.0"
authors = ["Colin Reeder <colin@vpzom.click>"]
edition = "2018"

[dependencies]
tokio = "0.1.21"
hyper = "0.12.30"
futures = "0.1.27"
hyper-tls = "0.3.2"
serde = "1.0.92"
serde_derive = "1.0.92"
serde_json = "1.0.39"
percent-encoding = "1.0.1"

+ 153
- 0
src/main.rs View File

@@ -0,0 +1,153 @@
use futures::{Future, IntoFuture, Stream};

#[derive(serde_derive::Deserialize)]
struct CrateInfoResponse {
#[serde(rename = "crate")]
crate_: CrateInfo,
}

#[derive(serde_derive::Deserialize)]
struct CrateInfo {
repository: Option<String>,
}

fn handle_query(
client: hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>,
req_path: &str,
) -> impl Future<Item = hyper::Response<hyper::Body>, Error = hyper::Error> {
let path = format!(
"/api/v1/crates/{}",
percent_encoding::utf8_percent_encode(req_path, percent_encoding::PATH_SEGMENT_ENCODE_SET)
);
let path_ref: &str = &path;
let req_path = req_path.to_owned();
hyper::Uri::builder()
.scheme("https")
.authority("crates.io")
.path_and_query(path_ref)
.build()
.map_err(|err| format!("Failed to construct API URI: {:?}", err))
.into_future()
.and_then(move |uri| {
let mut req = hyper::Request::new("".into());
*req.uri_mut() = uri;
req.headers_mut().insert(
hyper::header::USER_AGENT,
hyper::header::HeaderValue::from_static("crepo.top"),
);

client
.request(req)
.map_err(|err| format!("Failed to make API request: {:?}", err))
.and_then(|res| {
if res.status() == hyper::StatusCode::OK {
Ok(Some(res.into_body()))
} else if res.status() == hyper::StatusCode::NOT_FOUND {
Ok(None)
} else {
Err(format!(
"Unexpected response code from API: {}",
res.status()
))
}
})
})
.and_then(|body| match body {
Some(body) => futures::future::Either::A(
body.concat2()
.map_err(|err| format!("Failed to read API response: {:?}", err))
.and_then(|body| {
serde_json::from_slice(&body)
.map_err(|err| format!("Failed to parse API response: {:?}", err))
})
.map(Some),
),
None => futures::future::Either::B(futures::future::ok(None)),
})
.and_then(move |body: Option<CrateInfoResponse>| match body {
Some(body) => {
let (text, url) = match body.crate_.repository {
Some(url) => (format!("Redirecting to {}", url), url),
None => {
let url = format!("https://crates.io/crates/{}", req_path);
(format!("No repository found, redirecting to {}", url), url)
}
};

url.parse()
.map(|location| {
let mut resp = hyper::Response::new(text.into());
*resp.status_mut() = hyper::StatusCode::FOUND;
resp.headers_mut().insert(hyper::header::LOCATION, location);

resp
})
.map_err(|err| format!("Failed to set Location header: {:?}", err))
}
None => {
let mut resp = hyper::Response::new("Crate not found.".into());
*resp.status_mut() = hyper::StatusCode::NOT_FOUND;
resp.headers_mut().insert(
hyper::header::CONTENT_TYPE,
hyper::header::HeaderValue::from_static("text/plain"),
);

Ok(resp)
}
})
.or_else(|err| {
eprintln!("Error: {:?}", err);

let mut resp = hyper::Response::new("Internal Server Error".into());
*resp.status_mut() = hyper::StatusCode::INTERNAL_SERVER_ERROR;

Ok(resp)
})
}

fn main() {
let port = 2727;

let http_client = hyper::Client::builder()
.build(hyper_tls::HttpsConnector::new(4).expect("HttpsConnector initialization failed"));

tokio::run(
hyper::Server::bind(&std::net::SocketAddr::new(
std::net::Ipv6Addr::UNSPECIFIED.into(),
port,
))
.serve(move || {
hyper::service::service_fn({
let http_client = http_client.clone();
move |req| {
let mut path = req.uri().path();
println!("{}", path);
if path.len() > 0 && path.starts_with("/") {
path = &path[1..];
}
let path = path;

if path.len() == 0 {
futures::future::Either::A(futures::future::ok(hyper::Response::new(
hyper::Body::from("Usage: https://crepo.top/{crate}"),
)))
} else {
if path.contains("/") || path.contains(".") {
// quick check for invalid crate names
let mut resp = hyper::Response::new("Invalid crate name.".into());
*resp.status_mut() = hyper::StatusCode::NOT_FOUND;
resp.headers_mut().insert(
hyper::header::CONTENT_TYPE,
hyper::header::HeaderValue::from_static("text/plain"),
);
futures::future::Either::A(futures::future::ok(resp))
} else {
futures::future::Either::B(handle_query(http_client.clone(), path))
}
}
}
})
})
.map_err(|err| panic!("Server execution failed: {:?}", err)),
);
}

Loading…
Cancel
Save