Skip to content

Commit

Permalink
Merge pull request #107 from tomaka/ssl
Browse files Browse the repository at this point in the history
Implement HTTPS
  • Loading branch information
frewsxcv committed Nov 24, 2015
2 parents c00b616 + f05deed commit ce10419
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 83 deletions.
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
language: rust

env:
matrix:
- FEATURES=''
- FEATURES='ssl'

rust:
- stable
- beta
- nightly

script:
- cargo build -v --no-default-features --features "$FEATURES"
- cargo test -v -j 1 --no-default-features --features "$FEATURES"

after_success: |
cargo doc && \
echo '<meta http-equiv=refresh content=0;url=tiny_http/index.html>' > target/doc/index.html && \
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ keywords = ["http", "server", "web"]
license = "Apache-2.0"
repository = "https://github.com/frewsxcv/tiny-http"

[features]
default = []
ssl = ["openssl"]

[dependencies]
ascii = "0.5"
chunked_transfer = "0.3"
encoding = "0.2"
openssl = { version = "0.7", optional = true }
rustc-serialize = "0.3"
url = "0.2"
chrono = "0.2.15"
15 changes: 15 additions & 0 deletions examples/ssl-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICUzCCAbwCCQCBrI63G7tNbzANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJO
TzENMAsGA1UECAwETm9uZTENMAsGA1UEBwwETm9uZTENMAsGA1UECgwETm9uZTEN
MAsGA1UECwwETm9uZTENMAsGA1UEAwwETm9uZTETMBEGCSqGSIb3DQEJARYETm9u
ZTAgFw0xNTExMjQxNDIwNDVaGA8yMDcwMDgyNzE0MjA0NVowbTELMAkGA1UEBhMC
Tk8xDTALBgNVBAgMBE5vbmUxDTALBgNVBAcMBE5vbmUxDTALBgNVBAoMBE5vbmUx
DTALBgNVBAsMBE5vbmUxDTALBgNVBAMMBE5vbmUxEzARBgkqhkiG9w0BCQEWBE5v
bmUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMKwa7v9rjWAcGNieV33ePxA
b+JtvyY12CuX2nR4jVllGubJhyaqKOpM1TryWo6B9nc7bSe4wO23VBqd4e8WwBW3
GD0qEuf0qFF/VyUZAZ3qJRqs1HUnBNMTC+BWdFNMzi8NTvnbHXZad5ECuFcQoiCk
qFPa2ixopNejZTNwNA2tAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAC9rxM6bMSsJH
b2SSzKh5m/cVJFC9yNWl2w2ge3Hm1TSJv80X3jUUQ2DLnPM3sE6yd1rLfq10qiVc
SbbWihmmBzx0OJMlo6Eg29m2Q5chV6mzBbfYZcbAE3iceDj6JXJLjJoqHs+39X/R
IP/a+TP8gZZ8VfvV3Z0tfqT5e8AkvPM=
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions examples/ssl-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDCsGu7/a41gHBjYnld93j8QG/ibb8mNdgrl9p0eI1ZZRrmyYcm
qijqTNU68lqOgfZ3O20nuMDtt1QaneHvFsAVtxg9KhLn9KhRf1clGQGd6iUarNR1
JwTTEwvgVnRTTM4vDU752x12WneRArhXEKIgpKhT2tosaKTXo2UzcDQNrQIDAQAB
AoGABDx2e6avbbaXu3HfFi5WUZbNWG3u5NPzGi+5ryMYYEOU7ESiTjMRpNd7JEc1
tTgatslyPJUGjaWZjOK2kc866rRSw38y3bH8AhMdcvpBn27BOoi5RgFrtpwe8CAQ
rZ7PcXusKDNzhFyaue4a2qUqszcv8nMhOtVE0ZmIrmOUOQECQQDmi0HfzJvE01pk
bRez6vZHnmsfb8jzR1+yUMPeiIqelESdlApnaT7PIo1JapAfQHOznB6ZPbdnXdyj
3rkv2zFhAkEA2C+pbzD0EV3AVBaOtFR3dHMQ3MbroPDJ8BJQtYhicbcrlSK48n94
goUH4hglAl/Pyg/dGLzHIxcSRdkScLVjzQJBANy503YMFc8ac97Wu+zcNrNXL0TH
5+NUIIE+5mj23ZD6b79W76cWkrYKZK83wYjKUnxSKtGYzzG+IfMa2L7C48ECQQCK
kxqfrJh2XUsAW6lDzHT5zxw6+MNnWZGH8qWLh43a6JfmM+irgKwltdJUyjdG61WN
Z1fJGJDpXEuZPEjGuG7tAkAMsCe/44GCLSzSiORACdtyO63Cu6JCz2dqdYtTgJdw
l+bnX9B8o4lTgltdJWQ/5DAoscNJTtnNBfj4xgxKIkfW
-----END RSA PRIVATE KEY-----
31 changes: 31 additions & 0 deletions examples/ssl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extern crate tiny_http;

#[cfg(not(feature = "ssl"))]
fn main() { println!("This example requires the `ssl` feature to be enabled"); }

#[cfg(feature = "ssl")]
fn main() {
use tiny_http::{Server, Response};

let server = Server::https("0.0.0.0:8000", tiny_http::SslConfig {
certificate: include_bytes!("ssl-cert.pem").to_vec(),
private_key: include_bytes!("ssl-key.pem").to_vec(),
}).unwrap();

println!("Note: connecting to this server will likely give you a warning from your browser \
because the connection is unsecure. This is because the certificate used by this \
example is self-signed. With a real certificate, you wouldn't get this warning.");

for request in server.incoming_requests() {
assert!(request.secure());

println!("received request! method: {:?}, url: {:?}, headers: {:?}",
request.method(),
request.url(),
request.headers()
);

let response = Response::from_string("hello world");
request.respond(response);
}
}
17 changes: 11 additions & 6 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::str::FromStr;

use common::{HTTPVersion, Method};
use util::{SequentialReader, SequentialReaderBuilder, SequentialWriterBuilder};
use util::ClosableTcpStream;
use util::RefinedTcpStream;

use Request;

Expand All @@ -36,17 +36,20 @@ pub struct ClientConnection {

// sequence of Readers to the stream, so that the data is not read in
// the wrong order
source: SequentialReaderBuilder<BufReader<ClosableTcpStream>>,
source: SequentialReaderBuilder<BufReader<RefinedTcpStream>>,

// sequence of Writers to the stream, to avoid writing response #2 before
// response #1
sink: SequentialWriterBuilder<BufWriter<ClosableTcpStream>>,
sink: SequentialWriterBuilder<BufWriter<RefinedTcpStream>>,

// Reader to read the next header from
next_header_source: SequentialReader<BufReader<ClosableTcpStream>>,
next_header_source: SequentialReader<BufReader<RefinedTcpStream>>,

// set to true if we know that the previous request is the last one
no_more_requests: bool,

// true if the connection goes through SSL
secure: bool,
}

/// Error that can happen when reading a request.
Expand All @@ -62,10 +65,11 @@ enum ReadError {

impl ClientConnection {
/// Creates a new ClientConnection that takes ownership of the TcpStream.
pub fn new(write_socket: ClosableTcpStream, mut read_socket: ClosableTcpStream)
pub fn new(write_socket: RefinedTcpStream, mut read_socket: RefinedTcpStream)
-> ClientConnection
{
let remote_addr = read_socket.peer_addr();
let secure = read_socket.secure();

let mut source = SequentialReaderBuilder::new(BufReader::with_capacity(1024, read_socket));
let first_header = source.next().unwrap();
Expand All @@ -76,6 +80,7 @@ impl ClientConnection {
remote_addr: remote_addr,
next_header_source: first_header,
no_more_requests: false,
secure: secure,
}
}

Expand Down Expand Up @@ -152,7 +157,7 @@ impl ClientConnection {
::std::mem::swap(&mut self.next_header_source, &mut data_source);

// building the next reader
let request = try!(::request::new_request(method, path, version.clone(),
let request = try!(::request::new_request(self.secure, method, path, version.clone(),
headers, self.remote_addr.as_ref().unwrap().clone(), data_source, writer)
.map_err(|e| {
use request;
Expand Down
105 changes: 95 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ extern crate encoding;
extern crate url;
extern crate chrono;

#[cfg(feature = "ssl")]
extern crate openssl;

use std::error::Error;
use std::io::Error as IoError;
use std::io::Result as IoResult;
use std::sync::Arc;
Expand Down Expand Up @@ -186,21 +190,47 @@ pub struct IncomingRequests<'a> {
pub struct ServerConfig<A> where A: ToSocketAddrs {
/// The addresses to listen to.
pub addr: A,

/// If `Some`, then the server will use SSL to encode the communications.
pub ssl: Option<SslConfig>,
}

/// Configuration of the server for SSL.
#[derive(Debug, Clone)]
pub struct SslConfig {
/// Contains the public certificate to send to clients.
pub certificate: Vec<u8>,
/// Contains the ultra-secret private key used to decode communications.
pub private_key: Vec<u8>,
}

impl Server {
/// Shortcut for a simple server on a specific address.
#[inline]
pub fn http<A>(addr: A) -> IoResult<Server>
pub fn http<A>(addr: A) -> Result<Server, Box<Error + Send + Sync + 'static>>
where A: ToSocketAddrs
{
Server::new(ServerConfig {
addr: addr,
ssl: None,
})
}

/// Shortcut for an HTTPS server on a specific address.
#[cfg(feature = "ssl")]
#[inline]
pub fn https<A>(addr: A, config: SslConfig)
-> Result<Server, Box<Error + Send + Sync + 'static>>
where A: ToSocketAddrs
{
Server::new(ServerConfig {
addr: addr,
ssl: Some(config),
})
}

/// Builds a new server that listens on the specified address.
pub fn new<A>(config: ServerConfig<A>) -> IoResult<Server>
pub fn new<A>(config: ServerConfig<A>) -> Result<Server, Box<Error + Send + Sync + 'static>>
where A: ToSocketAddrs
{
// building the "close" variable
Expand All @@ -213,6 +243,42 @@ impl Server {
(listener, local_addr)
};

// building the SSL capabilities
#[cfg(feature = "ssl")]
type SslContext = openssl::ssl::SslContext;
#[cfg(not(feature = "ssl"))]
type SslContext = ();
let ssl: Option<SslContext> = match config.ssl {
#[cfg(feature = "ssl")]
Some(mut config) => {
use std::io::Cursor;
use openssl::ssl;
use openssl::x509::X509;
use openssl::crypto::pkey::PKey;
use openssl::ssl::SSL_VERIFY_NONE;

let mut ctxt = try!(SslContext::new(ssl::SslMethod::Sslv23));
try!(ctxt.set_cipher_list("DEFAULT"));
let certificate = try!(X509::from_pem(&mut Cursor::new(&config.certificate[..])));
try!(ctxt.set_certificate(&certificate));
let private_key = try!(PKey::private_key_from_pem(&mut Cursor::new(&config.private_key[..])));
try!(ctxt.set_private_key(&private_key));
ctxt.set_verify(SSL_VERIFY_NONE, None);
try!(ctxt.check_private_key());

// let's wipe the certificate and private key from memory, because we're
// better safe than sorry
for b in &mut config.certificate { *b = 0; }
for b in &mut config.private_key { *b = 0; }

Some(ctxt)
},
#[cfg(not(feature = "ssl"))]
Some(_) => return Err("Building a server with SSL requires enabling the `ssl` feature \
in tiny-http".to_owned().into()),
None => None,
};

// creating a task where server.accept() is continuously called
// and ClientConnection objects are pushed in the messages queue
let messages = MessagesQueue::with_capacity(8);
Expand All @@ -224,14 +290,33 @@ impl Server {
let tasks_pool = util::TaskPool::new();

loop {
let new_client = server.accept().map(|(sock, _)| {
use util::ClosableTcpStream;

let read_closable = ClosableTcpStream::new(sock.try_clone().unwrap(), true, false);
let write_closable = ClosableTcpStream::new(sock, false, true);

ClientConnection::new(write_closable, read_closable)
});
let new_client = match server.accept() {
Ok((sock, _)) => {
use util::RefinedTcpStream;

let (read_closable, write_closable) = match ssl {
None => {
RefinedTcpStream::new(sock)
},
#[cfg(feature = "ssl")]
Some(ref ssl) => {
// trying to apply SSL over the connection
// if an error occurs, we just close the socket and resume listening
let sock = match openssl::ssl::SslStream::accept(ssl, sock) {
Ok(s) => s,
Err(_) => continue
};

RefinedTcpStream::new(sock)
},
#[cfg(not(feature = "ssl"))]
Some(_) => unreachable!(),
};

Ok(ClientConnection::new(write_closable, read_closable))
},
Err(e) => Err(e),
};

match new_client {
Ok(client) => {
Expand Down
12 changes: 11 additions & 1 deletion src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub struct Request {

remote_addr: SocketAddr,

// true if HTTPS, false if HTTP
secure: bool,

method: Method,

path: String,
Expand Down Expand Up @@ -104,7 +107,7 @@ impl From<IoError> for RequestCreationError {
/// It is the responsibility of the `Request` to read only the data of the request and not further.
///
/// The `Write` object will be used by the `Request` to write the response.
pub fn new_request<R, W>(method: Method, path: String,
pub fn new_request<R, W>(secure: bool, method: Method, path: String,
version: HTTPVersion, headers: Vec<Header>,
remote_addr: SocketAddr, mut source_data: R, writer: W)
-> Result<Request, RequestCreationError>
Expand Down Expand Up @@ -200,6 +203,7 @@ pub fn new_request<R, W>(method: Method, path: String,
data_reader: Some(reader),
response_writer: Some(Box::new(writer) as Box<Write + Send + 'static>),
remote_addr: remote_addr,
secure: secure,
method: method,
path: path,
http_version: version,
Expand All @@ -210,6 +214,12 @@ pub fn new_request<R, W>(method: Method, path: String,
}

impl Request {
/// Returns true if the request was made through HTTPS.
#[inline]
pub fn secure(&self) -> bool {
self.secure
}

/// Returns the method requested by the client (eg. `GET`, `POST`, etc.).
#[inline]
pub fn method(&self) -> &Method {
Expand Down
Loading

0 comments on commit ce10419

Please sign in to comment.