From bbfef92e3d2a5e325668e07137dc531aff35a323 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Fri, 5 Jul 2013 10:16:04 +1000 Subject: Move `extra::net_*` to `extra::net::*` properly. Where * = tcp, ip, url. Formerly, extra::net::* were aliases of extra::net_*, but were the recommended path to use. Thus, the documentation talked of the `net_*` modules while everything else was written expecting `net::*`. This moves the actual modules so that `extra::net::*` is the actual location of the modules. This will naturally break any code which used `extra::net_*` directly. They should be altered to use `extra::net::*` (which has been the documented way of doing things for some time). This ensures that there is one, and only one, obvious way of doing things. --- src/libextra/extra.rs | 4 +- src/libextra/net.rs | 25 - src/libextra/net/ip.rs | 446 +++++++++++ src/libextra/net/mod.rs | 25 + src/libextra/net/tcp.rs | 1953 +++++++++++++++++++++++++++++++++++++++++++++++ src/libextra/net/url.rs | 1071 ++++++++++++++++++++++++++ src/libextra/net_ip.rs | 446 ----------- src/libextra/net_tcp.rs | 1953 ----------------------------------------------- src/libextra/net_url.rs | 1071 -------------------------- 9 files changed, 3496 insertions(+), 3498 deletions(-) delete mode 100644 src/libextra/net.rs create mode 100644 src/libextra/net/ip.rs create mode 100644 src/libextra/net/mod.rs create mode 100644 src/libextra/net/tcp.rs create mode 100644 src/libextra/net/url.rs delete mode 100644 src/libextra/net_ip.rs delete mode 100644 src/libextra/net_tcp.rs delete mode 100644 src/libextra/net_url.rs (limited to 'src/libextra') diff --git a/src/libextra/extra.rs b/src/libextra/extra.rs index 50c57b28d22..2d222099f2d 100644 --- a/src/libextra/extra.rs +++ b/src/libextra/extra.rs @@ -40,10 +40,8 @@ pub mod uv_ll; // General io and system-services modules +#[path = "net/mod.rs"] pub mod net; -pub mod net_ip; -pub mod net_tcp; -pub mod net_url; // libuv modules pub mod uv; diff --git a/src/libextra/net.rs b/src/libextra/net.rs deleted file mode 100644 index ca1cc123596..00000000000 --- a/src/libextra/net.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/*! -Top-level module for network-related functionality. - -Basically, including this module gives you: - -* `net_tcp` -* `net_ip` -* `net_url` - -See each of those three modules for documentation on what they do. -*/ - -pub use tcp = net_tcp; -pub use ip = net_ip; -pub use url = net_url; diff --git a/src/libextra/net/ip.rs b/src/libextra/net/ip.rs new file mode 100644 index 00000000000..8db5c65b750 --- /dev/null +++ b/src/libextra/net/ip.rs @@ -0,0 +1,446 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Types/fns concerning Internet Protocol (IP), versions 4 & 6 + +#[allow(missing_doc)]; + + +use std::libc; +use std::comm::{stream, SharedChan}; +use std::ptr; +use std::result; +use std::str; + +use iotask = uv::iotask::IoTask; +use interact = uv::iotask::interact; + +use sockaddr_in = super::super::uv_ll::sockaddr_in; +use sockaddr_in6 = super::super::uv_ll::sockaddr_in6; +use addrinfo = super::super::uv_ll::addrinfo; +use uv_getaddrinfo_t = super::super::uv_ll::uv_getaddrinfo_t; +use uv_ip4_name = super::super::uv_ll::ip4_name; +use uv_ip4_port = super::super::uv_ll::ip4_port; +use uv_ip6_name = super::super::uv_ll::ip6_name; +use uv_ip6_port = super::super::uv_ll::ip6_port; +use uv_getaddrinfo = super::super::uv_ll::getaddrinfo; +use uv_freeaddrinfo = super::super::uv_ll::freeaddrinfo; +use create_uv_getaddrinfo_t = super::super::uv_ll::getaddrinfo_t; +use set_data_for_req = super::super::uv_ll::set_data_for_req; +use get_data_for_req = super::super::uv_ll::get_data_for_req; +use ll = super::super::uv_ll; + +/// An IP address +pub enum IpAddr { + /// An IPv4 address + Ipv4(sockaddr_in), + Ipv6(sockaddr_in6) +} + +/// Human-friendly feedback on why a parse_addr attempt failed +pub struct ParseAddrErr { + err_msg: ~str, +} + +/** + * Convert a `IpAddr` to a str + * + * # Arguments + * + * * ip - a `extra::net::ip::IpAddr` + */ +pub fn format_addr(ip: &IpAddr) -> ~str { + match *ip { + Ipv4(ref addr) => unsafe { + let result = uv_ip4_name(addr); + if result == ~"" { + fail!("failed to convert inner sockaddr_in address to str") + } + result + }, + Ipv6(ref addr) => unsafe { + let result = uv_ip6_name(addr); + if result == ~"" { + fail!("failed to convert inner sockaddr_in address to str") + } + result + } + } +} + +/** + * Get the associated port + * + * # Arguments + * * ip - a `extra::net::ip::IpAddr` + */ +pub fn get_port(ip: &IpAddr) -> uint { + match *ip { + Ipv4(ref addr) => unsafe { + uv_ip4_port(addr) + }, + Ipv6(ref addr) => unsafe { + uv_ip6_port(addr) + } + } +} + +/// Represents errors returned from `net::ip::get_addr()` +enum IpGetAddrErr { + GetAddrUnknownError +} + +/** + * Attempts name resolution on the provided `node` string + * + * # Arguments + * + * * `node` - a string representing some host address + * * `iotask` - a `uv::iotask` used to interact with the underlying event loop + * + * # Returns + * + * A `result<~[ip_addr], ip_get_addr_err>` instance that will contain + * a vector of `ip_addr` results, in the case of success, or an error + * object in the case of failure +*/ +pub fn get_addr(node: &str, iotask: &iotask) + -> result::Result<~[IpAddr], IpGetAddrErr> { + let (output_po, output_ch) = stream(); + let mut output_ch = Some(SharedChan::new(output_ch)); + do str::as_buf(node) |node_ptr, len| { + let output_ch = output_ch.swap_unwrap(); + debug!("slice len %?", len); + let handle = create_uv_getaddrinfo_t(); + let handle_ptr: *uv_getaddrinfo_t = &handle; + let handle_data = GetAddrData { + output_ch: output_ch.clone() + }; + let handle_data_ptr: *GetAddrData = &handle_data; + do interact(iotask) |loop_ptr| { + unsafe { + let result = uv_getaddrinfo( + loop_ptr, + handle_ptr, + get_addr_cb, + node_ptr, + ptr::null(), + ptr::null()); + match result { + 0i32 => { + set_data_for_req(handle_ptr, handle_data_ptr); + } + _ => { + output_ch.send(result::Err(GetAddrUnknownError)); + } + } + } + }; + output_po.recv() + } +} + +pub mod v4 { + + use net::ip::{IpAddr, Ipv4, ParseAddrErr}; + use uv::ll; + use uv_ip4_addr = uv::ll::ip4_addr; + use uv_ip4_name = uv::ll::ip4_name; + + use std::cast::transmute; + use std::result; + use std::uint; + + /** + * Convert a str to `ip_addr` + * + * # Failure + * + * Fails if the string is not a valid IPv4 address + * + * # Arguments + * + * * ip - a string of the format `x.x.x.x` + * + * # Returns + * + * * an `ip_addr` of the `ipv4` variant + */ + pub fn parse_addr(ip: &str) -> IpAddr { + match try_parse_addr(ip) { + result::Ok(addr) => addr, + result::Err(ref err_data) => fail!(copy err_data.err_msg) + } + } + // the simple, old style numberic representation of + // ipv4 + pub struct Ipv4Rep { a: u8, b: u8, c: u8, d: u8 } + + pub trait AsUnsafeU32 { + unsafe fn as_u32(&self) -> u32; + } + + impl AsUnsafeU32 for Ipv4Rep { + // this is pretty dastardly, i know + unsafe fn as_u32(&self) -> u32 { + let this: &mut u32 = transmute(self); + *this + } + } + pub fn parse_to_ipv4_rep(ip: &str) -> result::Result { + let parts: ~[uint] = ip.split_iter('.').transform(|s| { + match uint::from_str(s) { + Some(n) if n <= 255 => n, + _ => 256 + } + }).collect(); + if parts.len() != 4 { + Err(fmt!("'%s' doesn't have 4 parts", ip)) + } else if parts.iter().any_(|x| *x == 256u) { + Err(fmt!("invalid octal in addr '%s'", ip)) + } else { + Ok(Ipv4Rep { + a: parts[0] as u8, b: parts[1] as u8, + c: parts[2] as u8, d: parts[3] as u8, + }) + } + } + pub fn try_parse_addr(ip: &str) -> result::Result { + unsafe { + let INADDR_NONE = ll::get_INADDR_NONE(); + let ip_rep_result = parse_to_ipv4_rep(ip); + if result::is_err(&ip_rep_result) { + let err_str = result::get_err(&ip_rep_result); + return result::Err(ParseAddrErr { err_msg: err_str }) + } + // ipv4_rep.as_u32 is unsafe :/ + let input_is_inaddr_none = + result::get(&ip_rep_result).as_u32() == INADDR_NONE; + + let new_addr = uv_ip4_addr(ip, 22); + let reformatted_name = uv_ip4_name(&new_addr); + debug!("try_parse_addr: input ip: %s reparsed ip: %s", + ip, reformatted_name); + let ref_ip_rep_result = parse_to_ipv4_rep(reformatted_name); + if result::is_err(&ref_ip_rep_result) { + let err_str = result::get_err(&ref_ip_rep_result); + return Err(ParseAddrErr { err_msg: err_str }) + } + + if result::get(&ref_ip_rep_result).as_u32() == INADDR_NONE && + !input_is_inaddr_none { + Err(ParseAddrErr { + err_msg: ~"uv_ip4_name produced invalid result.", + }) + } else { + Ok(Ipv4(copy(new_addr))) + } + } + } +} +pub mod v6 { + + use net::ip::{IpAddr, Ipv6, ParseAddrErr}; + use uv_ip6_addr = uv::ll::ip6_addr; + use uv_ip6_name = uv::ll::ip6_name; + + use std::result; + + /** + * Convert a str to `ip_addr` + * + * # Failure + * + * Fails if the string is not a valid IPv6 address + * + * # Arguments + * + * * ip - an ipv6 string. See RFC2460 for spec. + * + * # Returns + * + * * an `ip_addr` of the `ipv6` variant + */ + pub fn parse_addr(ip: &str) -> IpAddr { + match try_parse_addr(ip) { + result::Ok(addr) => addr, + result::Err(err_data) => fail!(copy err_data.err_msg) + } + } + pub fn try_parse_addr(ip: &str) -> result::Result { + unsafe { + // need to figure out how to establish a parse failure.. + let new_addr = uv_ip6_addr(ip, 22); + let reparsed_name = uv_ip6_name(&new_addr); + debug!("v6::try_parse_addr ip: '%s' reparsed '%s'", + ip, reparsed_name); + // '::' appears to be uv_ip6_name() returns for bogus + // parses.. + if ip != &"::" && reparsed_name == ~"::" { + Err(ParseAddrErr { err_msg:fmt!("failed to parse '%s'", ip) }) + } + else { + Ok(Ipv6(new_addr)) + } + } + } +} + +struct GetAddrData { + output_ch: SharedChan> +} + +extern fn get_addr_cb(handle: *uv_getaddrinfo_t, + status: libc::c_int, + res: *addrinfo) { + unsafe { + debug!("in get_addr_cb"); + let handle_data = get_data_for_req(handle) as + *GetAddrData; + let output_ch = (*handle_data).output_ch.clone(); + if status == 0i32 { + if res != (ptr::null::()) { + let mut out_vec = ~[]; + debug!("initial addrinfo: %?", res); + let mut curr_addr = res; + loop { + let new_ip_addr = if ll::is_ipv4_addrinfo(curr_addr) { + Ipv4(copy(( + *ll::addrinfo_as_sockaddr_in(curr_addr)))) + } + else if ll::is_ipv6_addrinfo(curr_addr) { + Ipv6(copy(( + *ll::addrinfo_as_sockaddr_in6(curr_addr)))) + } + else { + debug!("curr_addr is not of family AF_INET or \ + AF_INET6. Error."); + output_ch.send( + result::Err(GetAddrUnknownError)); + break; + }; + out_vec.push(new_ip_addr); + + let next_addr = ll::get_next_addrinfo(curr_addr); + if next_addr == ptr::null::() as *addrinfo { + debug!("null next_addr encountered. no mas"); + break; + } + else { + curr_addr = next_addr; + debug!("next_addr addrinfo: %?", curr_addr); + } + } + debug!("successful process addrinfo result, len: %?", + out_vec.len()); + output_ch.send(result::Ok(out_vec)); + } + else { + debug!("addrinfo pointer is NULL"); + output_ch.send( + result::Err(GetAddrUnknownError)); + } + } + else { + debug!("status != 0 error in get_addr_cb"); + output_ch.send( + result::Err(GetAddrUnknownError)); + } + if res != (ptr::null::()) { + uv_freeaddrinfo(res); + } + debug!("leaving get_addr_cb"); + } +} + +#[cfg(test)] +mod test { + + use net::ip::*; + use net::ip::v4; + use net::ip::v6; + use uv; + + use std::result; + + #[test] + fn test_ip_ipv4_parse_and_format_ip() { + let localhost_str = ~"127.0.0.1"; + assert!(format_addr(&v4::parse_addr(localhost_str)) + == localhost_str) + } + #[test] + fn test_ip_ipv6_parse_and_format_ip() { + let localhost_str = ~"::1"; + let format_result = format_addr(&v6::parse_addr(localhost_str)); + debug!("results: expected: '%s' actual: '%s'", + localhost_str, format_result); + assert_eq!(format_result, localhost_str); + } + #[test] + fn test_ip_ipv4_bad_parse() { + match v4::try_parse_addr("b4df00d") { + result::Err(ref err_info) => { + debug!("got error as expected %?", err_info); + assert!(true); + } + result::Ok(ref addr) => { + fail!("Expected failure, but got addr %?", addr); + } + } + } + #[test] + #[ignore(target_os="win32")] + fn test_ip_ipv6_bad_parse() { + match v6::try_parse_addr("::,~2234k;") { + result::Err(ref err_info) => { + debug!("got error as expected %?", err_info); + assert!(true); + } + result::Ok(ref addr) => { + fail!("Expected failure, but got addr %?", addr); + } + } + } + #[test] + #[ignore(reason = "valgrind says it's leaky")] + fn test_ip_get_addr() { + let localhost_name = ~"localhost"; + let iotask = &uv::global_loop::get(); + let ga_result = get_addr(localhost_name, iotask); + if result::is_err(&ga_result) { + fail!("got err result from net::ip::get_addr();") + } + // note really sure how to reliably test/assert + // this.. mostly just wanting to see it work, atm. + let results = result::unwrap(ga_result); + debug!("test_get_addr: Number of results for %s: %?", + localhost_name, results.len()); + for results.iter().advance |r| { + let ipv_prefix = match *r { + Ipv4(_) => ~"IPv4", + Ipv6(_) => ~"IPv6" + }; + debug!("test_get_addr: result %s: '%s'", + ipv_prefix, format_addr(r)); + } + // at least one result.. this is going to vary from system + // to system, based on stuff like the contents of /etc/hosts + assert!(!results.is_empty()); + } + #[test] + #[ignore(reason = "valgrind says it's leaky")] + fn test_ip_get_addr_bad_input() { + let localhost_name = ~"sjkl234m,./sdf"; + let iotask = &uv::global_loop::get(); + let ga_result = get_addr(localhost_name, iotask); + assert!(result::is_err(&ga_result)); + } +} diff --git a/src/libextra/net/mod.rs b/src/libextra/net/mod.rs new file mode 100644 index 00000000000..463260bd3dc --- /dev/null +++ b/src/libextra/net/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! +Top-level module for network-related functionality. + +Basically, including this module gives you: + +* `tcp` +* `ip` +* `url` + +See each of those three modules for documentation on what they do. +*/ + +pub mod tcp; +pub mod ip; +pub mod url; diff --git a/src/libextra/net/tcp.rs b/src/libextra/net/tcp.rs new file mode 100644 index 00000000000..e0ed313bc02 --- /dev/null +++ b/src/libextra/net/tcp.rs @@ -0,0 +1,1953 @@ +// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! High-level interface to libuv's TCP functionality +// FIXME #4425: Need FFI fixes + +#[allow(missing_doc)]; + + +use future; +use future_spawn = future::spawn; +use ip = net::ip; +use uv; +use uv::iotask; +use uv::iotask::IoTask; + +use std::io; +use std::libc::size_t; +use std::libc; +use std::comm::{stream, Port, SharedChan}; +use std::ptr; +use std::result::{Result}; +use std::result; +use std::uint; +use std::vec; + +pub mod rustrt { + use std::libc; + + #[nolink] + pub extern { + unsafe fn rust_uv_current_kernel_malloc(size: libc::c_uint) + -> *libc::c_void; + unsafe fn rust_uv_current_kernel_free(mem: *libc::c_void); + unsafe fn rust_uv_helper_uv_tcp_t_size() -> libc::c_uint; + } +} + +/** + * Encapsulates an open TCP/IP connection through libuv + * + * `TcpSocket` is non-copyable/sendable and automagically handles closing the + * underlying libuv data structures when it goes out of scope. This is the + * data structure that is used for read/write operations over a TCP stream. + */ +pub struct TcpSocket { + socket_data: @TcpSocketData, +} + +#[unsafe_destructor] +impl Drop for TcpSocket { + fn drop(&self) { + tear_down_socket_data(self.socket_data) + } +} + +pub fn TcpSocket(socket_data: @TcpSocketData) -> TcpSocket { + TcpSocket { + socket_data: socket_data + } +} + +/** + * A buffered wrapper for `net::tcp::TcpSocket` + * + * It is created with a call to `net::tcp::socket_buf()` and has impls that + * satisfy both the `io::Reader` and `io::Writer` traits. + */ +pub struct TcpSocketBuf { + data: @mut TcpBufferedSocketData, + end_of_stream: @mut bool +} + +pub fn TcpSocketBuf(data: @mut TcpBufferedSocketData) -> TcpSocketBuf { + TcpSocketBuf { + data: data, + end_of_stream: @mut false + } +} + +/// Contains raw, string-based, error information returned from libuv +pub struct TcpErrData { + err_name: ~str, + err_msg: ~str, +} + +/// Details returned as part of a `Result::Err` result from `tcp::listen` +pub enum TcpListenErrData { + /** + * Some unplanned-for error. The first and second fields correspond + * to libuv's `err_name` and `err_msg` fields, respectively. + */ + GenericListenErr(~str, ~str), + /** + * Failed to bind to the requested IP/Port, because it is already in use. + * + * # Possible Causes + * + * * Attempting to bind to a port already bound to another listener + */ + AddressInUse, + /** + * Request to bind to an IP/Port was denied by the system. + * + * # Possible Causes + * + * * Attemping to binding to an IP/Port as a non-Administrator + * on Windows Vista+ + * * Attempting to bind, as a non-priv'd + * user, to 'privileged' ports (< 1024) on *nix + */ + AccessDenied +} +/// Details returned as part of a `Result::Err` result from `tcp::connect` +pub enum TcpConnectErrData { + /** + * Some unplanned-for error. The first and second fields correspond + * to libuv's `err_name` and `err_msg` fields, respectively. + */ + GenericConnectErr(~str, ~str), + /// Invalid IP or invalid port + ConnectionRefused +} + +/** + * Initiate a client connection over TCP/IP + * + * # Arguments + * + * * `input_ip` - The IP address (versions 4 or 6) of the remote host + * * `port` - the unsigned integer of the desired remote host port + * * `iotask` - a `uv::iotask` that the tcp request will run on + * + * # Returns + * + * A `result` that, if the operation succeeds, contains a + * `net::net::TcpSocket` that can be used to send and receive data to/from + * the remote host. In the event of failure, a + * `net::tcp::TcpConnectErrData` instance will be returned + */ +pub fn connect(input_ip: ip::IpAddr, port: uint, + iotask: &IoTask) + -> result::Result { + unsafe { + let (result_po, result_ch) = stream::(); + let result_ch = SharedChan::new(result_ch); + let (closed_signal_po, closed_signal_ch) = stream::<()>(); + let closed_signal_ch = SharedChan::new(closed_signal_ch); + let conn_data = ConnectReqData { + result_ch: result_ch, + closed_signal_ch: closed_signal_ch + }; + let conn_data_ptr: *ConnectReqData = &conn_data; + let (reader_po, reader_ch) = stream::>(); + let reader_ch = SharedChan::new(reader_ch); + let stream_handle_ptr = malloc_uv_tcp_t(); + *(stream_handle_ptr as *mut uv::ll::uv_tcp_t) = uv::ll::tcp_t(); + let socket_data = @TcpSocketData { + reader_po: @reader_po, + reader_ch: reader_ch, + stream_handle_ptr: stream_handle_ptr, + connect_req: uv::ll::connect_t(), + write_req: uv::ll::write_t(), + ipv6: match input_ip { + ip::Ipv4(_) => { false } + ip::Ipv6(_) => { true } + }, + iotask: iotask.clone() + }; + let socket_data_ptr: *TcpSocketData = &*socket_data; + // get an unsafe representation of our stream_handle_ptr that + // we can send into the interact cb to be handled in libuv.. + debug!("stream_handle_ptr outside interact %?", + stream_handle_ptr); + do iotask::interact(iotask) |loop_ptr| { + debug!("in interact cb for tcp client connect.."); + debug!("stream_handle_ptr in interact %?", + stream_handle_ptr); + match uv::ll::tcp_init( loop_ptr, stream_handle_ptr) { + 0i32 => { + debug!("tcp_init successful"); + debug!("dealing w/ ipv4 connection.."); + let connect_req_ptr: *uv::ll::uv_connect_t = + &(*socket_data_ptr).connect_req; + let addr_str = ip::format_addr(&input_ip); + let connect_result = match input_ip { + ip::Ipv4(ref addr) => { + // have to "recreate" the + // sockaddr_in/6 since the ip_addr + // discards the port info.. should + // probably add an additional rust + // type that actually is closer to + // what the libuv API expects (ip str + // + port num) + debug!("addr: %?", addr); + let in_addr = uv::ll::ip4_addr(addr_str, + port as int); + uv::ll::tcp_connect( + connect_req_ptr, + stream_handle_ptr, + &in_addr, + tcp_connect_on_connect_cb) + } + ip::Ipv6(ref addr) => { + debug!("addr: %?", addr); + let in_addr = uv::ll::ip6_addr(addr_str, + port as int); + uv::ll::tcp_connect6( + connect_req_ptr, + stream_handle_ptr, + &in_addr, + tcp_connect_on_connect_cb) + } + }; + match connect_result { + 0i32 => { + debug!("tcp_connect successful: \ + stream %x, + socket data %x", + stream_handle_ptr as uint, + socket_data_ptr as uint); + // reusable data that we'll have for the + // duration.. + uv::ll::set_data_for_uv_handle( + stream_handle_ptr, + socket_data_ptr as + *libc::c_void); + // just so the connect_cb can send the + // outcome.. + uv::ll::set_data_for_req(connect_req_ptr, + conn_data_ptr); + debug!("leaving tcp_connect interact cb..."); + // let tcp_connect_on_connect_cb send on + // the result_ch, now.. + } + _ => { + // immediate connect + // failure.. probably a garbage ip or + // somesuch + let err_data = + uv::ll::get_last_err_data(loop_ptr); + let result_ch = (*conn_data_ptr) + .result_ch.clone(); + result_ch.send(ConnFailure(err_data)); + uv::ll::set_data_for_uv_handle( + stream_handle_ptr, + conn_data_ptr); + uv::ll::close(stream_handle_ptr, + stream_error_close_cb); + } + } + } + _ => { + // failure to create a tcp handle + let err_data = uv::ll::get_last_err_data(loop_ptr); + let result_ch = (*conn_data_ptr).result_ch.clone(); + result_ch.send(ConnFailure(err_data)); + } + } + } + match result_po.recv() { + ConnSuccess => { + debug!("tcp::connect - received success on result_po"); + result::Ok(TcpSocket(socket_data)) + } + ConnFailure(ref err_data) => { + closed_signal_po.recv(); + debug!("tcp::connect - received failure on result_po"); + // still have to free the malloc'd stream handle.. + rustrt::rust_uv_current_kernel_free(stream_handle_ptr + as *libc::c_void); + let tcp_conn_err = match err_data.err_name { + ~"ECONNREFUSED" => ConnectionRefused, + _ => GenericConnectErr(copy err_data.err_name, + copy err_data.err_msg) + }; + result::Err(tcp_conn_err) + } + } + } +} + +/** + * Write binary data to a tcp stream; Blocks until operation completes + * + * # Arguments + * + * * sock - a `TcpSocket` to write to + * * raw_write_data - a vector of `~[u8]` that will be written to the stream. + * This value must remain valid for the duration of the `write` call + * + * # Returns + * + * A `Result` object with a `()` value as the `Ok` variant, or a + * `TcpErrData` value as the `Err` variant + */ +pub fn write(sock: &TcpSocket, raw_write_data: ~[u8]) + -> result::Result<(), TcpErrData> { + let socket_data_ptr: *TcpSocketData = &*sock.socket_data; + write_common_impl(socket_data_ptr, raw_write_data) +} + +/** + * Write binary data to tcp stream; Returns a `future::Future` value + * immediately + * + * # Safety + * + * This function can produce unsafe results if: + * + * 1. the call to `write_future` is made + * 2. the `future::Future` value returned is never resolved via + * `Future::get` + * 3. and then the `TcpSocket` passed in to `write_future` leaves + * scope and is destructed before the task that runs the libuv write + * operation completes. + * + * As such: If using `write_future`, always be sure to resolve the returned + * `Future` so as to ensure libuv doesn't try to access a released write + * handle. Otherwise, use the blocking `tcp::write` function instead. + * + * # Arguments + * + * * sock - a `TcpSocket` to write to + * * raw_write_data - a vector of `~[u8]` that will be written to the stream. + * This value must remain valid for the duration of the `write` call + * + * # Returns + * + * A `Future` value that, once the `write` operation completes, resolves to a + * `Result` object with a `nil` value as the `Ok` variant, or a `TcpErrData` + * value as the `Err` variant + */ +pub fn write_future(sock: &TcpSocket, raw_write_data: ~[u8]) + -> future::Future> +{ + let socket_data_ptr: *TcpSocketData = &*sock.socket_data; + do future_spawn { + let data_copy = copy(raw_write_data); + write_common_impl(socket_data_ptr, data_copy) + } +} + +/** + * Begin reading binary data from an open TCP connection; used with + * `read_stop` + * + * # Arguments + * + * * sock -- a `net::tcp::TcpSocket` for the connection to read from + * + * # Returns + * + * * A `Result` instance that will either contain a + * `std::comm::Port>` that the user can read + * (and * optionally, loop on) from until `read_stop` is called, or a + * `TcpErrData` record + */ +pub fn read_start(sock: &TcpSocket) + -> result::Result<@Port>, + TcpErrData> { + let socket_data: *TcpSocketData = &*sock.socket_data; + read_start_common_impl(socket_data) +} + +/** + * Stop reading from an open TCP connection; used with `read_start` + * + * # Arguments + * + * * `sock` - a `net::tcp::TcpSocket` that you wish to stop reading on + */ +pub fn read_stop(sock: &TcpSocket) -> result::Result<(), TcpErrData> { + let socket_data: *TcpSocketData = &*sock.socket_data; + read_stop_common_impl(socket_data) +} + +/** + * Reads a single chunk of data from `TcpSocket`; block until data/error + * recv'd + * + * Does a blocking read operation for a single chunk of data from a + * `TcpSocket` until a data arrives or an error is received. The provided + * `timeout_msecs` value is used to raise an error if the timeout period + * passes without any data received. + * + * # Arguments + * + * * `sock` - a `net::tcp::TcpSocket` that you wish to read from + * * `timeout_msecs` - a `uint` value, in msecs, to wait before dropping the + * read attempt. Pass `0u` to wait indefinitely + */ +pub fn read(sock: &TcpSocket, timeout_msecs: uint) + -> result::Result<~[u8],TcpErrData> { + let socket_data: *TcpSocketData = &*sock.socket_data; + read_common_impl(socket_data, timeout_msecs) +} + +/** + * Reads a single chunk of data; returns a `future::Future<~[u8]>` + * immediately + * + * Does a non-blocking read operation for a single chunk of data from a + * `TcpSocket` and immediately returns a `Future` value representing the + * result. When resolving the returned `Future`, it will block until data + * arrives or an error is received. The provided `timeout_msecs` + * value is used to raise an error if the timeout period passes without any + * data received. + * + * # Safety + * + * This function can produce unsafe results if the call to `read_future` is + * made, the `future::Future` value returned is never resolved via + * `Future::get`, and then the `TcpSocket` passed in to `read_future` leaves + * scope and is destructed before the task that runs the libuv read + * operation completes. + * + * As such: If using `read_future`, always be sure to resolve the returned + * `Future` so as to ensure libuv doesn't try to access a released read + * handle. Otherwise, use the blocking `tcp::read` function instead. + * + * # Arguments + * + * * `sock` - a `net::tcp::TcpSocket` that you wish to read from + * * `timeout_msecs` - a `uint` value, in msecs, to wait before dropping the + * read attempt. Pass `0u` to wait indefinitely + */ +fn read_future(sock: &TcpSocket, timeout_msecs: uint) + -> future::Future> { + let socket_data: *TcpSocketData = &*sock.socket_data; + do future_spawn { + read_common_impl(socket_data, timeout_msecs) + } +} + +/** + * Bind an incoming client connection to a `net::tcp::TcpSocket` + * + * # Notes + * + * It is safe to call `net::tcp::accept` _only_ within the context of the + * `new_connect_cb` callback provided as the final argument to the + * `net::tcp::listen` function. + * + * The `new_conn` opaque value is provided _only_ as the first argument to the + * `new_connect_cb` provided as a part of `net::tcp::listen`. + * It can be safely sent to another task but it _must_ be + * used (via `net::tcp::accept`) before the `new_connect_cb` call it was + * provided to returns. + * + * This implies that a port/chan pair must be used to make sure that the + * `new_connect_cb` call blocks until an attempt to create a + * `net::tcp::TcpSocket` is completed. + * + * # Example + * + * Here, the `new_conn` is used in conjunction with `accept` from within + * a task spawned by the `new_connect_cb` passed into `listen` + * + * ~~~ {.rust} + * do net::tcp::listen(remote_ip, remote_port, backlog, iotask, + * // this callback is ran once after the connection is successfully + * // set up + * |kill_ch| { + * // pass the kill_ch to your main loop or wherever you want + * // to be able to externally kill the server from + * }) + * // this callback is ran when a new connection arrives + * |new_conn, kill_ch| { + * let (cont_po, cont_ch) = comm::stream::>(); + * do task::spawn { + * let accept_result = net::tcp::accept(new_conn); + * match accept_result { + * Err(accept_error) => { + * cont_ch.send(Some(accept_error)); + * // fail? + * }, + * Ok(sock) => { + * cont_ch.send(None); + * // do work here + * } + * } + * }; + * match cont_po.recv() { + * // shut down listen() + * Some(err_data) => kill_ch.send(Some(err_data)), + * // wait for next connection + * None => () + * } + * }; + * ~~~ + * + * # Arguments + * + * * `new_conn` - an opaque value used to create a new `TcpSocket` + * + * # Returns + * + * On success, this function will return a `net::tcp::TcpSocket` as the + * `Ok` variant of a `Result`. The `net::tcp::TcpSocket` is anchored within + * the task that `accept` was called within for its lifetime. On failure, + * this function will return a `net::tcp::TcpErrData` record + * as the `Err` variant of a `Result`. + */ +pub fn accept(new_conn: TcpNewConnection) + -> result::Result { + unsafe { + match new_conn{ + NewTcpConn(server_handle_ptr) => { + let server_data_ptr = uv::ll::get_data_for_uv_handle( + server_handle_ptr) as *TcpListenFcData; + let (reader_po, reader_ch) = stream::< + Result<~[u8], TcpErrData>>(); + let reader_ch = SharedChan::new(reader_ch); + let iotask = &(*server_data_ptr).iotask; + let stream_handle_ptr = malloc_uv_tcp_t(); + *(stream_handle_ptr as *mut uv::ll::uv_tcp_t) = + uv::ll::tcp_t(); + let client_socket_data: @TcpSocketData = @TcpSocketData { + reader_po: @reader_po, + reader_ch: reader_ch, + stream_handle_ptr : stream_handle_ptr, + connect_req : uv::ll::connect_t(), + write_req : uv::ll::write_t(), + ipv6: (*server_data_ptr).ipv6, + iotask : iotask.clone() + }; + let client_socket_data_ptr: *TcpSocketData = + &*client_socket_data; + let client_stream_handle_ptr = + (*client_socket_data_ptr).stream_handle_ptr; + + let (result_po, result_ch) = stream::>(); + let result_ch = SharedChan::new(result_ch); + + // UNSAFE LIBUV INTERACTION BEGIN + // .. normally this happens within the context of + // a call to uv::hl::interact.. but we're breaking + // the rules here because this always has to be + // called within the context of a listen() new_connect_cb + // callback (or it will likely fail and drown your cat) + debug!("in interact cb for tcp::accept"); + let loop_ptr = uv::ll::get_loop_for_uv_handle( + server_handle_ptr); + match uv::ll::tcp_init(loop_ptr, client_stream_handle_ptr) { + 0i32 => { + debug!("uv_tcp_init successful for \ + client stream"); + match uv::ll::accept( + server_handle_ptr as *libc::c_void, + client_stream_handle_ptr as *libc::c_void) { + 0i32 => { + debug!("successfully accepted client \ + connection: \ + stream %x, \ + socket data %x", + client_stream_handle_ptr as uint, + client_socket_data_ptr as uint); + uv::ll::set_data_for_uv_handle( + client_stream_handle_ptr, + client_socket_data_ptr + as *libc::c_void); + let ptr = uv::ll::get_data_for_uv_handle( + client_stream_handle_ptr); + debug!("ptrs: %x %x", + client_socket_data_ptr as uint, + ptr as uint); + result_ch.send(None); + } + _ => { + debug!("failed to accept client conn"); + result_ch.send(Some( + uv::ll::get_last_err_data( + loop_ptr).to_tcp_err())); + } + } + } + _ => { + debug!("failed to accept client stream"); + result_ch.send(Some( + uv::ll::get_last_err_data( + loop_ptr).to_tcp_err())); + } + } + // UNSAFE LIBUV INTERACTION END + match result_po.recv() { + Some(err_data) => result::Err(err_data), + None => result::Ok(TcpSocket(client_socket_data)) + } + } + } + } +} + +/** + * Bind to a given IP/port and listen for new connections + * + * # Arguments + * + * * `host_ip` - a `net::ip::IpAddr` representing a unique IP + * (versions 4 or 6) + * * `port` - a uint representing the port to listen on + * * `backlog` - a uint representing the number of incoming connections + * to cache in memory + * * `hl_loop` - a `uv_iotask::IoTask` that the tcp request will run on + * * `on_establish_cb` - a callback that is evaluated if/when the listener + * is successfully established. it takes no parameters + * * `new_connect_cb` - a callback to be evaluated, on the libuv thread, + * whenever a client attempts to conect on the provided ip/port. the + * callback's arguments are: + * * `new_conn` - an opaque type that can be passed to + * `net::tcp::accept` in order to be converted to a `TcpSocket`. + * * `kill_ch` - channel of type `std::comm::Chan>`. + * this channel can be used to send a message to cause `listen` to begin + * closing the underlying libuv data structures. + * + * # returns + * + * a `Result` instance containing empty data of type `()` on a + * successful/normal shutdown, and a `TcpListenErrData` enum in the event + * of listen exiting because of an error + */ +pub fn listen(host_ip: ip::IpAddr, port: uint, backlog: uint, + iotask: &IoTask, + on_establish_cb: ~fn(SharedChan>), + new_connect_cb: ~fn(TcpNewConnection, + SharedChan>)) + -> result::Result<(), TcpListenErrData> { + do listen_common(host_ip, port, backlog, iotask, + on_establish_cb) + // on_connect_cb + |handle| { + unsafe { + let server_data_ptr = uv::ll::get_data_for_uv_handle(handle) + as *TcpListenFcData; + let new_conn = NewTcpConn(handle); + let kill_ch = (*server_data_ptr).kill_ch.clone(); + new_connect_cb(new_conn, kill_ch); + } + } +} + +fn listen_common(host_ip: ip::IpAddr, + port: uint, + backlog: uint, + iotask: &IoTask, + on_establish_cb: ~fn(SharedChan>), + on_connect_cb: ~fn(*uv::ll::uv_tcp_t)) + -> result::Result<(), TcpListenErrData> { + let (stream_closed_po, stream_closed_ch) = stream::<()>(); + let stream_closed_ch = SharedChan::new(stream_closed_ch); + let (kill_po, kill_ch) = stream::>(); + let kill_ch = SharedChan::new(kill_ch); + let server_stream = uv::ll::tcp_t(); + let server_stream_ptr: *uv::ll::uv_tcp_t = &server_stream; + let server_data: TcpListenFcData = TcpListenFcData { + server_stream_ptr: server_stream_ptr, + stream_closed_ch: stream_closed_ch, + kill_ch: kill_ch.clone(), + on_connect_cb: on_connect_cb, + iotask: iotask.clone(), + ipv6: match &host_ip { + &ip::Ipv4(_) => { false } + &ip::Ipv6(_) => { true } + }, + active: @mut true + }; + let server_data_ptr: *TcpListenFcData = &server_data; + + let (setup_po, setup_ch) = stream(); + + // this is to address a compiler warning about + // an implicit copy.. it seems that double nested + // will defeat a move sigil, as is done to the host_ip + // arg above.. this same pattern works w/o complaint in + // tcp::connect (because the iotask::interact cb isn't + // nested within a std::comm::listen block) + let loc_ip = copy(host_ip); + do iotask::interact(iotask) |loop_ptr| { + unsafe { + match uv::ll::tcp_init(loop_ptr, server_stream_ptr) { + 0i32 => { + uv::ll::set_data_for_uv_handle( + server_stream_ptr, + server_data_ptr); + let addr_str = ip::format_addr(&loc_ip); + let bind_result = match loc_ip { + ip::Ipv4(ref addr) => { + debug!("addr: %?", addr); + let in_addr = uv::ll::ip4_addr( + addr_str, + port as int); + uv::ll::tcp_bind(server_stream_ptr, &in_addr) + } + ip::Ipv6(ref addr) => { + debug!("addr: %?", addr); + let in_addr = uv::ll::ip6_addr( + addr_str, + port as int); + uv::ll::tcp_bind6(server_stream_ptr, &in_addr) + } + }; + match bind_result { + 0i32 => { + match uv::ll::listen( + server_stream_ptr, + backlog as libc::c_int, + tcp_lfc_on_connection_cb) { + 0i32 => setup_ch.send(None), + _ => { + debug!( + "failure to uv_tcp_init"); + let err_data = + uv::ll::get_last_err_data( + loop_ptr); + setup_ch.send(Some(err_data)); + } + } + } + _ => { + debug!("failure to uv_tcp_bind"); + let err_data = uv::ll::get_last_err_data( + loop_ptr); + setup_ch.send(Some(err_data)); + } + } + } + _ => { + debug!("failure to uv_tcp_bind"); + let err_data = uv::ll::get_last_err_data( + loop_ptr); + setup_ch.send(Some(err_data)); + } + } + } + } + + let setup_result = setup_po.recv(); + + match setup_result { + Some(ref err_data) => { + do iotask::interact(iotask) |loop_ptr| { + unsafe { + debug!( + "tcp::listen post-kill recv hl interact %?", + loop_ptr); + *(*server_data_ptr).active = false; + uv::ll::close(server_stream_ptr, tcp_lfc_close_cb); + } + }; + stream_closed_po.recv(); + match err_data.err_name { + ~"EACCES" => { + debug!("Got EACCES error"); + result::Err(AccessDenied) + } + ~"EADDRINUSE" => { + debug!("Got EADDRINUSE error"); + result::Err(AddressInUse) + } + _ => { + debug!("Got '%s' '%s' libuv error", + err_data.err_name, err_data.err_msg); + result::Err( + GenericListenErr(copy err_data.err_name, + copy err_data.err_msg)) + } + } + } + None => { + on_establish_cb(kill_ch.clone()); + let kill_result = kill_po.recv(); + do iotask::interact(iotask) |loop_ptr| { + unsafe { + debug!( + "tcp::listen post-kill recv hl interact %?", + loop_ptr); + *(*server_data_ptr).active = false; + uv::ll::close(server_stream_ptr, tcp_lfc_close_cb); + } + }; + stream_closed_po.recv(); + match kill_result { + // some failure post bind/listen + Some(ref err_data) => result::Err(GenericListenErr( + copy err_data.err_name, + copy err_data.err_msg)), + // clean exit + None => result::Ok(()) + } + } + } +} + + +/** + * Convert a `net::tcp::TcpSocket` to a `net::tcp::TcpSocketBuf`. + * + * This function takes ownership of a `net::tcp::TcpSocket`, returning it + * stored within a buffered wrapper, which can be converted to a `io::Reader` + * or `io::Writer` + * + * # Arguments + * + * * `sock` -- a `net::tcp::TcpSocket` that you want to buffer + * + * # Returns + * + * A buffered wrapper that you can cast as an `io::Reader` or `io::Writer` + */ +pub fn socket_buf(sock: TcpSocket) -> TcpSocketBuf { + TcpSocketBuf(@mut TcpBufferedSocketData { + sock: sock, buf: ~[], buf_off: 0 + }) +} + +/// Convenience methods extending `net::tcp::TcpSocket` +impl TcpSocket { + pub fn read_start(&self) -> result::Result<@Port< + result::Result<~[u8], TcpErrData>>, TcpErrData> { + read_start(self) + } + pub fn read_stop(&self) -> + result::Result<(), TcpErrData> { + read_stop(self) + } + pub fn read(&self, timeout_msecs: uint) -> + result::Result<~[u8], TcpErrData> { + read(self, timeout_msecs) + } + pub fn read_future(&self, timeout_msecs: uint) -> + future::Future> { + read_future(self, timeout_msecs) + } + pub fn write(&self, raw_write_data: ~[u8]) + -> result::Result<(), TcpErrData> { + write(self, raw_write_data) + } + pub fn write_future(&self, raw_write_data: ~[u8]) + -> future::Future> { + write_future(self, raw_write_data) + } + pub fn get_peer_addr(&self) -> ip::IpAddr { + unsafe { + if self.socket_data.ipv6 { + let addr = uv::ll::ip6_addr("", 0); + uv::ll::tcp_getpeername6(self.socket_data.stream_handle_ptr, + &addr); + ip::Ipv6(addr) + } else { + let addr = uv::ll::ip4_addr("", 0); + uv::ll::tcp_getpeername(self.socket_data.stream_handle_ptr, + &addr); + ip::Ipv4(addr) + } + } + } +} + +/// Implementation of `io::Reader` trait for a buffered `net::tcp::TcpSocket` +impl io::Reader for TcpSocketBuf { + fn read(&self, buf: &mut [u8], len: uint) -> uint { + if len == 0 { return 0 } + let mut count: uint = 0; + + loop { + assert!(count < len); + + // If possible, copy up to `len` bytes from the internal + // `data.buf` into `buf` + let nbuffered = self.data.buf.len() - self.data.buf_off; + let needed = len - count; + if nbuffered > 0 { + unsafe { + let ncopy = uint::min(nbuffered, needed); + let dst = ptr::mut_offset( + vec::raw::to_mut_ptr(buf), count); + let src = ptr::offset( + vec::raw::to_ptr(self.data.buf), + self.data.buf_off); + ptr::copy_memory(dst, src, ncopy); + self.data.buf_off += ncopy; + count += ncopy; + } + } + + assert!(count <= len); + if count == len { + break; + } + + // We copied all the bytes we had in the internal buffer into + // the result buffer, but the caller wants more bytes, so we + // need to read in data from the socket. Note that the internal + // buffer is of no use anymore as we read all bytes from it, + // so we can throw it away. + let read_result = { + let data = &*self.data; + read(&data.sock, 0) + }; + if read_result.is_err() { + let err_data = read_result.get_err(); + + if err_data.err_name == ~"EOF" { + *self.end_of_stream = true; + break; + } else { + debug!("ERROR sock_buf as io::reader.read err %? %?", + err_data.err_name, err_data.err_msg); + // As we have already copied data into result buffer, + // we cannot simply return 0 here. Instead the error + // should show up in a later call to read(). + break; + } + } else { + self.data.buf = result::unwrap(read_result); + self.data.buf_off = 0; + } + } + + count + } + fn read_byte(&self) -> int { + loop { + if self.data.buf.len() > self.data.buf_off { + let c = self.data.buf[self.data.buf_off]; + self.data.buf_off += 1; + return c as int + } + + let read_result = { + let data = &*self.data; + read(&data.sock, 0) + }; + if read_result.is_err() { + let err_data = read_result.get_err(); + + if err_data.err_name == ~"EOF" { + *self.end_of_stream = true; + return -1 + } else { + debug!("ERROR sock_buf as io::reader.read err %? %?", + err_data.err_name, err_data.err_msg); + fail!() + } + } else { + self.data.buf = result::unwrap(read_result); + self.data.buf_off = 0; + } + } + } + fn eof(&self) -> bool { + *self.end_of_stream + } + fn seek(&self, dist: int, seek: io::SeekStyle) { + debug!("tcp_socket_buf seek stub %? %?", dist, seek); + // noop + } + fn tell(&self) -> uint { + 0u // noop + } +} + +/// Implementation of `io::Reader` trait for a buffered `net::tcp::TcpSocket` +impl io::Writer for TcpSocketBuf { + pub fn write(&self, data: &[u8]) { + let socket_data_ptr: *TcpSocketData = + &(*((*(self.data)).sock).socket_data); + let w_result = write_common_impl(socket_data_ptr, + data.slice(0, data.len()).to_owned()); + if w_result.is_err() { + let err_data = w_result.get_err(); + debug!( + "ERROR sock_buf as io::writer.writer err: %? %?", + err_data.err_name, err_data.err_msg); + } + } + fn seek(&self, dist: int, seek: io::SeekStyle) { + debug!("tcp_socket_buf seek stub %? %?", dist, seek); + // noop + } + fn tell(&self) -> uint { + 0u + } + fn flush(&self) -> int { + 0 + } + fn get_type(&self) -> io::WriterType { + io::File + } +} + +// INTERNAL API + +fn tear_down_socket_data(socket_data: @TcpSocketData) { + unsafe { + let (closed_po, closed_ch) = stream::<()>(); + let closed_ch = SharedChan::new(closed_ch); + let close_data = TcpSocketCloseData { + closed_ch: closed_ch + }; + let close_data_ptr: *TcpSocketCloseData = &close_data; + let stream_handle_ptr = (*socket_data).stream_handle_ptr; + do iotask::interact(&(*socket_data).iotask) |loop_ptr| { + debug!( + "interact dtor for tcp_socket stream %? loop %?", + stream_handle_ptr, loop_ptr); + uv::ll::set_data_for_uv_handle(stream_handle_ptr, + close_data_ptr); + uv::ll::close(stream_handle_ptr, tcp_socket_dtor_close_cb); + }; + closed_po.recv(); + //the line below will most likely crash + //log(debug, fmt!("about to free socket_data at %?", socket_data)); + rustrt::rust_uv_current_kernel_free(stream_handle_ptr + as *libc::c_void); + debug!("exiting dtor for tcp_socket"); + } +} + +// shared implementation for tcp::read +fn read_common_impl(socket_data: *TcpSocketData, timeout_msecs: uint) + -> result::Result<~[u8],TcpErrData> { + unsafe { + use timer; + + debug!("starting tcp::read"); + let iotask = &(*socket_data).iotask; + let rs_result = read_start_common_impl(socket_data); + if result::is_err(&rs_result) { + let err_data = result::get_err(&rs_result); + result::Err(err_data) + } + else { + debug!("tcp::read before recv_timeout"); + let read_result = if timeout_msecs > 0u { + timer::recv_timeout( + iotask, timeout_msecs, result::unwrap(rs_result)) + } else { + Some(result::get(&rs_result).recv()) + }; + debug!("tcp::read after recv_timeout"); + match read_result { + None => { + debug!("tcp::read: timed out.."); + let err_data = TcpErrData { + err_name: ~"TIMEOUT", + err_msg: ~"req timed out" + }; + read_stop_common_impl(socket_data); + result::Err(err_data) + } + Some(data_result) => { + debug!("tcp::read got data"); + read_stop_common_impl(socket_data); + data_result + } + } + } + } +} + +// shared impl for read_stop +fn read_stop_common_impl(socket_data: *TcpSocketData) -> + result::Result<(), TcpErrData> { + unsafe { + let stream_handle_ptr = (*socket_data).stream_handle_ptr; + let (stop_po, stop_ch) = stream::>(); + do iotask::interact(&(*socket_data).iotask) |loop_ptr| { + debug!("in interact cb for tcp::read_stop"); + match uv::ll::read_stop(stream_handle_ptr + as *uv::ll::uv_stream_t) { + 0i32 => { + debug!("successfully called uv_read_stop"); + stop_ch.send(None); + } + _ => { + debug!("failure in calling uv_read_stop"); + let err_data = uv::ll::get_last_err_data(loop_ptr); + stop_ch.send(Some(err_data.to_tcp_err())); + } + } + } + match stop_po.recv() { + Some(err_data) => Err(err_data), + None => Ok(()) + } + } +} + +// shared impl for read_start +fn read_start_common_impl(socket_data: *TcpSocketData) + -> result::Result<@Port< + result::Result<~[u8], TcpErrData>>, TcpErrData> { + unsafe { + let stream_handle_ptr = (*socket_data).stream_handle_ptr; + let (start_po, start_ch) = stream::>(); + debug!("in tcp::read_start before interact loop"); + do iotask::interact(&(*socket_data).iotask) |loop_ptr| { + debug!("in tcp::read_start interact cb %?", + loop_ptr); + match uv::ll::read_start(stream_handle_ptr + as *uv::ll::uv_stream_t, + on_alloc_cb, + on_tcp_read_cb) { + 0i32 => { + debug!("success doing uv_read_start"); + start_ch.send(None); + } + _ => { + debug!("error attempting uv_read_start"); + let err_data = uv::ll::get_last_err_data(loop_ptr); + start_ch.send(Some(err_data)); + } + } + } + match start_po.recv() { + Some(ref err_data) => result::Err( + err_data.to_tcp_err()), + None => { + result::Ok((*socket_data).reader_po) + } + } + } +} + +// helper to convert a "class" vector of [u8] to a *[uv::ll::uv_buf_t] + +// shared implementation used by write and write_future +fn write_common_impl(socket_data_ptr: *TcpSocketData, + raw_write_data: ~[u8]) + -> result::Result<(), TcpErrData> { + unsafe { + let write_req_ptr: *uv::ll::uv_write_t = + &(*socket_data_ptr).write_req; + let stream_handle_ptr = + (*socket_data_ptr).stream_handle_ptr; + let write_buf_vec = ~[ + uv::ll::buf_init(vec::raw::to_ptr(raw_write_data), + raw_write_data.len()) + ]; + let write_buf_vec_ptr: *~[uv::ll::uv_buf_t] = &write_buf_vec; + let (result_po, result_ch) = stream::(); + let result_ch = SharedChan::new(result_ch); + let write_data = WriteReqData { + result_ch: result_ch + }; + let write_data_ptr: *WriteReqData = &write_data; + do iotask::interact(&(*socket_data_ptr).iotask) |loop_ptr| { + debug!("in interact cb for tcp::write %?", + loop_ptr); + match uv::ll::write(write_req_ptr, + stream_handle_ptr, + write_buf_vec_ptr, + tcp_write_complete_cb) { + 0i32 => { + debug!("uv_write() invoked successfully"); + uv::ll::set_data_for_req(write_req_ptr, + write_data_ptr); + } + _ => { + debug!("error invoking uv_write()"); + let err_data = uv::ll::get_last_err_data(loop_ptr); + let result_ch = (*write_data_ptr).result_ch.clone(); + result_ch.send(TcpWriteError(err_data.to_tcp_err())); + } + } + } + // FIXME (#2656): Instead of passing unsafe pointers to local data, + // and waiting here for the write to complete, we should transfer + // ownership of everything to the I/O task and let it deal with the + // aftermath, so we don't have to sit here blocking. + match result_po.recv() { + TcpWriteSuccess => Ok(()), + TcpWriteError(err_data) => Err(err_data) + } + } +} + +enum TcpNewConnection { + NewTcpConn(*uv::ll::uv_tcp_t) +} + +struct TcpListenFcData { + server_stream_ptr: *uv::ll::uv_tcp_t, + stream_closed_ch: SharedChan<()>, + kill_ch: SharedChan>, + on_connect_cb: ~fn(*uv::ll::uv_tcp_t), + iotask: IoTask, + ipv6: bool, + active: @mut bool, +} + +extern fn tcp_lfc_close_cb(handle: *uv::ll::uv_tcp_t) { + unsafe { + let server_data_ptr = uv::ll::get_data_for_uv_handle( + handle) as *TcpListenFcData; + let stream_closed_ch = (*server_data_ptr).stream_closed_ch.clone(); + stream_closed_ch.send(()); + } +} + +extern fn tcp_lfc_on_connection_cb(handle: *uv::ll::uv_tcp_t, + status: libc::c_int) { + unsafe { + let server_data_ptr = uv::ll::get_data_for_uv_handle(handle) + as *TcpListenFcData; + let kill_ch = (*server_data_ptr).kill_ch.clone(); + if *(*server_data_ptr).active { + match status { + 0i32 => ((*server_data_ptr).on_connect_cb)(handle), + _ => { + let loop_ptr = uv::ll::get_loop_for_uv_handle(handle); + kill_ch.send( + Some(uv::ll::get_last_err_data(loop_ptr) + .to_tcp_err())); + *(*server_data_ptr).active = false; + } + } + } + } +} + +fn malloc_uv_tcp_t() -> *uv::ll::uv_tcp_t { + unsafe { + rustrt::rust_uv_current_kernel_malloc( + rustrt::rust_uv_helper_uv_tcp_t_size()) as *uv::ll::uv_tcp_t + } +} + +enum TcpConnectResult { + TcpConnected(TcpSocket), + TcpConnectError(TcpErrData) +} + +enum TcpWriteResult { + TcpWriteSuccess, + TcpWriteError(TcpErrData) +} + +enum TcpReadStartResult { + TcpReadStartSuccess(Port), + TcpReadStartError(TcpErrData) +} + +enum TcpReadResult { + TcpReadData(~[u8]), + TcpReadDone, + TcpReadErr(TcpErrData) +} + +trait ToTcpErr { + fn to_tcp_err(&self) -> TcpErrData; +} + +impl ToTcpErr for uv::ll::uv_err_data { + fn to_tcp_err(&self) -> TcpErrData { + TcpErrData { err_name: copy self.err_name, err_msg: copy self.err_msg } + } +} + +extern fn on_tcp_read_cb(stream: *uv::ll::uv_stream_t, + nread: libc::ssize_t, + buf: uv::ll::uv_buf_t) { + unsafe { + debug!("entering on_tcp_read_cb stream: %x nread: %?", + stream as uint, nread); + let loop_ptr = uv::ll::get_loop_for_uv_handle(stream); + let socket_data_ptr = uv::ll::get_data_for_uv_handle(stream) + as *TcpSocketData; + debug!("socket data is %x", socket_data_ptr as uint); + match nread as int { + // incoming err.. probably eof + -1 => { + let err_data = uv::ll::get_last_err_data(loop_ptr).to_tcp_err(); + debug!("on_tcp_read_cb: incoming err.. name %? msg %?", + err_data.err_name, err_data.err_msg); + let reader_ch = &(*socket_data_ptr).reader_ch; + reader_ch.send(result::Err(err_data)); + } + // do nothing .. unneeded buf + 0 => (), + // have data + _ => { + // we have data + debug!("tcp on_read_cb nread: %d", nread as int); + let reader_ch = &(*socket_data_ptr).reader_ch; + let buf_base = uv::ll::get_base_from_buf(buf); + let new_bytes = vec::from_buf(buf_base, nread as uint); + reader_ch.send(result::Ok(new_bytes)); + } + } + uv::ll::free_base_of_buf(buf); + debug!("exiting on_tcp_read_cb"); + } +} + +extern fn on_alloc_cb(handle: *libc::c_void, + suggested_size: size_t) + -> uv::ll::uv_buf_t { + unsafe { + debug!("tcp read on_alloc_cb!"); + let char_ptr = uv::ll::malloc_buf_base_of(suggested_size); + debug!("tcp read on_alloc_cb h: %? char_ptr: %u sugsize: %u", + handle, + char_ptr as uint, + suggested_size as uint); + uv::ll::buf_init(char_ptr, suggested_size as uint) + } +} + +struct TcpSocketCloseData { + closed_ch: SharedChan<()>, +} + +extern fn tcp_socket_dtor_close_cb(handle: *uv::ll::uv_tcp_t) { + unsafe { + let data = uv::ll::get_data_for_uv_handle(handle) + as *TcpSocketCloseData; + let closed_ch = (*data).closed_ch.clone(); + closed_ch.send(()); + debug!("tcp_socket_dtor_close_cb exiting.."); + } +} + +extern fn tcp_write_complete_cb(write_req: *uv::ll::uv_write_t, + status: libc::c_int) { + unsafe { + let write_data_ptr = uv::ll::get_data_for_req(write_req) + as *WriteReqData; + if status == 0i32 { + debug!("successful write complete"); + let result_ch = (*write_data_ptr).result_ch.clone(); + result_ch.send(TcpWriteSuccess); + } else { + let stream_handle_ptr = uv::ll::get_stream_handle_from_write_req( + write_req); + let loop_ptr = uv::ll::get_loop_for_uv_handle(stream_handle_ptr); + let err_data = uv::ll::get_last_err_data(loop_ptr); + debug!("failure to write"); + let result_ch = (*write_data_ptr).result_ch.clone(); + result_ch.send(TcpWriteError(err_data.to_tcp_err())); + } + } +} + +struct WriteReqData { + result_ch: SharedChan, +} + +struct ConnectReqData { + result_ch: SharedChan, + closed_signal_ch: SharedChan<()>, +} + +extern fn stream_error_close_cb(handle: *uv::ll::uv_tcp_t) { + unsafe { + let data = uv::ll::get_data_for_uv_handle(handle) as + *ConnectReqData; + let closed_signal_ch = (*data).closed_signal_ch.clone(); + closed_signal_ch.send(()); + debug!("exiting steam_error_close_cb for %?", handle); + } +} + +extern fn tcp_connect_close_cb(handle: *uv::ll::uv_tcp_t) { + debug!("closed client tcp handle %?", handle); +} + +extern fn tcp_connect_on_connect_cb(connect_req_ptr: *uv::ll::uv_connect_t, + status: libc::c_int) { + unsafe { + let conn_data_ptr = (uv::ll::get_data_for_req(connect_req_ptr) + as *ConnectReqData); + let result_ch = (*conn_data_ptr).result_ch.clone(); + debug!("tcp_connect result_ch %?", result_ch); + let tcp_stream_ptr = + uv::ll::get_stream_handle_from_connect_req(connect_req_ptr); + match status { + 0i32 => { + debug!("successful tcp connection!"); + result_ch.send(ConnSuccess); + } + _ => { + debug!("error in tcp_connect_on_connect_cb"); + let loop_ptr = uv::ll::get_loop_for_uv_handle(tcp_stream_ptr); + let err_data = uv::ll::get_last_err_data(loop_ptr); + debug!("err_data %? %?", err_data.err_name, + err_data.err_msg); + result_ch.send(ConnFailure(err_data)); + uv::ll::set_data_for_uv_handle(tcp_stream_ptr, + conn_data_ptr); + uv::ll::close(tcp_stream_ptr, stream_error_close_cb); + } + } + debug!("leaving tcp_connect_on_connect_cb"); + } +} + +enum ConnAttempt { + ConnSuccess, + ConnFailure(uv::ll::uv_err_data) +} + +struct TcpSocketData { + reader_po: @Port>, + reader_ch: SharedChan>, + stream_handle_ptr: *uv::ll::uv_tcp_t, + connect_req: uv::ll::uv_connect_t, + write_req: uv::ll::uv_write_t, + ipv6: bool, + iotask: IoTask, +} + +struct TcpBufferedSocketData { + sock: TcpSocket, + buf: ~[u8], + buf_off: uint +} + +#[cfg(test)] +mod test { + + use net::ip; + use net::tcp::{GenericListenErr, TcpConnectErrData, TcpListenErrData}; + use net::tcp::{connect, accept, read, listen, TcpSocket, socket_buf}; + use net; + use uv::iotask::IoTask; + use uv; + + use std::cell::Cell; + use std::comm::{stream, SharedChan}; + use std::io; + use std::result; + use std::str; + use std::task; + + // FIXME don't run on fbsd or linux 32 bit (#2064) + #[cfg(target_os="win32")] + #[cfg(target_os="darwin")] + #[cfg(target_os="linux")] + #[cfg(target_os="android")] + mod tcp_ipv4_server_and_client_test { + #[cfg(target_arch="x86_64")] + mod impl64 { + use net::tcp::test::*; + + #[test] + fn test_gl_tcp_server_and_client_ipv4() { + impl_gl_tcp_ipv4_server_and_client(); + } + #[test] + fn test_gl_tcp_get_peer_addr() { + impl_gl_tcp_ipv4_get_peer_addr(); + } + #[test] + fn test_gl_tcp_ipv4_client_error_connection_refused() { + impl_gl_tcp_ipv4_client_error_connection_refused(); + } + #[test] + fn test_gl_tcp_server_address_in_use() { + impl_gl_tcp_ipv4_server_address_in_use(); + } + #[test] + fn test_gl_tcp_server_access_denied() { + impl_gl_tcp_ipv4_server_access_denied(); + } + // Strange failure on Windows. --pcwalton + #[test] + #[ignore(cfg(target_os = "win32"))] + fn test_gl_tcp_ipv4_server_client_reader_writer() { + impl_gl_tcp_ipv4_server_client_reader_writer(); + } + #[test] + fn test_tcp_socket_impl_reader_handles_eof() { + impl_tcp_socket_impl_reader_handles_eof(); + } + } + #[cfg(target_arch="x86")] + #[cfg(target_arch="arm")] + #[cfg(target_arch="mips")] + mod impl32 { + use net::tcp::test::*; + + #[test] + #[ignore(cfg(target_os = "linux"))] + fn test_gl_tcp_server_and_client_ipv4() { + unsafe { + impl_gl_tcp_ipv4_server_and_client(); + } + } + #[test] + #[ignore(cfg(target_os = "linux"))] + fn test_gl_tcp_get_peer_addr() { + unsafe { + impl_gl_tcp_ipv4_get_peer_addr(); + } + } + #[test] + #[ignore(cfg(target_os = "linux"))] + fn test_gl_tcp_ipv4_client_error_connection_refused() { + unsafe { + impl_gl_tcp_ipv4_client_error_connection_refused(); + } + } + #[test] + #[ignore(cfg(target_os = "linux"))] + fn test_gl_tcp_server_address_in_use() { + unsafe { + impl_gl_tcp_ipv4_server_address_in_use(); + } + } + #[test] + #[ignore(cfg(target_os = "linux"))] + #[ignore(cfg(windows), reason = "deadlocking bots")] + fn test_gl_tcp_server_access_denied() { + unsafe { + impl_gl_tcp_ipv4_server_access_denied(); + } + } + #[test] + #[ignore(cfg(target_os = "linux"))] + #[ignore(cfg(target_os = "win32"))] + fn test_gl_tcp_ipv4_server_client_reader_writer() { + impl_gl_tcp_ipv4_server_client_reader_writer(); + } + } + } + pub fn impl_gl_tcp_ipv4_server_and_client() { + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 8888u; + let expected_req = ~"ping"; + let expected_resp = "pong"; + + let (server_result_po, server_result_ch) = stream::<~str>(); + + let (cont_po, cont_ch) = stream::<()>(); + let cont_ch = SharedChan::new(cont_ch); + // server + let hl_loop_clone = hl_loop.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + let cont_ch = cont_ch.clone(); + let actual_req = run_tcp_test_server( + server_ip, + server_port, + expected_resp.to_str(), + cont_ch.clone(), + &hl_loop_clone); + server_result_ch.send(actual_req); + }; + cont_po.recv(); + // client + debug!("server started, firing up client.."); + let actual_resp_result = run_tcp_test_client( + server_ip, + server_port, + expected_req, + hl_loop); + assert!(actual_resp_result.is_ok()); + let actual_resp = actual_resp_result.get(); + let actual_req = server_result_po.recv(); + debug!("REQ: expected: '%s' actual: '%s'", + expected_req, actual_req); + debug!("RESP: expected: '%s' actual: '%s'", + expected_resp, actual_resp); + assert!(actual_req.contains(expected_req)); + assert!(actual_resp.contains(expected_resp)); + } + pub fn impl_gl_tcp_ipv4_get_peer_addr() { + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 8887u; + let expected_resp = "pong"; + + let (cont_po, cont_ch) = stream::<()>(); + let cont_ch = SharedChan::new(cont_ch); + // server + let hl_loop_clone = hl_loop.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + let cont_ch = cont_ch.clone(); + run_tcp_test_server( + server_ip, + server_port, + expected_resp.to_str(), + cont_ch.clone(), + &hl_loop_clone); + }; + cont_po.recv(); + // client + debug!("server started, firing up client.."); + let server_ip_addr = ip::v4::parse_addr(server_ip); + let iotask = uv::global_loop::get(); + let connect_result = connect(server_ip_addr, server_port, + &iotask); + + let sock = result::unwrap(connect_result); + + debug!("testing peer address"); + // This is what we are actually testing! + assert!(net::ip::format_addr(&sock.get_peer_addr()) == + ~"127.0.0.1"); + assert_eq!(net::ip::get_port(&sock.get_peer_addr()), 8887); + + // Fulfill the protocol the test server expects + let resp_bytes = "ping".as_bytes().to_owned(); + tcp_write_single(&sock, resp_bytes); + debug!("message sent"); + sock.read(0u); + debug!("result read"); + } + pub fn impl_gl_tcp_ipv4_client_error_connection_refused() { + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 8889u; + let expected_req = ~"ping"; + // client + debug!("firing up client.."); + let actual_resp_result = run_tcp_test_client( + server_ip, + server_port, + expected_req, + hl_loop); + match actual_resp_result.get_err() { + ConnectionRefused => (), + _ => fail!("unknown error.. expected connection_refused") + } + } + pub fn impl_gl_tcp_ipv4_server_address_in_use() { + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 8890u; + let expected_req = ~"ping"; + let expected_resp = "pong"; + + let (cont_po, cont_ch) = stream::<()>(); + let cont_ch = SharedChan::new(cont_ch); + // server + let hl_loop_clone = hl_loop.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + let cont_ch = cont_ch.clone(); + run_tcp_test_server( + server_ip, + server_port, + expected_resp.to_str(), + cont_ch.clone(), + &hl_loop_clone); + } + cont_po.recv(); + // this one should fail.. + let listen_err = run_tcp_test_server_fail( + server_ip, + server_port, + hl_loop); + // client.. just doing this so that the first server tears down + debug!("server started, firing up client.."); + run_tcp_test_client( + server_ip, + server_port, + expected_req, + hl_loop); + match listen_err { + AddressInUse => { + assert!(true); + } + _ => { + fail!("expected address_in_use listen error, \ + but got a different error varient. check logs."); + } + } + } + pub fn impl_gl_tcp_ipv4_server_access_denied() { + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 80u; + // this one should fail.. + let listen_err = run_tcp_test_server_fail( + server_ip, + server_port, + hl_loop); + match listen_err { + AccessDenied => { + assert!(true); + } + _ => { + fail!("expected address_in_use listen error, \ + but got a different error varient. check logs."); + } + } + } + pub fn impl_gl_tcp_ipv4_server_client_reader_writer() { + + let iotask = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 8891u; + let expected_req = ~"ping"; + let expected_resp = "pong"; + + let (server_result_po, server_result_ch) = stream::<~str>(); + + let (cont_po, cont_ch) = stream::<()>(); + let cont_ch = SharedChan::new(cont_ch); + // server + let iotask_clone = iotask.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + let cont_ch = cont_ch.clone(); + let actual_req = run_tcp_test_server( + server_ip, + server_port, + expected_resp.to_str(), + cont_ch.clone(), + &iotask_clone); + server_result_ch.send(actual_req); + }; + cont_po.recv(); + // client + let server_addr = ip::v4::parse_addr(server_ip); + let conn_result = connect(server_addr, server_port, iotask); + if result::is_err(&conn_result) { + assert!(false); + } + let sock_buf = @socket_buf(result::unwrap(conn_result)); + buf_write(sock_buf, expected_req); + + // so contrived! + let actual_resp = buf_read(sock_buf, expected_resp.as_bytes().len()); + + let actual_req = server_result_po.recv(); + debug!("REQ: expected: '%s' actual: '%s'", + expected_req, actual_req); + debug!("RESP: expected: '%s' actual: '%s'", + expected_resp, actual_resp); + assert!(actual_req.contains(expected_req)); + assert!(actual_resp.contains(expected_resp)); + } + + pub fn impl_tcp_socket_impl_reader_handles_eof() { + use std::io::{Reader,ReaderUtil}; + + let hl_loop = &uv::global_loop::get(); + let server_ip = "127.0.0.1"; + let server_port = 10041u; + let expected_req = ~"GET /"; + let expected_resp = "A string\nwith multiple lines\n"; + + let (cont_po, cont_ch) = stream::<()>(); + let cont_ch = SharedChan::new(cont_ch); + // server + let hl_loop_clone = hl_loop.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + let cont_ch = cont_ch.clone(); + run_tcp_test_server( + server_ip, + server_port, + expected_resp.to_str(), + cont_ch.clone(), + &hl_loop_clone); + }; + cont_po.recv(); + // client + debug!("server started, firing up client.."); + let server_addr = ip::v4::parse_addr(server_ip); + let conn_result = connect(server_addr, server_port, hl_loop); + if result::is_err(&conn_result) { + assert!(false); + } + let sock_buf = @socket_buf(result::unwrap(conn_result)); + buf_write(sock_buf, expected_req); + + let buf_reader = sock_buf as @Reader; + let actual_response = str::from_bytes(buf_reader.read_whole_stream()); + debug!("Actual response: %s", actual_response); + assert!(expected_resp == actual_response); + } + + fn buf_write(w: &W, val: &str) { + debug!("BUF_WRITE: val len %?", val.len()); + let b_slice = val.as_bytes(); + debug!("BUF_WRITE: b_slice len %?", + b_slice.len()); + w.write(b_slice) + } + + fn buf_read(r: &R, len: uint) -> ~str { + let new_bytes = (*r).read_bytes(len); + debug!("in buf_read.. new_bytes len: %?", + new_bytes.len()); + str::from_bytes(new_bytes) + } + + fn run_tcp_test_server(server_ip: &str, server_port: uint, resp: ~str, + cont_ch: SharedChan<()>, + iotask: &IoTask) -> ~str { + let (server_po, server_ch) = stream::<~str>(); + let server_ch = SharedChan::new(server_ch); + let server_ip_addr = ip::v4::parse_addr(server_ip); + let resp_cell = Cell::new(resp); + let listen_result = listen(server_ip_addr, server_port, 128, + iotask, + // on_establish_cb -- called when listener is set up + |kill_ch| { + debug!("establish_cb %?", + kill_ch); + cont_ch.send(()); + }, + // risky to run this on the loop, but some users + // will want the POWER + |new_conn, kill_ch| { + let resp_cell2 = Cell::new(resp_cell.take()); + debug!("SERVER: new connection!"); + let (cont_po, cont_ch) = stream(); + let server_ch = server_ch.clone(); + do task::spawn_sched(task::ManualThreads(1u)) { + debug!("SERVER: starting worker for new req"); + + let accept_result = accept(new_conn); + debug!("SERVER: after accept()"); + if result::is_err(&accept_result) { + debug!("SERVER: error accept connection"); + let err_data = result::get_err(&accept_result); + kill_ch.send(Some(err_data)); + debug!( + "SERVER/WORKER: send on err cont ch"); + cont_ch.send(()); + } + else { + debug!("SERVER/WORKER: send on cont ch"); + cont_ch.send(()); + let sock = result::unwrap(accept_result); + let peer_addr = sock.get_peer_addr(); + debug!("SERVER: successfully accepted \ + connection from %s:%u", + ip::format_addr(&peer_addr), + ip::get_port(&peer_addr)); + let received_req_bytes = read(&sock, 0u); + match received_req_bytes { + result::Ok(data) => { + debug!("SERVER: got REQ str::from_bytes.."); + debug!("SERVER: REQ data len: %?", + data.len()); + server_ch.send( + str::from_bytes(data)); + debug!("SERVER: before write"); + let s = resp_cell2.take(); + tcp_write_single(&sock, s.as_bytes().to_owned()); + debug!("SERVER: after write.. die"); + kill_ch.send(None); + } + result::Err(err_data) => { + debug!("SERVER: error recvd: %s %s", + err_data.err_name, err_data.err_msg); + kill_ch.send(Some(err_data)); + server_ch.send(~""); + } + } + debug!("SERVER: worker spinning down"); + } + } + debug!("SERVER: waiting to recv on cont_ch"); + cont_po.recv(); + }); + // err check on listen_result + if result::is_err(&listen_result) { + match result::get_err(&listen_result) { + GenericListenErr(ref name, ref msg) => { + fail!("SERVER: exited abnormally name %s msg %s", *name, *msg); + } + AccessDenied => { + fail!("SERVER: exited abnormally, got access denied.."); + } + AddressInUse => { + fail!("SERVER: exited abnormally, got address in use..."); + } + } + } + let ret_val = server_po.recv(); + debug!("SERVER: exited and got return val: '%s'", ret_val); + ret_val + } + + fn run_tcp_test_server_fail(server_ip: &str, server_port: uint, + iotask: &IoTask) -> TcpListenErrData { + let server_ip_addr = ip::v4::parse_addr(server_ip); + let listen_result = listen(server_ip_addr, server_port, 128, + iotask, + // on_establish_cb -- called when listener is set up + |kill_ch| { + debug!("establish_cb %?", kill_ch); + }, + |new_conn, kill_ch| { + fail!("SERVER: shouldn't be called.. %? %?", new_conn, kill_ch); + }); + // err check on listen_result + if result::is_err(&listen_result) { + result::get_err(&listen_result) + } + else { + fail!("SERVER: did not fail as expected") + } + } + + fn run_tcp_test_client(server_ip: &str, server_port: uint, resp: &str, + iotask: &IoTask) -> result::Result<~str, + TcpConnectErrData> { + let server_ip_addr = ip::v4::parse_addr(server_ip); + + debug!("CLIENT: starting.."); + let connect_result = connect(server_ip_addr, server_port, + iotask); + if result::is_err(&connect_result) { + debug!("CLIENT: failed to connect"); + let err_data = result::get_err(&connect_result); + Err(err_data) + } + else { + let sock = result::unwrap(connect_result); + let resp_bytes = resp.as_bytes().to_owned(); + tcp_write_single(&sock, resp_bytes); + let read_result = sock.read(0u); + if read_result.is_err() { + debug!("CLIENT: failure to read"); + Ok(~"") + } + else { + let ret_val = str::from_bytes(read_result.get()); + debug!("CLIENT: after client_ch recv ret: '%s'", + ret_val); + Ok(ret_val) + } + } + } + + fn tcp_write_single(sock: &TcpSocket, val: ~[u8]) { + let mut write_result_future = sock.write_future(val); + let write_result = write_result_future.get(); + if result::is_err(&write_result) { + debug!("tcp_write_single: write failed!"); + let err_data = result::get_err(&write_result); + debug!("tcp_write_single err name: %s msg: %s", + err_data.err_name, err_data.err_msg); + // meh. torn on what to do here. + fail!("tcp_write_single failed"); + } + } +} diff --git a/src/libextra/net/url.rs b/src/libextra/net/url.rs new file mode 100644 index 00000000000..4ed8761cc11 --- /dev/null +++ b/src/libextra/net/url.rs @@ -0,0 +1,1071 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Types/fns concerning URLs (see RFC 3986) + +#[allow(missing_doc)]; + + +use std::cmp::Eq; +use std::io::{Reader, ReaderUtil}; +use std::io; +use std::hashmap::HashMap; +use std::to_bytes; +use std::uint; + +#[deriving(Clone, Eq)] +struct Url { + scheme: ~str, + user: Option, + host: ~str, + port: Option<~str>, + path: ~str, + query: Query, + fragment: Option<~str> +} + +#[deriving(Clone, Eq)] +struct UserInfo { + user: ~str, + pass: Option<~str> +} + +pub type Query = ~[(~str, ~str)]; + +impl Url { + pub fn new(scheme: ~str, + user: Option, + host: ~str, + port: Option<~str>, + path: ~str, + query: Query, + fragment: Option<~str>) + -> Url { + Url { + scheme: scheme, + user: user, + host: host, + port: port, + path: path, + query: query, + fragment: fragment, + } + } +} + +impl UserInfo { + pub fn new(user: ~str, pass: Option<~str>) -> UserInfo { + UserInfo { user: user, pass: pass } + } +} + +fn encode_inner(s: &str, full_url: bool) -> ~str { + do io::with_str_reader(s) |rdr| { + let mut out = ~""; + + while !rdr.eof() { + let ch = rdr.read_byte() as char; + match ch { + // unreserved: + 'A' .. 'Z' | + 'a' .. 'z' | + '0' .. '9' | + '-' | '.' | '_' | '~' => { + out.push_char(ch); + } + _ => { + if full_url { + match ch { + // gen-delims: + ':' | '/' | '?' | '#' | '[' | ']' | '@' | + + // sub-delims: + '!' | '$' | '&' | '"' | '(' | ')' | '*' | + '+' | ',' | ';' | '=' => { + out.push_char(ch); + } + + _ => out.push_str(fmt!("%%%X", ch as uint)) + } + } else { + out.push_str(fmt!("%%%X", ch as uint)); + } + } + } + } + + out + } +} + +/** + * Encodes a URI by replacing reserved characters with percent encoded + * character sequences. + * + * This function is compliant with RFC 3986. + */ +pub fn encode(s: &str) -> ~str { + encode_inner(s, true) +} + +/** + * Encodes a URI component by replacing reserved characters with percent + * encoded character sequences. + * + * This function is compliant with RFC 3986. + */ + +pub fn encode_component(s: &str) -> ~str { + encode_inner(s, false) +} + +fn decode_inner(s: &str, full_url: bool) -> ~str { + do io::with_str_reader(s) |rdr| { + let mut out = ~""; + + while !rdr.eof() { + match rdr.read_char() { + '%' => { + let bytes = rdr.read_bytes(2u); + let ch = uint::parse_bytes(bytes, 16u).get() as char; + + if full_url { + // Only decode some characters: + match ch { + // gen-delims: + ':' | '/' | '?' | '#' | '[' | ']' | '@' | + + // sub-delims: + '!' | '$' | '&' | '"' | '(' | ')' | '*' | + '+' | ',' | ';' | '=' => { + out.push_char('%'); + out.push_char(bytes[0u] as char); + out.push_char(bytes[1u] as char); + } + + ch => out.push_char(ch) + } + } else { + out.push_char(ch); + } + } + ch => out.push_char(ch) + } + } + + out + } +} + +/** + * Decode a string encoded with percent encoding. + * + * This will only decode escape sequences generated by encode. + */ +pub fn decode(s: &str) -> ~str { + decode_inner(s, true) +} + +/** + * Decode a string encoded with percent encoding. + */ +pub fn decode_component(s: &str) -> ~str { + decode_inner(s, false) +} + +fn encode_plus(s: &str) -> ~str { + do io::with_str_reader(s) |rdr| { + let mut out = ~""; + + while !rdr.eof() { + let ch = rdr.read_byte() as char; + match ch { + 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => { + out.push_char(ch); + } + ' ' => out.push_char('+'), + _ => out.push_str(fmt!("%%%X", ch as uint)) + } + } + + out + } +} + +/** + * Encode a hashmap to the 'application/x-www-form-urlencoded' media type. + */ +pub fn encode_form_urlencoded(m: &HashMap<~str, ~[~str]>) -> ~str { + let mut out = ~""; + let mut first = true; + + for m.iter().advance |(key, values)| { + let key = encode_plus(*key); + + for values.iter().advance |value| { + if first { + first = false; + } else { + out.push_char('&'); + first = false; + } + + out.push_str(fmt!("%s=%s", key, encode_plus(*value))); + } + } + + out +} + +/** + * Decode a string encoded with the 'application/x-www-form-urlencoded' media + * type into a hashmap. + */ +pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<~str, ~[~str]> { + do io::with_bytes_reader(s) |rdr| { + let mut m = HashMap::new(); + let mut key = ~""; + let mut value = ~""; + let mut parsing_key = true; + + while !rdr.eof() { + match rdr.read_char() { + '&' | ';' => { + if key != ~"" && value != ~"" { + let mut values = match m.pop(&key) { + Some(values) => values, + None => ~[], + }; + + values.push(value); + m.insert(key, values); + } + + parsing_key = true; + key = ~""; + value = ~""; + } + '=' => parsing_key = false, + ch => { + let ch = match ch { + '%' => { + let bytes = rdr.read_bytes(2u); + uint::parse_bytes(bytes, 16u).get() as char + } + '+' => ' ', + ch => ch + }; + + if parsing_key { + key.push_char(ch) + } else { + value.push_char(ch) + } + } + } + } + + if key != ~"" && value != ~"" { + let mut values = match m.pop(&key) { + Some(values) => values, + None => ~[], + }; + + values.push(value); + m.insert(key, values); + } + + m + } +} + + +fn split_char_first(s: &str, c: char) -> (~str, ~str) { + let len = s.len(); + let mut index = len; + let mut mat = 0; + do io::with_str_reader(s) |rdr| { + let mut ch; + while !rdr.eof() { + ch = rdr.read_byte() as char; + if ch == c { + // found a match, adjust markers + index = rdr.tell()-1; + mat = 1; + break; + } + } + } + if index+mat == len { + return (s.slice(0, index).to_owned(), ~""); + } else { + return (s.slice(0, index).to_owned(), + s.slice(index + mat, s.len()).to_owned()); + } +} + +fn userinfo_from_str(uinfo: &str) -> UserInfo { + let (user, p) = split_char_first(uinfo, ':'); + let pass = if p.is_empty() { + None + } else { + Some(p) + }; + return UserInfo::new(user, pass); +} + +fn userinfo_to_str(userinfo: &UserInfo) -> ~str { + match userinfo.pass { + Some(ref pass) => fmt!("%s:%s@", userinfo.user, *pass), + None => fmt!("%s@", userinfo.user), + } +} + +fn query_from_str(rawquery: &str) -> Query { + let mut query: Query = ~[]; + if !rawquery.is_empty() { + for rawquery.split_iter('&').advance |p| { + let (k, v) = split_char_first(p, '='); + query.push((decode_component(k), decode_component(v))); + }; + } + return query; +} + +pub fn query_to_str(query: &Query) -> ~str { + let mut strvec = ~[]; + for query.iter().advance |kv| { + match kv { + &(ref k, ref v) => { + strvec.push(fmt!("%s=%s", + encode_component(*k), + encode_component(*v)) + ); + } + } + } + return strvec.connect("&"); +} + +// returns the scheme and the rest of the url, or a parsing error +pub fn get_scheme(rawurl: &str) -> Result<(~str, ~str), ~str> { + for rawurl.iter().enumerate().advance |(i,c)| { + match c { + 'A' .. 'Z' | 'a' .. 'z' => loop, + '0' .. '9' | '+' | '-' | '.' => { + if i == 0 { + return Err(~"url: Scheme must begin with a letter."); + } + loop; + } + ':' => { + if i == 0 { + return Err(~"url: Scheme cannot be empty."); + } else { + return Ok((rawurl.slice(0,i).to_owned(), + rawurl.slice(i+1,rawurl.len()).to_owned())); + } + } + _ => { + return Err(~"url: Invalid character in scheme."); + } + } + }; + return Err(~"url: Scheme must be terminated with a colon."); +} + +#[deriving(Clone, Eq)] +enum Input { + Digit, // all digits + Hex, // digits and letters a-f + Unreserved // all other legal characters +} + +// returns userinfo, host, port, and unparsed part, or an error +fn get_authority(rawurl: &str) -> + Result<(Option, ~str, Option<~str>, ~str), ~str> { + if !rawurl.starts_with("//") { + // there is no authority. + return Ok((None, ~"", None, rawurl.to_str())); + } + + enum State { + Start, // starting state + PassHostPort, // could be in user or port + Ip6Port, // either in ipv6 host or port + Ip6Host, // are in an ipv6 host + InHost, // are in a host - may be ipv6, but don't know yet + InPort // are in port + } + + let len = rawurl.len(); + let mut st = Start; + let mut in = Digit; // most restricted, start here. + + let mut userinfo = None; + let mut host = ~""; + let mut port = None; + + let mut colon_count = 0; + let mut pos = 0; + let mut begin = 2; + let mut end = len; + + for rawurl.iter().enumerate().advance |(i,c)| { + if i < 2 { loop; } // ignore the leading // + + // deal with input class first + match c { + '0' .. '9' => (), + 'A' .. 'F' | 'a' .. 'f' => { + if in == Digit { + in = Hex; + } + } + 'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' | + '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => { + in = Unreserved; + } + ':' | '@' | '?' | '#' | '/' => { + // separators, don't change anything + } + _ => { + return Err(~"Illegal character in authority"); + } + } + + // now process states + match c { + ':' => { + colon_count += 1; + match st { + Start => { + pos = i; + st = PassHostPort; + } + PassHostPort => { + // multiple colons means ipv6 address. + if in == Unreserved { + return Err( + ~"Illegal characters in IPv6 address."); + } + st = Ip6Host; + } + InHost => { + pos = i; + // can't be sure whether this is an ipv6 address or a port + if in == Unreserved { + return Err(~"Illegal characters in authority."); + } + st = Ip6Port; + } + Ip6Port => { + if in == Unreserved { + return Err(~"Illegal characters in authority."); + } + st = Ip6Host; + } + Ip6Host => { + if colon_count > 7 { + host = rawurl.slice(begin, i).to_owned(); + pos = i; + st = InPort; + } + } + _ => { + return Err(~"Invalid ':' in authority."); + } + } + in = Digit; // reset input class + } + + '@' => { + in = Digit; // reset input class + colon_count = 0; // reset count + match st { + Start => { + let user = rawurl.slice(begin, i).to_owned(); + userinfo = Some(UserInfo::new(user, None)); + st = InHost; + } + PassHostPort => { + let user = rawurl.slice(begin, pos).to_owned(); + let pass = rawurl.slice(pos+1, i).to_owned(); + userinfo = Some(UserInfo::new(user, Some(pass))); + st = InHost; + } + _ => { + return Err(~"Invalid '@' in authority."); + } + } + begin = i+1; + } + + '?' | '#' | '/' => { + end = i; + break; + } + _ => () + } + end = i; + } + + let end = end; // make end immutable so it can be captured + + let host_is_end_plus_one: &fn() -> bool = || { + let xs = ['?', '#', '/']; + end+1 == len + && !xs.iter().any_(|x| *x == (rawurl[end] as char)) + }; + + // finish up + match st { + Start => { + if host_is_end_plus_one() { + host = rawurl.slice(begin, end+1).to_owned(); + } else { + host = rawurl.slice(begin, end).to_owned(); + } + } + PassHostPort | Ip6Port => { + if in != Digit { + return Err(~"Non-digit characters in port."); + } + host = rawurl.slice(begin, pos).to_owned(); + port = Some(rawurl.slice(pos+1, end).to_owned()); + } + Ip6Host | InHost => { + host = rawurl.slice(begin, end).to_owned(); + } + InPort => { + if in != Digit { + return Err(~"Non-digit characters in port."); + } + port = Some(rawurl.slice(pos+1, end).to_owned()); + } + } + + let rest = if host_is_end_plus_one() { ~"" } + else { rawurl.slice(end, len).to_owned() }; + return Ok((userinfo, host, port, rest)); +} + + +// returns the path and unparsed part of url, or an error +fn get_path(rawurl: &str, authority: bool) -> + Result<(~str, ~str), ~str> { + let len = rawurl.len(); + let mut end = len; + for rawurl.iter().enumerate().advance |(i,c)| { + match c { + 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.' + | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '=' + | '_' | '-' => { + loop; + } + '?' | '#' => { + end = i; + break; + } + _ => return Err(~"Invalid character in path.") + } + } + + if authority { + if end != 0 && !rawurl.starts_with("/") { + return Err(~"Non-empty path must begin with\ + '/' in presence of authority."); + } + } + + return Ok((decode_component(rawurl.slice(0, end)), + rawurl.slice(end, len).to_owned())); +} + +// returns the parsed query and the fragment, if present +fn get_query_fragment(rawurl: &str) -> + Result<(Query, Option<~str>), ~str> { + if !rawurl.starts_with("?") { + if rawurl.starts_with("#") { + let f = decode_component(rawurl.slice( + 1, + rawurl.len())); + return Ok((~[], Some(f))); + } else { + return Ok((~[], None)); + } + } + let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#'); + let f = if r.len() != 0 { + Some(decode_component(r)) } else { None }; + return Ok((query_from_str(q), f)); +} + +/** + * Parse a `str` to a `url` + * + * # Arguments + * + * `rawurl` - a string representing a full url, including scheme. + * + * # Returns + * + * a `url` that contains the parsed representation of the url. + * + */ + +pub fn from_str(rawurl: &str) -> Result { + // scheme + let (scheme, rest) = match get_scheme(rawurl) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + // authority + let (userinfo, host, port, rest) = match get_authority(rest) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + // path + let has_authority = if host == ~"" { false } else { true }; + let (path, rest) = match get_path(rest, has_authority) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + // query and fragment + let (query, fragment) = match get_query_fragment(rest) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + Ok(Url::new(scheme, userinfo, host, port, path, query, fragment)) +} + +impl FromStr for Url { + fn from_str(s: &str) -> Option { + match from_str(s) { + Ok(url) => Some(url), + Err(_) => None + } + } +} + +/** + * Format a `url` as a string + * + * # Arguments + * + * `url` - a url. + * + * # Returns + * + * a `str` that contains the formatted url. Note that this will usually + * be an inverse of `from_str` but might strip out unneeded separators. + * for example, "http://somehost.com?", when parsed and formatted, will + * result in just "http://somehost.com". + * + */ +pub fn to_str(url: &Url) -> ~str { + let user = match url.user { + Some(ref user) => userinfo_to_str(user), + None => ~"", + }; + + let authority = if url.host.is_empty() { + ~"" + } else { + fmt!("//%s%s", user, url.host) + }; + + let query = if url.query.is_empty() { + ~"" + } else { + fmt!("?%s", query_to_str(&url.query)) + }; + + let fragment = match url.fragment { + Some(ref fragment) => fmt!("#%s", encode_component(*fragment)), + None => ~"", + }; + + fmt!("%s:%s%s%s%s", url.scheme, authority, url.path, query, fragment) +} + +impl ToStr for Url { + pub fn to_str(&self) -> ~str { + to_str(self) + } +} + +impl IterBytes for Url { + fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool { + self.to_str().iter_bytes(lsb0, f) + } +} + +// Put a few tests outside of the 'test' module so they can test the internal +// functions and those functions don't need 'pub' + +#[test] +fn test_split_char_first() { + let (u,v) = split_char_first("hello, sweet world", ','); + assert_eq!(u, ~"hello"); + assert_eq!(v, ~" sweet world"); + + let (u,v) = split_char_first("hello sweet world", ','); + assert_eq!(u, ~"hello sweet world"); + assert_eq!(v, ~""); +} + +#[test] +fn test_get_authority() { + let (u, h, p, r) = get_authority( + "//user:pass@rust-lang.org/something").unwrap(); + assert_eq!(u, Some(UserInfo::new(~"user", Some(~"pass")))); + assert_eq!(h, ~"rust-lang.org"); + assert!(p.is_none()); + assert_eq!(r, ~"/something"); + + let (u, h, p, r) = get_authority( + "//rust-lang.org:8000?something").unwrap(); + assert!(u.is_none()); + assert_eq!(h, ~"rust-lang.org"); + assert_eq!(p, Some(~"8000")); + assert_eq!(r, ~"?something"); + + let (u, h, p, r) = get_authority( + "//rust-lang.org#blah").unwrap(); + assert!(u.is_none()); + assert_eq!(h, ~"rust-lang.org"); + assert!(p.is_none()); + assert_eq!(r, ~"#blah"); + + // ipv6 tests + let (_, h, _, _) = get_authority( + "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap(); + assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); + + let (_, h, p, _) = get_authority( + "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap(); + assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); + assert_eq!(p, Some(~"8000")); + + let (u, h, p, _) = get_authority( + "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah" + ).unwrap(); + assert_eq!(u, Some(UserInfo::new(~"us", Some(~"p")))); + assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); + assert_eq!(p, Some(~"8000")); + + // invalid authorities; + assert!(get_authority("//user:pass@rust-lang:something").is_err()); + assert!(get_authority("//user@rust-lang:something:/path").is_err()); + assert!(get_authority( + "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err()); + assert!(get_authority( + "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err()); + + // these parse as empty, because they don't start with '//' + let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap(); + assert_eq!(h, ~""); + let (_, h, _, _) = get_authority("rust-lang.org").unwrap(); + assert_eq!(h, ~""); +} + +#[test] +fn test_get_path() { + let (p, r) = get_path("/something+%20orother", true).unwrap(); + assert_eq!(p, ~"/something+ orother"); + assert_eq!(r, ~""); + let (p, r) = get_path("test@email.com#fragment", false).unwrap(); + assert_eq!(p, ~"test@email.com"); + assert_eq!(r, ~"#fragment"); + let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap(); + assert_eq!(p, ~"/gen/:addr="); + assert_eq!(r, ~"?q=v"); + + //failure cases + assert!(get_path("something?q", true).is_err()); +} + +#[cfg(test)] +mod tests { + + use net::url::*; + + use std::hashmap::HashMap; + + #[test] + fn test_url_parse() { + let url = ~"http://user:pass@rust-lang.org/doc?s=v#something"; + + let up = from_str(url); + let u = up.unwrap(); + assert!(u.scheme == ~"http"); + let userinfo = u.user.get_ref(); + assert!(userinfo.user == ~"user"); + assert!(userinfo.pass.get_ref() == &~"pass"); + assert!(u.host == ~"rust-lang.org"); + assert!(u.path == ~"/doc"); + assert!(u.query == ~[(~"s", ~"v")]); + assert!(u.fragment.get_ref() == &~"something"); + } + + #[test] + fn test_url_parse_host_slash() { + let urlstr = ~"http://0.42.42.42/"; + let url = from_str(urlstr).unwrap(); + assert!(url.host == ~"0.42.42.42"); + assert!(url.path == ~"/"); + } + + #[test] + fn test_url_with_underscores() { + let urlstr = ~"http://dotcom.com/file_name.html"; + let url = from_str(urlstr).unwrap(); + assert!(url.path == ~"/file_name.html"); + } + + #[test] + fn test_url_with_dashes() { + let urlstr = ~"http://dotcom.com/file-name.html"; + let url = from_str(urlstr).unwrap(); + assert!(url.path == ~"/file-name.html"); + } + + #[test] + fn test_no_scheme() { + assert!(get_scheme("noschemehere.html").is_err()); + } + + #[test] + fn test_invalid_scheme_errors() { + assert!(from_str("99://something").is_err()); + assert!(from_str("://something").is_err()); + } + + #[test] + fn test_full_url_parse_and_format() { + let url = ~"http://user:pass@rust-lang.org/doc?s=v#something"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_userless_url_parse_and_format() { + let url = ~"http://rust-lang.org/doc?s=v#something"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_queryless_url_parse_and_format() { + let url = ~"http://user:pass@rust-lang.org/doc#something"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_empty_query_url_parse_and_format() { + let url = ~"http://user:pass@rust-lang.org/doc?#something"; + let should_be = ~"http://user:pass@rust-lang.org/doc#something"; + assert_eq!(from_str(url).unwrap().to_str(), should_be); + } + + #[test] + fn test_fragmentless_url_parse_and_format() { + let url = ~"http://user:pass@rust-lang.org/doc?q=v"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_minimal_url_parse_and_format() { + let url = ~"http://rust-lang.org/doc"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_scheme_host_only_url_parse_and_format() { + let url = ~"http://rust-lang.org"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_pathless_url_parse_and_format() { + let url = ~"http://user:pass@rust-lang.org?q=v#something"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_scheme_host_fragment_only_url_parse_and_format() { + let url = ~"http://rust-lang.org#something"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_url_component_encoding() { + let url = ~"http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B"; + let u = from_str(url).unwrap(); + assert!(u.path == ~"/doc uments"); + assert!(u.query == ~[(~"ba%d ", ~"#&+")]); + } + + #[test] + fn test_url_without_authority() { + let url = ~"mailto:test@email.com"; + assert_eq!(from_str(url).unwrap().to_str(), url); + } + + #[test] + fn test_encode() { + assert_eq!(encode(""), ~""); + assert_eq!(encode("http://example.com"), ~"http://example.com"); + assert_eq!(encode("foo bar% baz"), ~"foo%20bar%25%20baz"); + assert_eq!(encode(" "), ~"%20"); + assert_eq!(encode("!"), ~"!"); + assert_eq!(encode("\""), ~"\""); + assert_eq!(encode("#"), ~"#"); + assert_eq!(encode("$"), ~"$"); + assert_eq!(encode("%"), ~"%25"); + assert_eq!(encode("&"), ~"&"); + assert_eq!(encode("'"), ~"%27"); + assert_eq!(encode("("), ~"("); + assert_eq!(encode(")"), ~")"); + assert_eq!(encode("*"), ~"*"); + assert_eq!(encode("+"), ~"+"); + assert_eq!(encode(","), ~","); + assert_eq!(encode("/"), ~"/"); + assert_eq!(encode(":"), ~":"); + assert_eq!(encode(";"), ~";"); + assert_eq!(encode("="), ~"="); + assert_eq!(encode("?"), ~"?"); + assert_eq!(encode("@"), ~"@"); + assert_eq!(encode("["), ~"["); + assert_eq!(encode("]"), ~"]"); + } + + #[test] + fn test_encode_component() { + assert_eq!(encode_component(""), ~""); + assert!(encode_component("http://example.com") == + ~"http%3A%2F%2Fexample.com"); + assert!(encode_component("foo bar% baz") == + ~"foo%20bar%25%20baz"); + assert_eq!(encode_component(" "), ~"%20"); + assert_eq!(encode_component("!"), ~"%21"); + assert_eq!(encode_component("#"), ~"%23"); + assert_eq!(encode_component("$"), ~"%24"); + assert_eq!(encode_component("%"), ~"%25"); + assert_eq!(encode_component("&"), ~"%26"); + assert_eq!(encode_component("'"), ~"%27"); + assert_eq!(encode_component("("), ~"%28"); + assert_eq!(encode_component(")"), ~"%29"); + assert_eq!(encode_component("*"), ~"%2A"); + assert_eq!(encode_component("+"), ~"%2B"); + assert_eq!(encode_component(","), ~"%2C"); + assert_eq!(encode_component("/"), ~"%2F"); + assert_eq!(encode_component(":"), ~"%3A"); + assert_eq!(encode_component(";"), ~"%3B"); + assert_eq!(encode_component("="), ~"%3D"); + assert_eq!(encode_component("?"), ~"%3F"); + assert_eq!(encode_component("@"), ~"%40"); + assert_eq!(encode_component("["), ~"%5B"); + assert_eq!(encode_component("]"), ~"%5D"); + } + + #[test] + fn test_decode() { + assert_eq!(decode(""), ~""); + assert_eq!(decode("abc/def 123"), ~"abc/def 123"); + assert_eq!(decode("abc%2Fdef%20123"), ~"abc%2Fdef 123"); + assert_eq!(decode("%20"), ~" "); + assert_eq!(decode("%21"), ~"%21"); + assert_eq!(decode("%22"), ~"%22"); + assert_eq!(decode("%23"), ~"%23"); + assert_eq!(decode("%24"), ~"%24"); + assert_eq!(decode("%25"), ~"%"); + assert_eq!(decode("%26"), ~"%26"); + assert_eq!(decode("%27"), ~"'"); + assert_eq!(decode("%28"), ~"%28"); + assert_eq!(decode("%29"), ~"%29"); + assert_eq!(decode("%2A"), ~"%2A"); + assert_eq!(decode("%2B"), ~"%2B"); + assert_eq!(decode("%2C"), ~"%2C"); + assert_eq!(decode("%2F"), ~"%2F"); + assert_eq!(decode("%3A"), ~"%3A"); + assert_eq!(decode("%3B"), ~"%3B"); + assert_eq!(decode("%3D"), ~"%3D"); + assert_eq!(decode("%3F"), ~"%3F"); + assert_eq!(decode("%40"), ~"%40"); + assert_eq!(decode("%5B"), ~"%5B"); + assert_eq!(decode("%5D"), ~"%5D"); + } + + #[test] + fn test_decode_component() { + assert_eq!(decode_component(""), ~""); + assert_eq!(decode_component("abc/def 123"), ~"abc/def 123"); + assert_eq!(decode_component("abc%2Fdef%20123"), ~"abc/def 123"); + assert_eq!(decode_component("%20"), ~" "); + assert_eq!(decode_component("%21"), ~"!"); + assert_eq!(decode_component("%22"), ~"\""); + assert_eq!(decode_component("%23"), ~"#"); + assert_eq!(decode_component("%24"), ~"$"); + assert_eq!(decode_component("%25"), ~"%"); + assert_eq!(decode_component("%26"), ~"&"); + assert_eq!(decode_component("%27"), ~"'"); + assert_eq!(decode_component("%28"), ~"("); + assert_eq!(decode_component("%29"), ~")"); + assert_eq!(decode_component("%2A"), ~"*"); + assert_eq!(decode_component("%2B"), ~"+"); + assert_eq!(decode_component("%2C"), ~","); + assert_eq!(decode_component("%2F"), ~"/"); + assert_eq!(decode_component("%3A"), ~":"); + assert_eq!(decode_component("%3B"), ~";"); + assert_eq!(decode_component("%3D"), ~"="); + assert_eq!(decode_component("%3F"), ~"?"); + assert_eq!(decode_component("%40"), ~"@"); + assert_eq!(decode_component("%5B"), ~"["); + assert_eq!(decode_component("%5D"), ~"]"); + } + + #[test] + fn test_encode_form_urlencoded() { + let mut m = HashMap::new(); + assert_eq!(encode_form_urlencoded(&m), ~""); + + m.insert(~"", ~[]); + m.insert(~"foo", ~[]); + assert_eq!(encode_form_urlencoded(&m), ~""); + + let mut m = HashMap::new(); + m.insert(~"foo", ~[~"bar", ~"123"]); + assert_eq!(encode_form_urlencoded(&m), ~"foo=bar&foo=123"); + + let mut m = HashMap::new(); + m.insert(~"foo bar", ~[~"abc", ~"12 = 34"]); + assert!(encode_form_urlencoded(&m) == + ~"foo+bar=abc&foo+bar=12+%3D+34"); + } + + #[test] + fn test_decode_form_urlencoded() { + // FIXME #4449: Commented out because this causes an ICE, but only + // on FreeBSD + /* + assert_eq!(decode_form_urlencoded([]).len(), 0); + + let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes(); + let form = decode_form_urlencoded(s); + assert_eq!(form.len(), 2); + assert_eq!(form.get_ref(&~"a"), &~[~"1"]); + assert_eq!(form.get_ref(&~"foo bar"), &~[~"abc", ~"12 = 34"]); + */ + } +} diff --git a/src/libextra/net_ip.rs b/src/libextra/net_ip.rs deleted file mode 100644 index d18aac68481..00000000000 --- a/src/libextra/net_ip.rs +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Types/fns concerning Internet Protocol (IP), versions 4 & 6 - -#[allow(missing_doc)]; - - -use std::libc; -use std::comm::{stream, SharedChan}; -use std::ptr; -use std::result; -use std::str; - -use iotask = uv::iotask::IoTask; -use interact = uv::iotask::interact; - -use sockaddr_in = super::uv_ll::sockaddr_in; -use sockaddr_in6 = super::uv_ll::sockaddr_in6; -use addrinfo = super::uv_ll::addrinfo; -use uv_getaddrinfo_t = super::uv_ll::uv_getaddrinfo_t; -use uv_ip4_name = super::uv_ll::ip4_name; -use uv_ip4_port = super::uv_ll::ip4_port; -use uv_ip6_name = super::uv_ll::ip6_name; -use uv_ip6_port = super::uv_ll::ip6_port; -use uv_getaddrinfo = super::uv_ll::getaddrinfo; -use uv_freeaddrinfo = super::uv_ll::freeaddrinfo; -use create_uv_getaddrinfo_t = super::uv_ll::getaddrinfo_t; -use set_data_for_req = super::uv_ll::set_data_for_req; -use get_data_for_req = super::uv_ll::get_data_for_req; -use ll = super::uv_ll; - -/// An IP address -pub enum IpAddr { - /// An IPv4 address - Ipv4(sockaddr_in), - Ipv6(sockaddr_in6) -} - -/// Human-friendly feedback on why a parse_addr attempt failed -pub struct ParseAddrErr { - err_msg: ~str, -} - -/** - * Convert a `IpAddr` to a str - * - * # Arguments - * - * * ip - a `extra::net::ip::IpAddr` - */ -pub fn format_addr(ip: &IpAddr) -> ~str { - match *ip { - Ipv4(ref addr) => unsafe { - let result = uv_ip4_name(addr); - if result == ~"" { - fail!("failed to convert inner sockaddr_in address to str") - } - result - }, - Ipv6(ref addr) => unsafe { - let result = uv_ip6_name(addr); - if result == ~"" { - fail!("failed to convert inner sockaddr_in address to str") - } - result - } - } -} - -/** - * Get the associated port - * - * # Arguments - * * ip - a `extra::net::ip::IpAddr` - */ -pub fn get_port(ip: &IpAddr) -> uint { - match *ip { - Ipv4(ref addr) => unsafe { - uv_ip4_port(addr) - }, - Ipv6(ref addr) => unsafe { - uv_ip6_port(addr) - } - } -} - -/// Represents errors returned from `net::ip::get_addr()` -enum IpGetAddrErr { - GetAddrUnknownError -} - -/** - * Attempts name resolution on the provided `node` string - * - * # Arguments - * - * * `node` - a string representing some host address - * * `iotask` - a `uv::iotask` used to interact with the underlying event loop - * - * # Returns - * - * A `result<~[ip_addr], ip_get_addr_err>` instance that will contain - * a vector of `ip_addr` results, in the case of success, or an error - * object in the case of failure -*/ -pub fn get_addr(node: &str, iotask: &iotask) - -> result::Result<~[IpAddr], IpGetAddrErr> { - let (output_po, output_ch) = stream(); - let mut output_ch = Some(SharedChan::new(output_ch)); - do str::as_buf(node) |node_ptr, len| { - let output_ch = output_ch.swap_unwrap(); - debug!("slice len %?", len); - let handle = create_uv_getaddrinfo_t(); - let handle_ptr: *uv_getaddrinfo_t = &handle; - let handle_data = GetAddrData { - output_ch: output_ch.clone() - }; - let handle_data_ptr: *GetAddrData = &handle_data; - do interact(iotask) |loop_ptr| { - unsafe { - let result = uv_getaddrinfo( - loop_ptr, - handle_ptr, - get_addr_cb, - node_ptr, - ptr::null(), - ptr::null()); - match result { - 0i32 => { - set_data_for_req(handle_ptr, handle_data_ptr); - } - _ => { - output_ch.send(result::Err(GetAddrUnknownError)); - } - } - } - }; - output_po.recv() - } -} - -pub mod v4 { - - use net::ip::{IpAddr, Ipv4, ParseAddrErr}; - use uv::ll; - use uv_ip4_addr = uv::ll::ip4_addr; - use uv_ip4_name = uv::ll::ip4_name; - - use std::cast::transmute; - use std::result; - use std::uint; - - /** - * Convert a str to `ip_addr` - * - * # Failure - * - * Fails if the string is not a valid IPv4 address - * - * # Arguments - * - * * ip - a string of the format `x.x.x.x` - * - * # Returns - * - * * an `ip_addr` of the `ipv4` variant - */ - pub fn parse_addr(ip: &str) -> IpAddr { - match try_parse_addr(ip) { - result::Ok(addr) => addr, - result::Err(ref err_data) => fail!(copy err_data.err_msg) - } - } - // the simple, old style numberic representation of - // ipv4 - pub struct Ipv4Rep { a: u8, b: u8, c: u8, d: u8 } - - pub trait AsUnsafeU32 { - unsafe fn as_u32(&self) -> u32; - } - - impl AsUnsafeU32 for Ipv4Rep { - // this is pretty dastardly, i know - unsafe fn as_u32(&self) -> u32 { - let this: &mut u32 = transmute(self); - *this - } - } - pub fn parse_to_ipv4_rep(ip: &str) -> result::Result { - let parts: ~[uint] = ip.split_iter('.').transform(|s| { - match uint::from_str(s) { - Some(n) if n <= 255 => n, - _ => 256 - } - }).collect(); - if parts.len() != 4 { - Err(fmt!("'%s' doesn't have 4 parts", ip)) - } else if parts.iter().any_(|x| *x == 256u) { - Err(fmt!("invalid octal in addr '%s'", ip)) - } else { - Ok(Ipv4Rep { - a: parts[0] as u8, b: parts[1] as u8, - c: parts[2] as u8, d: parts[3] as u8, - }) - } - } - pub fn try_parse_addr(ip: &str) -> result::Result { - unsafe { - let INADDR_NONE = ll::get_INADDR_NONE(); - let ip_rep_result = parse_to_ipv4_rep(ip); - if result::is_err(&ip_rep_result) { - let err_str = result::get_err(&ip_rep_result); - return result::Err(ParseAddrErr { err_msg: err_str }) - } - // ipv4_rep.as_u32 is unsafe :/ - let input_is_inaddr_none = - result::get(&ip_rep_result).as_u32() == INADDR_NONE; - - let new_addr = uv_ip4_addr(ip, 22); - let reformatted_name = uv_ip4_name(&new_addr); - debug!("try_parse_addr: input ip: %s reparsed ip: %s", - ip, reformatted_name); - let ref_ip_rep_result = parse_to_ipv4_rep(reformatted_name); - if result::is_err(&ref_ip_rep_result) { - let err_str = result::get_err(&ref_ip_rep_result); - return Err(ParseAddrErr { err_msg: err_str }) - } - - if result::get(&ref_ip_rep_result).as_u32() == INADDR_NONE && - !input_is_inaddr_none { - Err(ParseAddrErr { - err_msg: ~"uv_ip4_name produced invalid result.", - }) - } else { - Ok(Ipv4(copy(new_addr))) - } - } - } -} -pub mod v6 { - - use net::ip::{IpAddr, Ipv6, ParseAddrErr}; - use uv_ip6_addr = uv::ll::ip6_addr; - use uv_ip6_name = uv::ll::ip6_name; - - use std::result; - - /** - * Convert a str to `ip_addr` - * - * # Failure - * - * Fails if the string is not a valid IPv6 address - * - * # Arguments - * - * * ip - an ipv6 string. See RFC2460 for spec. - * - * # Returns - * - * * an `ip_addr` of the `ipv6` variant - */ - pub fn parse_addr(ip: &str) -> IpAddr { - match try_parse_addr(ip) { - result::Ok(addr) => addr, - result::Err(err_data) => fail!(copy err_data.err_msg) - } - } - pub fn try_parse_addr(ip: &str) -> result::Result { - unsafe { - // need to figure out how to establish a parse failure.. - let new_addr = uv_ip6_addr(ip, 22); - let reparsed_name = uv_ip6_name(&new_addr); - debug!("v6::try_parse_addr ip: '%s' reparsed '%s'", - ip, reparsed_name); - // '::' appears to be uv_ip6_name() returns for bogus - // parses.. - if ip != &"::" && reparsed_name == ~"::" { - Err(ParseAddrErr { err_msg:fmt!("failed to parse '%s'", ip) }) - } - else { - Ok(Ipv6(new_addr)) - } - } - } -} - -struct GetAddrData { - output_ch: SharedChan> -} - -extern fn get_addr_cb(handle: *uv_getaddrinfo_t, - status: libc::c_int, - res: *addrinfo) { - unsafe { - debug!("in get_addr_cb"); - let handle_data = get_data_for_req(handle) as - *GetAddrData; - let output_ch = (*handle_data).output_ch.clone(); - if status == 0i32 { - if res != (ptr::null::()) { - let mut out_vec = ~[]; - debug!("initial addrinfo: %?", res); - let mut curr_addr = res; - loop { - let new_ip_addr = if ll::is_ipv4_addrinfo(curr_addr) { - Ipv4(copy(( - *ll::addrinfo_as_sockaddr_in(curr_addr)))) - } - else if ll::is_ipv6_addrinfo(curr_addr) { - Ipv6(copy(( - *ll::addrinfo_as_sockaddr_in6(curr_addr)))) - } - else { - debug!("curr_addr is not of family AF_INET or \ - AF_INET6. Error."); - output_ch.send( - result::Err(GetAddrUnknownError)); - break; - }; - out_vec.push(new_ip_addr); - - let next_addr = ll::get_next_addrinfo(curr_addr); - if next_addr == ptr::null::() as *addrinfo { - debug!("null next_addr encountered. no mas"); - break; - } - else { - curr_addr = next_addr; - debug!("next_addr addrinfo: %?", curr_addr); - } - } - debug!("successful process addrinfo result, len: %?", - out_vec.len()); - output_ch.send(result::Ok(out_vec)); - } - else { - debug!("addrinfo pointer is NULL"); - output_ch.send( - result::Err(GetAddrUnknownError)); - } - } - else { - debug!("status != 0 error in get_addr_cb"); - output_ch.send( - result::Err(GetAddrUnknownError)); - } - if res != (ptr::null::()) { - uv_freeaddrinfo(res); - } - debug!("leaving get_addr_cb"); - } -} - -#[cfg(test)] -mod test { - - use net_ip::*; - use net_ip::v4; - use net_ip::v6; - use uv; - - use std::result; - - #[test] - fn test_ip_ipv4_parse_and_format_ip() { - let localhost_str = ~"127.0.0.1"; - assert!(format_addr(&v4::parse_addr(localhost_str)) - == localhost_str) - } - #[test] - fn test_ip_ipv6_parse_and_format_ip() { - let localhost_str = ~"::1"; - let format_result = format_addr(&v6::parse_addr(localhost_str)); - debug!("results: expected: '%s' actual: '%s'", - localhost_str, format_result); - assert_eq!(format_result, localhost_str); - } - #[test] - fn test_ip_ipv4_bad_parse() { - match v4::try_parse_addr("b4df00d") { - result::Err(ref err_info) => { - debug!("got error as expected %?", err_info); - assert!(true); - } - result::Ok(ref addr) => { - fail!("Expected failure, but got addr %?", addr); - } - } - } - #[test] - #[ignore(target_os="win32")] - fn test_ip_ipv6_bad_parse() { - match v6::try_parse_addr("::,~2234k;") { - result::Err(ref err_info) => { - debug!("got error as expected %?", err_info); - assert!(true); - } - result::Ok(ref addr) => { - fail!("Expected failure, but got addr %?", addr); - } - } - } - #[test] - #[ignore(reason = "valgrind says it's leaky")] - fn test_ip_get_addr() { - let localhost_name = ~"localhost"; - let iotask = &uv::global_loop::get(); - let ga_result = get_addr(localhost_name, iotask); - if result::is_err(&ga_result) { - fail!("got err result from net::ip::get_addr();") - } - // note really sure how to reliably test/assert - // this.. mostly just wanting to see it work, atm. - let results = result::unwrap(ga_result); - debug!("test_get_addr: Number of results for %s: %?", - localhost_name, results.len()); - for results.iter().advance |r| { - let ipv_prefix = match *r { - Ipv4(_) => ~"IPv4", - Ipv6(_) => ~"IPv6" - }; - debug!("test_get_addr: result %s: '%s'", - ipv_prefix, format_addr(r)); - } - // at least one result.. this is going to vary from system - // to system, based on stuff like the contents of /etc/hosts - assert!(!results.is_empty()); - } - #[test] - #[ignore(reason = "valgrind says it's leaky")] - fn test_ip_get_addr_bad_input() { - let localhost_name = ~"sjkl234m,./sdf"; - let iotask = &uv::global_loop::get(); - let ga_result = get_addr(localhost_name, iotask); - assert!(result::is_err(&ga_result)); - } -} diff --git a/src/libextra/net_tcp.rs b/src/libextra/net_tcp.rs deleted file mode 100644 index 6a22950ec04..00000000000 --- a/src/libextra/net_tcp.rs +++ /dev/null @@ -1,1953 +0,0 @@ -// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! High-level interface to libuv's TCP functionality -// FIXME #4425: Need FFI fixes - -#[allow(missing_doc)]; - - -use future; -use future_spawn = future::spawn; -use ip = net_ip; -use uv; -use uv::iotask; -use uv::iotask::IoTask; - -use std::io; -use std::libc::size_t; -use std::libc; -use std::comm::{stream, Port, SharedChan}; -use std::ptr; -use std::result::{Result}; -use std::result; -use std::uint; -use std::vec; - -pub mod rustrt { - use std::libc; - - #[nolink] - pub extern { - unsafe fn rust_uv_current_kernel_malloc(size: libc::c_uint) - -> *libc::c_void; - unsafe fn rust_uv_current_kernel_free(mem: *libc::c_void); - unsafe fn rust_uv_helper_uv_tcp_t_size() -> libc::c_uint; - } -} - -/** - * Encapsulates an open TCP/IP connection through libuv - * - * `TcpSocket` is non-copyable/sendable and automagically handles closing the - * underlying libuv data structures when it goes out of scope. This is the - * data structure that is used for read/write operations over a TCP stream. - */ -pub struct TcpSocket { - socket_data: @TcpSocketData, -} - -#[unsafe_destructor] -impl Drop for TcpSocket { - fn drop(&self) { - tear_down_socket_data(self.socket_data) - } -} - -pub fn TcpSocket(socket_data: @TcpSocketData) -> TcpSocket { - TcpSocket { - socket_data: socket_data - } -} - -/** - * A buffered wrapper for `net::tcp::TcpSocket` - * - * It is created with a call to `net::tcp::socket_buf()` and has impls that - * satisfy both the `io::Reader` and `io::Writer` traits. - */ -pub struct TcpSocketBuf { - data: @mut TcpBufferedSocketData, - end_of_stream: @mut bool -} - -pub fn TcpSocketBuf(data: @mut TcpBufferedSocketData) -> TcpSocketBuf { - TcpSocketBuf { - data: data, - end_of_stream: @mut false - } -} - -/// Contains raw, string-based, error information returned from libuv -pub struct TcpErrData { - err_name: ~str, - err_msg: ~str, -} - -/// Details returned as part of a `Result::Err` result from `tcp::listen` -pub enum TcpListenErrData { - /** - * Some unplanned-for error. The first and second fields correspond - * to libuv's `err_name` and `err_msg` fields, respectively. - */ - GenericListenErr(~str, ~str), - /** - * Failed to bind to the requested IP/Port, because it is already in use. - * - * # Possible Causes - * - * * Attempting to bind to a port already bound to another listener - */ - AddressInUse, - /** - * Request to bind to an IP/Port was denied by the system. - * - * # Possible Causes - * - * * Attemping to binding to an IP/Port as a non-Administrator - * on Windows Vista+ - * * Attempting to bind, as a non-priv'd - * user, to 'privileged' ports (< 1024) on *nix - */ - AccessDenied -} -/// Details returned as part of a `Result::Err` result from `tcp::connect` -pub enum TcpConnectErrData { - /** - * Some unplanned-for error. The first and second fields correspond - * to libuv's `err_name` and `err_msg` fields, respectively. - */ - GenericConnectErr(~str, ~str), - /// Invalid IP or invalid port - ConnectionRefused -} - -/** - * Initiate a client connection over TCP/IP - * - * # Arguments - * - * * `input_ip` - The IP address (versions 4 or 6) of the remote host - * * `port` - the unsigned integer of the desired remote host port - * * `iotask` - a `uv::iotask` that the tcp request will run on - * - * # Returns - * - * A `result` that, if the operation succeeds, contains a - * `net::net::TcpSocket` that can be used to send and receive data to/from - * the remote host. In the event of failure, a - * `net::tcp::TcpConnectErrData` instance will be returned - */ -pub fn connect(input_ip: ip::IpAddr, port: uint, - iotask: &IoTask) - -> result::Result { - unsafe { - let (result_po, result_ch) = stream::(); - let result_ch = SharedChan::new(result_ch); - let (closed_signal_po, closed_signal_ch) = stream::<()>(); - let closed_signal_ch = SharedChan::new(closed_signal_ch); - let conn_data = ConnectReqData { - result_ch: result_ch, - closed_signal_ch: closed_signal_ch - }; - let conn_data_ptr: *ConnectReqData = &conn_data; - let (reader_po, reader_ch) = stream::>(); - let reader_ch = SharedChan::new(reader_ch); - let stream_handle_ptr = malloc_uv_tcp_t(); - *(stream_handle_ptr as *mut uv::ll::uv_tcp_t) = uv::ll::tcp_t(); - let socket_data = @TcpSocketData { - reader_po: @reader_po, - reader_ch: reader_ch, - stream_handle_ptr: stream_handle_ptr, - connect_req: uv::ll::connect_t(), - write_req: uv::ll::write_t(), - ipv6: match input_ip { - ip::Ipv4(_) => { false } - ip::Ipv6(_) => { true } - }, - iotask: iotask.clone() - }; - let socket_data_ptr: *TcpSocketData = &*socket_data; - // get an unsafe representation of our stream_handle_ptr that - // we can send into the interact cb to be handled in libuv.. - debug!("stream_handle_ptr outside interact %?", - stream_handle_ptr); - do iotask::interact(iotask) |loop_ptr| { - debug!("in interact cb for tcp client connect.."); - debug!("stream_handle_ptr in interact %?", - stream_handle_ptr); - match uv::ll::tcp_init( loop_ptr, stream_handle_ptr) { - 0i32 => { - debug!("tcp_init successful"); - debug!("dealing w/ ipv4 connection.."); - let connect_req_ptr: *uv::ll::uv_connect_t = - &(*socket_data_ptr).connect_req; - let addr_str = ip::format_addr(&input_ip); - let connect_result = match input_ip { - ip::Ipv4(ref addr) => { - // have to "recreate" the - // sockaddr_in/6 since the ip_addr - // discards the port info.. should - // probably add an additional rust - // type that actually is closer to - // what the libuv API expects (ip str - // + port num) - debug!("addr: %?", addr); - let in_addr = uv::ll::ip4_addr(addr_str, - port as int); - uv::ll::tcp_connect( - connect_req_ptr, - stream_handle_ptr, - &in_addr, - tcp_connect_on_connect_cb) - } - ip::Ipv6(ref addr) => { - debug!("addr: %?", addr); - let in_addr = uv::ll::ip6_addr(addr_str, - port as int); - uv::ll::tcp_connect6( - connect_req_ptr, - stream_handle_ptr, - &in_addr, - tcp_connect_on_connect_cb) - } - }; - match connect_result { - 0i32 => { - debug!("tcp_connect successful: \ - stream %x, - socket data %x", - stream_handle_ptr as uint, - socket_data_ptr as uint); - // reusable data that we'll have for the - // duration.. - uv::ll::set_data_for_uv_handle( - stream_handle_ptr, - socket_data_ptr as - *libc::c_void); - // just so the connect_cb can send the - // outcome.. - uv::ll::set_data_for_req(connect_req_ptr, - conn_data_ptr); - debug!("leaving tcp_connect interact cb..."); - // let tcp_connect_on_connect_cb send on - // the result_ch, now.. - } - _ => { - // immediate connect - // failure.. probably a garbage ip or - // somesuch - let err_data = - uv::ll::get_last_err_data(loop_ptr); - let result_ch = (*conn_data_ptr) - .result_ch.clone(); - result_ch.send(ConnFailure(err_data)); - uv::ll::set_data_for_uv_handle( - stream_handle_ptr, - conn_data_ptr); - uv::ll::close(stream_handle_ptr, - stream_error_close_cb); - } - } - } - _ => { - // failure to create a tcp handle - let err_data = uv::ll::get_last_err_data(loop_ptr); - let result_ch = (*conn_data_ptr).result_ch.clone(); - result_ch.send(ConnFailure(err_data)); - } - } - } - match result_po.recv() { - ConnSuccess => { - debug!("tcp::connect - received success on result_po"); - result::Ok(TcpSocket(socket_data)) - } - ConnFailure(ref err_data) => { - closed_signal_po.recv(); - debug!("tcp::connect - received failure on result_po"); - // still have to free the malloc'd stream handle.. - rustrt::rust_uv_current_kernel_free(stream_handle_ptr - as *libc::c_void); - let tcp_conn_err = match err_data.err_name { - ~"ECONNREFUSED" => ConnectionRefused, - _ => GenericConnectErr(copy err_data.err_name, - copy err_data.err_msg) - }; - result::Err(tcp_conn_err) - } - } - } -} - -/** - * Write binary data to a tcp stream; Blocks until operation completes - * - * # Arguments - * - * * sock - a `TcpSocket` to write to - * * raw_write_data - a vector of `~[u8]` that will be written to the stream. - * This value must remain valid for the duration of the `write` call - * - * # Returns - * - * A `Result` object with a `()` value as the `Ok` variant, or a - * `TcpErrData` value as the `Err` variant - */ -pub fn write(sock: &TcpSocket, raw_write_data: ~[u8]) - -> result::Result<(), TcpErrData> { - let socket_data_ptr: *TcpSocketData = &*sock.socket_data; - write_common_impl(socket_data_ptr, raw_write_data) -} - -/** - * Write binary data to tcp stream; Returns a `future::Future` value - * immediately - * - * # Safety - * - * This function can produce unsafe results if: - * - * 1. the call to `write_future` is made - * 2. the `future::Future` value returned is never resolved via - * `Future::get` - * 3. and then the `TcpSocket` passed in to `write_future` leaves - * scope and is destructed before the task that runs the libuv write - * operation completes. - * - * As such: If using `write_future`, always be sure to resolve the returned - * `Future` so as to ensure libuv doesn't try to access a released write - * handle. Otherwise, use the blocking `tcp::write` function instead. - * - * # Arguments - * - * * sock - a `TcpSocket` to write to - * * raw_write_data - a vector of `~[u8]` that will be written to the stream. - * This value must remain valid for the duration of the `write` call - * - * # Returns - * - * A `Future` value that, once the `write` operation completes, resolves to a - * `Result` object with a `nil` value as the `Ok` variant, or a `TcpErrData` - * value as the `Err` variant - */ -pub fn write_future(sock: &TcpSocket, raw_write_data: ~[u8]) - -> future::Future> -{ - let socket_data_ptr: *TcpSocketData = &*sock.socket_data; - do future_spawn { - let data_copy = copy(raw_write_data); - write_common_impl(socket_data_ptr, data_copy) - } -} - -/** - * Begin reading binary data from an open TCP connection; used with - * `read_stop` - * - * # Arguments - * - * * sock -- a `net::tcp::TcpSocket` for the connection to read from - * - * # Returns - * - * * A `Result` instance that will either contain a - * `std::comm::Port>` that the user can read - * (and * optionally, loop on) from until `read_stop` is called, or a - * `TcpErrData` record - */ -pub fn read_start(sock: &TcpSocket) - -> result::Result<@Port>, - TcpErrData> { - let socket_data: *TcpSocketData = &*sock.socket_data; - read_start_common_impl(socket_data) -} - -/** - * Stop reading from an open TCP connection; used with `read_start` - * - * # Arguments - * - * * `sock` - a `net::tcp::TcpSocket` that you wish to stop reading on - */ -pub fn read_stop(sock: &TcpSocket) -> result::Result<(), TcpErrData> { - let socket_data: *TcpSocketData = &*sock.socket_data; - read_stop_common_impl(socket_data) -} - -/** - * Reads a single chunk of data from `TcpSocket`; block until data/error - * recv'd - * - * Does a blocking read operation for a single chunk of data from a - * `TcpSocket` until a data arrives or an error is received. The provided - * `timeout_msecs` value is used to raise an error if the timeout period - * passes without any data received. - * - * # Arguments - * - * * `sock` - a `net::tcp::TcpSocket` that you wish to read from - * * `timeout_msecs` - a `uint` value, in msecs, to wait before dropping the - * read attempt. Pass `0u` to wait indefinitely - */ -pub fn read(sock: &TcpSocket, timeout_msecs: uint) - -> result::Result<~[u8],TcpErrData> { - let socket_data: *TcpSocketData = &*sock.socket_data; - read_common_impl(socket_data, timeout_msecs) -} - -/** - * Reads a single chunk of data; returns a `future::Future<~[u8]>` - * immediately - * - * Does a non-blocking read operation for a single chunk of data from a - * `TcpSocket` and immediately returns a `Future` value representing the - * result. When resolving the returned `Future`, it will block until data - * arrives or an error is received. The provided `timeout_msecs` - * value is used to raise an error if the timeout period passes without any - * data received. - * - * # Safety - * - * This function can produce unsafe results if the call to `read_future` is - * made, the `future::Future` value returned is never resolved via - * `Future::get`, and then the `TcpSocket` passed in to `read_future` leaves - * scope and is destructed before the task that runs the libuv read - * operation completes. - * - * As such: If using `read_future`, always be sure to resolve the returned - * `Future` so as to ensure libuv doesn't try to access a released read - * handle. Otherwise, use the blocking `tcp::read` function instead. - * - * # Arguments - * - * * `sock` - a `net::tcp::TcpSocket` that you wish to read from - * * `timeout_msecs` - a `uint` value, in msecs, to wait before dropping the - * read attempt. Pass `0u` to wait indefinitely - */ -fn read_future(sock: &TcpSocket, timeout_msecs: uint) - -> future::Future> { - let socket_data: *TcpSocketData = &*sock.socket_data; - do future_spawn { - read_common_impl(socket_data, timeout_msecs) - } -} - -/** - * Bind an incoming client connection to a `net::tcp::TcpSocket` - * - * # Notes - * - * It is safe to call `net::tcp::accept` _only_ within the context of the - * `new_connect_cb` callback provided as the final argument to the - * `net::tcp::listen` function. - * - * The `new_conn` opaque value is provided _only_ as the first argument to the - * `new_connect_cb` provided as a part of `net::tcp::listen`. - * It can be safely sent to another task but it _must_ be - * used (via `net::tcp::accept`) before the `new_connect_cb` call it was - * provided to returns. - * - * This implies that a port/chan pair must be used to make sure that the - * `new_connect_cb` call blocks until an attempt to create a - * `net::tcp::TcpSocket` is completed. - * - * # Example - * - * Here, the `new_conn` is used in conjunction with `accept` from within - * a task spawned by the `new_connect_cb` passed into `listen` - * - * ~~~ {.rust} - * do net::tcp::listen(remote_ip, remote_port, backlog, iotask, - * // this callback is ran once after the connection is successfully - * // set up - * |kill_ch| { - * // pass the kill_ch to your main loop or wherever you want - * // to be able to externally kill the server from - * }) - * // this callback is ran when a new connection arrives - * |new_conn, kill_ch| { - * let (cont_po, cont_ch) = comm::stream::>(); - * do task::spawn { - * let accept_result = net::tcp::accept(new_conn); - * match accept_result { - * Err(accept_error) => { - * cont_ch.send(Some(accept_error)); - * // fail? - * }, - * Ok(sock) => { - * cont_ch.send(None); - * // do work here - * } - * } - * }; - * match cont_po.recv() { - * // shut down listen() - * Some(err_data) => kill_ch.send(Some(err_data)), - * // wait for next connection - * None => () - * } - * }; - * ~~~ - * - * # Arguments - * - * * `new_conn` - an opaque value used to create a new `TcpSocket` - * - * # Returns - * - * On success, this function will return a `net::tcp::TcpSocket` as the - * `Ok` variant of a `Result`. The `net::tcp::TcpSocket` is anchored within - * the task that `accept` was called within for its lifetime. On failure, - * this function will return a `net::tcp::TcpErrData` record - * as the `Err` variant of a `Result`. - */ -pub fn accept(new_conn: TcpNewConnection) - -> result::Result { - unsafe { - match new_conn{ - NewTcpConn(server_handle_ptr) => { - let server_data_ptr = uv::ll::get_data_for_uv_handle( - server_handle_ptr) as *TcpListenFcData; - let (reader_po, reader_ch) = stream::< - Result<~[u8], TcpErrData>>(); - let reader_ch = SharedChan::new(reader_ch); - let iotask = &(*server_data_ptr).iotask; - let stream_handle_ptr = malloc_uv_tcp_t(); - *(stream_handle_ptr as *mut uv::ll::uv_tcp_t) = - uv::ll::tcp_t(); - let client_socket_data: @TcpSocketData = @TcpSocketData { - reader_po: @reader_po, - reader_ch: reader_ch, - stream_handle_ptr : stream_handle_ptr, - connect_req : uv::ll::connect_t(), - write_req : uv::ll::write_t(), - ipv6: (*server_data_ptr).ipv6, - iotask : iotask.clone() - }; - let client_socket_data_ptr: *TcpSocketData = - &*client_socket_data; - let client_stream_handle_ptr = - (*client_socket_data_ptr).stream_handle_ptr; - - let (result_po, result_ch) = stream::>(); - let result_ch = SharedChan::new(result_ch); - - // UNSAFE LIBUV INTERACTION BEGIN - // .. normally this happens within the context of - // a call to uv::hl::interact.. but we're breaking - // the rules here because this always has to be - // called within the context of a listen() new_connect_cb - // callback (or it will likely fail and drown your cat) - debug!("in interact cb for tcp::accept"); - let loop_ptr = uv::ll::get_loop_for_uv_handle( - server_handle_ptr); - match uv::ll::tcp_init(loop_ptr, client_stream_handle_ptr) { - 0i32 => { - debug!("uv_tcp_init successful for \ - client stream"); - match uv::ll::accept( - server_handle_ptr as *libc::c_void, - client_stream_handle_ptr as *libc::c_void) { - 0i32 => { - debug!("successfully accepted client \ - connection: \ - stream %x, \ - socket data %x", - client_stream_handle_ptr as uint, - client_socket_data_ptr as uint); - uv::ll::set_data_for_uv_handle( - client_stream_handle_ptr, - client_socket_data_ptr - as *libc::c_void); - let ptr = uv::ll::get_data_for_uv_handle( - client_stream_handle_ptr); - debug!("ptrs: %x %x", - client_socket_data_ptr as uint, - ptr as uint); - result_ch.send(None); - } - _ => { - debug!("failed to accept client conn"); - result_ch.send(Some( - uv::ll::get_last_err_data( - loop_ptr).to_tcp_err())); - } - } - } - _ => { - debug!("failed to accept client stream"); - result_ch.send(Some( - uv::ll::get_last_err_data( - loop_ptr).to_tcp_err())); - } - } - // UNSAFE LIBUV INTERACTION END - match result_po.recv() { - Some(err_data) => result::Err(err_data), - None => result::Ok(TcpSocket(client_socket_data)) - } - } - } - } -} - -/** - * Bind to a given IP/port and listen for new connections - * - * # Arguments - * - * * `host_ip` - a `net::ip::IpAddr` representing a unique IP - * (versions 4 or 6) - * * `port` - a uint representing the port to listen on - * * `backlog` - a uint representing the number of incoming connections - * to cache in memory - * * `hl_loop` - a `uv_iotask::IoTask` that the tcp request will run on - * * `on_establish_cb` - a callback that is evaluated if/when the listener - * is successfully established. it takes no parameters - * * `new_connect_cb` - a callback to be evaluated, on the libuv thread, - * whenever a client attempts to conect on the provided ip/port. the - * callback's arguments are: - * * `new_conn` - an opaque type that can be passed to - * `net::tcp::accept` in order to be converted to a `TcpSocket`. - * * `kill_ch` - channel of type `std::comm::Chan>`. - * this channel can be used to send a message to cause `listen` to begin - * closing the underlying libuv data structures. - * - * # returns - * - * a `Result` instance containing empty data of type `()` on a - * successful/normal shutdown, and a `TcpListenErrData` enum in the event - * of listen exiting because of an error - */ -pub fn listen(host_ip: ip::IpAddr, port: uint, backlog: uint, - iotask: &IoTask, - on_establish_cb: ~fn(SharedChan>), - new_connect_cb: ~fn(TcpNewConnection, - SharedChan>)) - -> result::Result<(), TcpListenErrData> { - do listen_common(host_ip, port, backlog, iotask, - on_establish_cb) - // on_connect_cb - |handle| { - unsafe { - let server_data_ptr = uv::ll::get_data_for_uv_handle(handle) - as *TcpListenFcData; - let new_conn = NewTcpConn(handle); - let kill_ch = (*server_data_ptr).kill_ch.clone(); - new_connect_cb(new_conn, kill_ch); - } - } -} - -fn listen_common(host_ip: ip::IpAddr, - port: uint, - backlog: uint, - iotask: &IoTask, - on_establish_cb: ~fn(SharedChan>), - on_connect_cb: ~fn(*uv::ll::uv_tcp_t)) - -> result::Result<(), TcpListenErrData> { - let (stream_closed_po, stream_closed_ch) = stream::<()>(); - let stream_closed_ch = SharedChan::new(stream_closed_ch); - let (kill_po, kill_ch) = stream::>(); - let kill_ch = SharedChan::new(kill_ch); - let server_stream = uv::ll::tcp_t(); - let server_stream_ptr: *uv::ll::uv_tcp_t = &server_stream; - let server_data: TcpListenFcData = TcpListenFcData { - server_stream_ptr: server_stream_ptr, - stream_closed_ch: stream_closed_ch, - kill_ch: kill_ch.clone(), - on_connect_cb: on_connect_cb, - iotask: iotask.clone(), - ipv6: match &host_ip { - &ip::Ipv4(_) => { false } - &ip::Ipv6(_) => { true } - }, - active: @mut true - }; - let server_data_ptr: *TcpListenFcData = &server_data; - - let (setup_po, setup_ch) = stream(); - - // this is to address a compiler warning about - // an implicit copy.. it seems that double nested - // will defeat a move sigil, as is done to the host_ip - // arg above.. this same pattern works w/o complaint in - // tcp::connect (because the iotask::interact cb isn't - // nested within a std::comm::listen block) - let loc_ip = copy(host_ip); - do iotask::interact(iotask) |loop_ptr| { - unsafe { - match uv::ll::tcp_init(loop_ptr, server_stream_ptr) { - 0i32 => { - uv::ll::set_data_for_uv_handle( - server_stream_ptr, - server_data_ptr); - let addr_str = ip::format_addr(&loc_ip); - let bind_result = match loc_ip { - ip::Ipv4(ref addr) => { - debug!("addr: %?", addr); - let in_addr = uv::ll::ip4_addr( - addr_str, - port as int); - uv::ll::tcp_bind(server_stream_ptr, &in_addr) - } - ip::Ipv6(ref addr) => { - debug!("addr: %?", addr); - let in_addr = uv::ll::ip6_addr( - addr_str, - port as int); - uv::ll::tcp_bind6(server_stream_ptr, &in_addr) - } - }; - match bind_result { - 0i32 => { - match uv::ll::listen( - server_stream_ptr, - backlog as libc::c_int, - tcp_lfc_on_connection_cb) { - 0i32 => setup_ch.send(None), - _ => { - debug!( - "failure to uv_tcp_init"); - let err_data = - uv::ll::get_last_err_data( - loop_ptr); - setup_ch.send(Some(err_data)); - } - } - } - _ => { - debug!("failure to uv_tcp_bind"); - let err_data = uv::ll::get_last_err_data( - loop_ptr); - setup_ch.send(Some(err_data)); - } - } - } - _ => { - debug!("failure to uv_tcp_bind"); - let err_data = uv::ll::get_last_err_data( - loop_ptr); - setup_ch.send(Some(err_data)); - } - } - } - } - - let setup_result = setup_po.recv(); - - match setup_result { - Some(ref err_data) => { - do iotask::interact(iotask) |loop_ptr| { - unsafe { - debug!( - "tcp::listen post-kill recv hl interact %?", - loop_ptr); - *(*server_data_ptr).active = false; - uv::ll::close(server_stream_ptr, tcp_lfc_close_cb); - } - }; - stream_closed_po.recv(); - match err_data.err_name { - ~"EACCES" => { - debug!("Got EACCES error"); - result::Err(AccessDenied) - } - ~"EADDRINUSE" => { - debug!("Got EADDRINUSE error"); - result::Err(AddressInUse) - } - _ => { - debug!("Got '%s' '%s' libuv error", - err_data.err_name, err_data.err_msg); - result::Err( - GenericListenErr(copy err_data.err_name, - copy err_data.err_msg)) - } - } - } - None => { - on_establish_cb(kill_ch.clone()); - let kill_result = kill_po.recv(); - do iotask::interact(iotask) |loop_ptr| { - unsafe { - debug!( - "tcp::listen post-kill recv hl interact %?", - loop_ptr); - *(*server_data_ptr).active = false; - uv::ll::close(server_stream_ptr, tcp_lfc_close_cb); - } - }; - stream_closed_po.recv(); - match kill_result { - // some failure post bind/listen - Some(ref err_data) => result::Err(GenericListenErr( - copy err_data.err_name, - copy err_data.err_msg)), - // clean exit - None => result::Ok(()) - } - } - } -} - - -/** - * Convert a `net::tcp::TcpSocket` to a `net::tcp::TcpSocketBuf`. - * - * This function takes ownership of a `net::tcp::TcpSocket`, returning it - * stored within a buffered wrapper, which can be converted to a `io::Reader` - * or `io::Writer` - * - * # Arguments - * - * * `sock` -- a `net::tcp::TcpSocket` that you want to buffer - * - * # Returns - * - * A buffered wrapper that you can cast as an `io::Reader` or `io::Writer` - */ -pub fn socket_buf(sock: TcpSocket) -> TcpSocketBuf { - TcpSocketBuf(@mut TcpBufferedSocketData { - sock: sock, buf: ~[], buf_off: 0 - }) -} - -/// Convenience methods extending `net::tcp::TcpSocket` -impl TcpSocket { - pub fn read_start(&self) -> result::Result<@Port< - result::Result<~[u8], TcpErrData>>, TcpErrData> { - read_start(self) - } - pub fn read_stop(&self) -> - result::Result<(), TcpErrData> { - read_stop(self) - } - pub fn read(&self, timeout_msecs: uint) -> - result::Result<~[u8], TcpErrData> { - read(self, timeout_msecs) - } - pub fn read_future(&self, timeout_msecs: uint) -> - future::Future> { - read_future(self, timeout_msecs) - } - pub fn write(&self, raw_write_data: ~[u8]) - -> result::Result<(), TcpErrData> { - write(self, raw_write_data) - } - pub fn write_future(&self, raw_write_data: ~[u8]) - -> future::Future> { - write_future(self, raw_write_data) - } - pub fn get_peer_addr(&self) -> ip::IpAddr { - unsafe { - if self.socket_data.ipv6 { - let addr = uv::ll::ip6_addr("", 0); - uv::ll::tcp_getpeername6(self.socket_data.stream_handle_ptr, - &addr); - ip::Ipv6(addr) - } else { - let addr = uv::ll::ip4_addr("", 0); - uv::ll::tcp_getpeername(self.socket_data.stream_handle_ptr, - &addr); - ip::Ipv4(addr) - } - } - } -} - -/// Implementation of `io::Reader` trait for a buffered `net::tcp::TcpSocket` -impl io::Reader for TcpSocketBuf { - fn read(&self, buf: &mut [u8], len: uint) -> uint { - if len == 0 { return 0 } - let mut count: uint = 0; - - loop { - assert!(count < len); - - // If possible, copy up to `len` bytes from the internal - // `data.buf` into `buf` - let nbuffered = self.data.buf.len() - self.data.buf_off; - let needed = len - count; - if nbuffered > 0 { - unsafe { - let ncopy = uint::min(nbuffered, needed); - let dst = ptr::mut_offset( - vec::raw::to_mut_ptr(buf), count); - let src = ptr::offset( - vec::raw::to_ptr(self.data.buf), - self.data.buf_off); - ptr::copy_memory(dst, src, ncopy); - self.data.buf_off += ncopy; - count += ncopy; - } - } - - assert!(count <= len); - if count == len { - break; - } - - // We copied all the bytes we had in the internal buffer into - // the result buffer, but the caller wants more bytes, so we - // need to read in data from the socket. Note that the internal - // buffer is of no use anymore as we read all bytes from it, - // so we can throw it away. - let read_result = { - let data = &*self.data; - read(&data.sock, 0) - }; - if read_result.is_err() { - let err_data = read_result.get_err(); - - if err_data.err_name == ~"EOF" { - *self.end_of_stream = true; - break; - } else { - debug!("ERROR sock_buf as io::reader.read err %? %?", - err_data.err_name, err_data.err_msg); - // As we have already copied data into result buffer, - // we cannot simply return 0 here. Instead the error - // should show up in a later call to read(). - break; - } - } else { - self.data.buf = result::unwrap(read_result); - self.data.buf_off = 0; - } - } - - count - } - fn read_byte(&self) -> int { - loop { - if self.data.buf.len() > self.data.buf_off { - let c = self.data.buf[self.data.buf_off]; - self.data.buf_off += 1; - return c as int - } - - let read_result = { - let data = &*self.data; - read(&data.sock, 0) - }; - if read_result.is_err() { - let err_data = read_result.get_err(); - - if err_data.err_name == ~"EOF" { - *self.end_of_stream = true; - return -1 - } else { - debug!("ERROR sock_buf as io::reader.read err %? %?", - err_data.err_name, err_data.err_msg); - fail!() - } - } else { - self.data.buf = result::unwrap(read_result); - self.data.buf_off = 0; - } - } - } - fn eof(&self) -> bool { - *self.end_of_stream - } - fn seek(&self, dist: int, seek: io::SeekStyle) { - debug!("tcp_socket_buf seek stub %? %?", dist, seek); - // noop - } - fn tell(&self) -> uint { - 0u // noop - } -} - -/// Implementation of `io::Reader` trait for a buffered `net::tcp::TcpSocket` -impl io::Writer for TcpSocketBuf { - pub fn write(&self, data: &[u8]) { - let socket_data_ptr: *TcpSocketData = - &(*((*(self.data)).sock).socket_data); - let w_result = write_common_impl(socket_data_ptr, - data.slice(0, data.len()).to_owned()); - if w_result.is_err() { - let err_data = w_result.get_err(); - debug!( - "ERROR sock_buf as io::writer.writer err: %? %?", - err_data.err_name, err_data.err_msg); - } - } - fn seek(&self, dist: int, seek: io::SeekStyle) { - debug!("tcp_socket_buf seek stub %? %?", dist, seek); - // noop - } - fn tell(&self) -> uint { - 0u - } - fn flush(&self) -> int { - 0 - } - fn get_type(&self) -> io::WriterType { - io::File - } -} - -// INTERNAL API - -fn tear_down_socket_data(socket_data: @TcpSocketData) { - unsafe { - let (closed_po, closed_ch) = stream::<()>(); - let closed_ch = SharedChan::new(closed_ch); - let close_data = TcpSocketCloseData { - closed_ch: closed_ch - }; - let close_data_ptr: *TcpSocketCloseData = &close_data; - let stream_handle_ptr = (*socket_data).stream_handle_ptr; - do iotask::interact(&(*socket_data).iotask) |loop_ptr| { - debug!( - "interact dtor for tcp_socket stream %? loop %?", - stream_handle_ptr, loop_ptr); - uv::ll::set_data_for_uv_handle(stream_handle_ptr, - close_data_ptr); - uv::ll::close(stream_handle_ptr, tcp_socket_dtor_close_cb); - }; - closed_po.recv(); - //the line below will most likely crash - //log(debug, fmt!("about to free socket_data at %?", socket_data)); - rustrt::rust_uv_current_kernel_free(stream_handle_ptr - as *libc::c_void); - debug!("exiting dtor for tcp_socket"); - } -} - -// shared implementation for tcp::read -fn read_common_impl(socket_data: *TcpSocketData, timeout_msecs: uint) - -> result::Result<~[u8],TcpErrData> { - unsafe { - use timer; - - debug!("starting tcp::read"); - let iotask = &(*socket_data).iotask; - let rs_result = read_start_common_impl(socket_data); - if result::is_err(&rs_result) { - let err_data = result::get_err(&rs_result); - result::Err(err_data) - } - else { - debug!("tcp::read before recv_timeout"); - let read_result = if timeout_msecs > 0u { - timer::recv_timeout( - iotask, timeout_msecs, result::unwrap(rs_result)) - } else { - Some(result::get(&rs_result).recv()) - }; - debug!("tcp::read after recv_timeout"); - match read_result { - None => { - debug!("tcp::read: timed out.."); - let err_data = TcpErrData { - err_name: ~"TIMEOUT", - err_msg: ~"req timed out" - }; - read_stop_common_impl(socket_data); - result::Err(err_data) - } - Some(data_result) => { - debug!("tcp::read got data"); - read_stop_common_impl(socket_data); - data_result - } - } - } - } -} - -// shared impl for read_stop -fn read_stop_common_impl(socket_data: *TcpSocketData) -> - result::Result<(), TcpErrData> { - unsafe { - let stream_handle_ptr = (*socket_data).stream_handle_ptr; - let (stop_po, stop_ch) = stream::>(); - do iotask::interact(&(*socket_data).iotask) |loop_ptr| { - debug!("in interact cb for tcp::read_stop"); - match uv::ll::read_stop(stream_handle_ptr - as *uv::ll::uv_stream_t) { - 0i32 => { - debug!("successfully called uv_read_stop"); - stop_ch.send(None); - } - _ => { - debug!("failure in calling uv_read_stop"); - let err_data = uv::ll::get_last_err_data(loop_ptr); - stop_ch.send(Some(err_data.to_tcp_err())); - } - } - } - match stop_po.recv() { - Some(err_data) => Err(err_data), - None => Ok(()) - } - } -} - -// shared impl for read_start -fn read_start_common_impl(socket_data: *TcpSocketData) - -> result::Result<@Port< - result::Result<~[u8], TcpErrData>>, TcpErrData> { - unsafe { - let stream_handle_ptr = (*socket_data).stream_handle_ptr; - let (start_po, start_ch) = stream::>(); - debug!("in tcp::read_start before interact loop"); - do iotask::interact(&(*socket_data).iotask) |loop_ptr| { - debug!("in tcp::read_start interact cb %?", - loop_ptr); - match uv::ll::read_start(stream_handle_ptr - as *uv::ll::uv_stream_t, - on_alloc_cb, - on_tcp_read_cb) { - 0i32 => { - debug!("success doing uv_read_start"); - start_ch.send(None); - } - _ => { - debug!("error attempting uv_read_start"); - let err_data = uv::ll::get_last_err_data(loop_ptr); - start_ch.send(Some(err_data)); - } - } - } - match start_po.recv() { - Some(ref err_data) => result::Err( - err_data.to_tcp_err()), - None => { - result::Ok((*socket_data).reader_po) - } - } - } -} - -// helper to convert a "class" vector of [u8] to a *[uv::ll::uv_buf_t] - -// shared implementation used by write and write_future -fn write_common_impl(socket_data_ptr: *TcpSocketData, - raw_write_data: ~[u8]) - -> result::Result<(), TcpErrData> { - unsafe { - let write_req_ptr: *uv::ll::uv_write_t = - &(*socket_data_ptr).write_req; - let stream_handle_ptr = - (*socket_data_ptr).stream_handle_ptr; - let write_buf_vec = ~[ - uv::ll::buf_init(vec::raw::to_ptr(raw_write_data), - raw_write_data.len()) - ]; - let write_buf_vec_ptr: *~[uv::ll::uv_buf_t] = &write_buf_vec; - let (result_po, result_ch) = stream::(); - let result_ch = SharedChan::new(result_ch); - let write_data = WriteReqData { - result_ch: result_ch - }; - let write_data_ptr: *WriteReqData = &write_data; - do iotask::interact(&(*socket_data_ptr).iotask) |loop_ptr| { - debug!("in interact cb for tcp::write %?", - loop_ptr); - match uv::ll::write(write_req_ptr, - stream_handle_ptr, - write_buf_vec_ptr, - tcp_write_complete_cb) { - 0i32 => { - debug!("uv_write() invoked successfully"); - uv::ll::set_data_for_req(write_req_ptr, - write_data_ptr); - } - _ => { - debug!("error invoking uv_write()"); - let err_data = uv::ll::get_last_err_data(loop_ptr); - let result_ch = (*write_data_ptr).result_ch.clone(); - result_ch.send(TcpWriteError(err_data.to_tcp_err())); - } - } - } - // FIXME (#2656): Instead of passing unsafe pointers to local data, - // and waiting here for the write to complete, we should transfer - // ownership of everything to the I/O task and let it deal with the - // aftermath, so we don't have to sit here blocking. - match result_po.recv() { - TcpWriteSuccess => Ok(()), - TcpWriteError(err_data) => Err(err_data) - } - } -} - -enum TcpNewConnection { - NewTcpConn(*uv::ll::uv_tcp_t) -} - -struct TcpListenFcData { - server_stream_ptr: *uv::ll::uv_tcp_t, - stream_closed_ch: SharedChan<()>, - kill_ch: SharedChan>, - on_connect_cb: ~fn(*uv::ll::uv_tcp_t), - iotask: IoTask, - ipv6: bool, - active: @mut bool, -} - -extern fn tcp_lfc_close_cb(handle: *uv::ll::uv_tcp_t) { - unsafe { - let server_data_ptr = uv::ll::get_data_for_uv_handle( - handle) as *TcpListenFcData; - let stream_closed_ch = (*server_data_ptr).stream_closed_ch.clone(); - stream_closed_ch.send(()); - } -} - -extern fn tcp_lfc_on_connection_cb(handle: *uv::ll::uv_tcp_t, - status: libc::c_int) { - unsafe { - let server_data_ptr = uv::ll::get_data_for_uv_handle(handle) - as *TcpListenFcData; - let kill_ch = (*server_data_ptr).kill_ch.clone(); - if *(*server_data_ptr).active { - match status { - 0i32 => ((*server_data_ptr).on_connect_cb)(handle), - _ => { - let loop_ptr = uv::ll::get_loop_for_uv_handle(handle); - kill_ch.send( - Some(uv::ll::get_last_err_data(loop_ptr) - .to_tcp_err())); - *(*server_data_ptr).active = false; - } - } - } - } -} - -fn malloc_uv_tcp_t() -> *uv::ll::uv_tcp_t { - unsafe { - rustrt::rust_uv_current_kernel_malloc( - rustrt::rust_uv_helper_uv_tcp_t_size()) as *uv::ll::uv_tcp_t - } -} - -enum TcpConnectResult { - TcpConnected(TcpSocket), - TcpConnectError(TcpErrData) -} - -enum TcpWriteResult { - TcpWriteSuccess, - TcpWriteError(TcpErrData) -} - -enum TcpReadStartResult { - TcpReadStartSuccess(Port), - TcpReadStartError(TcpErrData) -} - -enum TcpReadResult { - TcpReadData(~[u8]), - TcpReadDone, - TcpReadErr(TcpErrData) -} - -trait ToTcpErr { - fn to_tcp_err(&self) -> TcpErrData; -} - -impl ToTcpErr for uv::ll::uv_err_data { - fn to_tcp_err(&self) -> TcpErrData { - TcpErrData { err_name: copy self.err_name, err_msg: copy self.err_msg } - } -} - -extern fn on_tcp_read_cb(stream: *uv::ll::uv_stream_t, - nread: libc::ssize_t, - buf: uv::ll::uv_buf_t) { - unsafe { - debug!("entering on_tcp_read_cb stream: %x nread: %?", - stream as uint, nread); - let loop_ptr = uv::ll::get_loop_for_uv_handle(stream); - let socket_data_ptr = uv::ll::get_data_for_uv_handle(stream) - as *TcpSocketData; - debug!("socket data is %x", socket_data_ptr as uint); - match nread as int { - // incoming err.. probably eof - -1 => { - let err_data = uv::ll::get_last_err_data(loop_ptr).to_tcp_err(); - debug!("on_tcp_read_cb: incoming err.. name %? msg %?", - err_data.err_name, err_data.err_msg); - let reader_ch = &(*socket_data_ptr).reader_ch; - reader_ch.send(result::Err(err_data)); - } - // do nothing .. unneeded buf - 0 => (), - // have data - _ => { - // we have data - debug!("tcp on_read_cb nread: %d", nread as int); - let reader_ch = &(*socket_data_ptr).reader_ch; - let buf_base = uv::ll::get_base_from_buf(buf); - let new_bytes = vec::from_buf(buf_base, nread as uint); - reader_ch.send(result::Ok(new_bytes)); - } - } - uv::ll::free_base_of_buf(buf); - debug!("exiting on_tcp_read_cb"); - } -} - -extern fn on_alloc_cb(handle: *libc::c_void, - suggested_size: size_t) - -> uv::ll::uv_buf_t { - unsafe { - debug!("tcp read on_alloc_cb!"); - let char_ptr = uv::ll::malloc_buf_base_of(suggested_size); - debug!("tcp read on_alloc_cb h: %? char_ptr: %u sugsize: %u", - handle, - char_ptr as uint, - suggested_size as uint); - uv::ll::buf_init(char_ptr, suggested_size as uint) - } -} - -struct TcpSocketCloseData { - closed_ch: SharedChan<()>, -} - -extern fn tcp_socket_dtor_close_cb(handle: *uv::ll::uv_tcp_t) { - unsafe { - let data = uv::ll::get_data_for_uv_handle(handle) - as *TcpSocketCloseData; - let closed_ch = (*data).closed_ch.clone(); - closed_ch.send(()); - debug!("tcp_socket_dtor_close_cb exiting.."); - } -} - -extern fn tcp_write_complete_cb(write_req: *uv::ll::uv_write_t, - status: libc::c_int) { - unsafe { - let write_data_ptr = uv::ll::get_data_for_req(write_req) - as *WriteReqData; - if status == 0i32 { - debug!("successful write complete"); - let result_ch = (*write_data_ptr).result_ch.clone(); - result_ch.send(TcpWriteSuccess); - } else { - let stream_handle_ptr = uv::ll::get_stream_handle_from_write_req( - write_req); - let loop_ptr = uv::ll::get_loop_for_uv_handle(stream_handle_ptr); - let err_data = uv::ll::get_last_err_data(loop_ptr); - debug!("failure to write"); - let result_ch = (*write_data_ptr).result_ch.clone(); - result_ch.send(TcpWriteError(err_data.to_tcp_err())); - } - } -} - -struct WriteReqData { - result_ch: SharedChan, -} - -struct ConnectReqData { - result_ch: SharedChan, - closed_signal_ch: SharedChan<()>, -} - -extern fn stream_error_close_cb(handle: *uv::ll::uv_tcp_t) { - unsafe { - let data = uv::ll::get_data_for_uv_handle(handle) as - *ConnectReqData; - let closed_signal_ch = (*data).closed_signal_ch.clone(); - closed_signal_ch.send(()); - debug!("exiting steam_error_close_cb for %?", handle); - } -} - -extern fn tcp_connect_close_cb(handle: *uv::ll::uv_tcp_t) { - debug!("closed client tcp handle %?", handle); -} - -extern fn tcp_connect_on_connect_cb(connect_req_ptr: *uv::ll::uv_connect_t, - status: libc::c_int) { - unsafe { - let conn_data_ptr = (uv::ll::get_data_for_req(connect_req_ptr) - as *ConnectReqData); - let result_ch = (*conn_data_ptr).result_ch.clone(); - debug!("tcp_connect result_ch %?", result_ch); - let tcp_stream_ptr = - uv::ll::get_stream_handle_from_connect_req(connect_req_ptr); - match status { - 0i32 => { - debug!("successful tcp connection!"); - result_ch.send(ConnSuccess); - } - _ => { - debug!("error in tcp_connect_on_connect_cb"); - let loop_ptr = uv::ll::get_loop_for_uv_handle(tcp_stream_ptr); - let err_data = uv::ll::get_last_err_data(loop_ptr); - debug!("err_data %? %?", err_data.err_name, - err_data.err_msg); - result_ch.send(ConnFailure(err_data)); - uv::ll::set_data_for_uv_handle(tcp_stream_ptr, - conn_data_ptr); - uv::ll::close(tcp_stream_ptr, stream_error_close_cb); - } - } - debug!("leaving tcp_connect_on_connect_cb"); - } -} - -enum ConnAttempt { - ConnSuccess, - ConnFailure(uv::ll::uv_err_data) -} - -struct TcpSocketData { - reader_po: @Port>, - reader_ch: SharedChan>, - stream_handle_ptr: *uv::ll::uv_tcp_t, - connect_req: uv::ll::uv_connect_t, - write_req: uv::ll::uv_write_t, - ipv6: bool, - iotask: IoTask, -} - -struct TcpBufferedSocketData { - sock: TcpSocket, - buf: ~[u8], - buf_off: uint -} - -#[cfg(test)] -mod test { - - use net::ip; - use net::tcp::{GenericListenErr, TcpConnectErrData, TcpListenErrData}; - use net::tcp::{connect, accept, read, listen, TcpSocket, socket_buf}; - use net; - use uv::iotask::IoTask; - use uv; - - use std::cell::Cell; - use std::comm::{stream, SharedChan}; - use std::io; - use std::result; - use std::str; - use std::task; - - // FIXME don't run on fbsd or linux 32 bit (#2064) - #[cfg(target_os="win32")] - #[cfg(target_os="darwin")] - #[cfg(target_os="linux")] - #[cfg(target_os="android")] - mod tcp_ipv4_server_and_client_test { - #[cfg(target_arch="x86_64")] - mod impl64 { - use net::tcp::test::*; - - #[test] - fn test_gl_tcp_server_and_client_ipv4() { - impl_gl_tcp_ipv4_server_and_client(); - } - #[test] - fn test_gl_tcp_get_peer_addr() { - impl_gl_tcp_ipv4_get_peer_addr(); - } - #[test] - fn test_gl_tcp_ipv4_client_error_connection_refused() { - impl_gl_tcp_ipv4_client_error_connection_refused(); - } - #[test] - fn test_gl_tcp_server_address_in_use() { - impl_gl_tcp_ipv4_server_address_in_use(); - } - #[test] - fn test_gl_tcp_server_access_denied() { - impl_gl_tcp_ipv4_server_access_denied(); - } - // Strange failure on Windows. --pcwalton - #[test] - #[ignore(cfg(target_os = "win32"))] - fn test_gl_tcp_ipv4_server_client_reader_writer() { - impl_gl_tcp_ipv4_server_client_reader_writer(); - } - #[test] - fn test_tcp_socket_impl_reader_handles_eof() { - impl_tcp_socket_impl_reader_handles_eof(); - } - } - #[cfg(target_arch="x86")] - #[cfg(target_arch="arm")] - #[cfg(target_arch="mips")] - mod impl32 { - use net::tcp::test::*; - - #[test] - #[ignore(cfg(target_os = "linux"))] - fn test_gl_tcp_server_and_client_ipv4() { - unsafe { - impl_gl_tcp_ipv4_server_and_client(); - } - } - #[test] - #[ignore(cfg(target_os = "linux"))] - fn test_gl_tcp_get_peer_addr() { - unsafe { - impl_gl_tcp_ipv4_get_peer_addr(); - } - } - #[test] - #[ignore(cfg(target_os = "linux"))] - fn test_gl_tcp_ipv4_client_error_connection_refused() { - unsafe { - impl_gl_tcp_ipv4_client_error_connection_refused(); - } - } - #[test] - #[ignore(cfg(target_os = "linux"))] - fn test_gl_tcp_server_address_in_use() { - unsafe { - impl_gl_tcp_ipv4_server_address_in_use(); - } - } - #[test] - #[ignore(cfg(target_os = "linux"))] - #[ignore(cfg(windows), reason = "deadlocking bots")] - fn test_gl_tcp_server_access_denied() { - unsafe { - impl_gl_tcp_ipv4_server_access_denied(); - } - } - #[test] - #[ignore(cfg(target_os = "linux"))] - #[ignore(cfg(target_os = "win32"))] - fn test_gl_tcp_ipv4_server_client_reader_writer() { - impl_gl_tcp_ipv4_server_client_reader_writer(); - } - } - } - pub fn impl_gl_tcp_ipv4_server_and_client() { - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 8888u; - let expected_req = ~"ping"; - let expected_resp = "pong"; - - let (server_result_po, server_result_ch) = stream::<~str>(); - - let (cont_po, cont_ch) = stream::<()>(); - let cont_ch = SharedChan::new(cont_ch); - // server - let hl_loop_clone = hl_loop.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - let cont_ch = cont_ch.clone(); - let actual_req = run_tcp_test_server( - server_ip, - server_port, - expected_resp.to_str(), - cont_ch.clone(), - &hl_loop_clone); - server_result_ch.send(actual_req); - }; - cont_po.recv(); - // client - debug!("server started, firing up client.."); - let actual_resp_result = run_tcp_test_client( - server_ip, - server_port, - expected_req, - hl_loop); - assert!(actual_resp_result.is_ok()); - let actual_resp = actual_resp_result.get(); - let actual_req = server_result_po.recv(); - debug!("REQ: expected: '%s' actual: '%s'", - expected_req, actual_req); - debug!("RESP: expected: '%s' actual: '%s'", - expected_resp, actual_resp); - assert!(actual_req.contains(expected_req)); - assert!(actual_resp.contains(expected_resp)); - } - pub fn impl_gl_tcp_ipv4_get_peer_addr() { - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 8887u; - let expected_resp = "pong"; - - let (cont_po, cont_ch) = stream::<()>(); - let cont_ch = SharedChan::new(cont_ch); - // server - let hl_loop_clone = hl_loop.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - let cont_ch = cont_ch.clone(); - run_tcp_test_server( - server_ip, - server_port, - expected_resp.to_str(), - cont_ch.clone(), - &hl_loop_clone); - }; - cont_po.recv(); - // client - debug!("server started, firing up client.."); - let server_ip_addr = ip::v4::parse_addr(server_ip); - let iotask = uv::global_loop::get(); - let connect_result = connect(server_ip_addr, server_port, - &iotask); - - let sock = result::unwrap(connect_result); - - debug!("testing peer address"); - // This is what we are actually testing! - assert!(net::ip::format_addr(&sock.get_peer_addr()) == - ~"127.0.0.1"); - assert_eq!(net::ip::get_port(&sock.get_peer_addr()), 8887); - - // Fulfill the protocol the test server expects - let resp_bytes = "ping".as_bytes().to_owned(); - tcp_write_single(&sock, resp_bytes); - debug!("message sent"); - sock.read(0u); - debug!("result read"); - } - pub fn impl_gl_tcp_ipv4_client_error_connection_refused() { - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 8889u; - let expected_req = ~"ping"; - // client - debug!("firing up client.."); - let actual_resp_result = run_tcp_test_client( - server_ip, - server_port, - expected_req, - hl_loop); - match actual_resp_result.get_err() { - ConnectionRefused => (), - _ => fail!("unknown error.. expected connection_refused") - } - } - pub fn impl_gl_tcp_ipv4_server_address_in_use() { - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 8890u; - let expected_req = ~"ping"; - let expected_resp = "pong"; - - let (cont_po, cont_ch) = stream::<()>(); - let cont_ch = SharedChan::new(cont_ch); - // server - let hl_loop_clone = hl_loop.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - let cont_ch = cont_ch.clone(); - run_tcp_test_server( - server_ip, - server_port, - expected_resp.to_str(), - cont_ch.clone(), - &hl_loop_clone); - } - cont_po.recv(); - // this one should fail.. - let listen_err = run_tcp_test_server_fail( - server_ip, - server_port, - hl_loop); - // client.. just doing this so that the first server tears down - debug!("server started, firing up client.."); - run_tcp_test_client( - server_ip, - server_port, - expected_req, - hl_loop); - match listen_err { - AddressInUse => { - assert!(true); - } - _ => { - fail!("expected address_in_use listen error, \ - but got a different error varient. check logs."); - } - } - } - pub fn impl_gl_tcp_ipv4_server_access_denied() { - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 80u; - // this one should fail.. - let listen_err = run_tcp_test_server_fail( - server_ip, - server_port, - hl_loop); - match listen_err { - AccessDenied => { - assert!(true); - } - _ => { - fail!("expected address_in_use listen error, \ - but got a different error varient. check logs."); - } - } - } - pub fn impl_gl_tcp_ipv4_server_client_reader_writer() { - - let iotask = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 8891u; - let expected_req = ~"ping"; - let expected_resp = "pong"; - - let (server_result_po, server_result_ch) = stream::<~str>(); - - let (cont_po, cont_ch) = stream::<()>(); - let cont_ch = SharedChan::new(cont_ch); - // server - let iotask_clone = iotask.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - let cont_ch = cont_ch.clone(); - let actual_req = run_tcp_test_server( - server_ip, - server_port, - expected_resp.to_str(), - cont_ch.clone(), - &iotask_clone); - server_result_ch.send(actual_req); - }; - cont_po.recv(); - // client - let server_addr = ip::v4::parse_addr(server_ip); - let conn_result = connect(server_addr, server_port, iotask); - if result::is_err(&conn_result) { - assert!(false); - } - let sock_buf = @socket_buf(result::unwrap(conn_result)); - buf_write(sock_buf, expected_req); - - // so contrived! - let actual_resp = buf_read(sock_buf, expected_resp.as_bytes().len()); - - let actual_req = server_result_po.recv(); - debug!("REQ: expected: '%s' actual: '%s'", - expected_req, actual_req); - debug!("RESP: expected: '%s' actual: '%s'", - expected_resp, actual_resp); - assert!(actual_req.contains(expected_req)); - assert!(actual_resp.contains(expected_resp)); - } - - pub fn impl_tcp_socket_impl_reader_handles_eof() { - use std::io::{Reader,ReaderUtil}; - - let hl_loop = &uv::global_loop::get(); - let server_ip = "127.0.0.1"; - let server_port = 10041u; - let expected_req = ~"GET /"; - let expected_resp = "A string\nwith multiple lines\n"; - - let (cont_po, cont_ch) = stream::<()>(); - let cont_ch = SharedChan::new(cont_ch); - // server - let hl_loop_clone = hl_loop.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - let cont_ch = cont_ch.clone(); - run_tcp_test_server( - server_ip, - server_port, - expected_resp.to_str(), - cont_ch.clone(), - &hl_loop_clone); - }; - cont_po.recv(); - // client - debug!("server started, firing up client.."); - let server_addr = ip::v4::parse_addr(server_ip); - let conn_result = connect(server_addr, server_port, hl_loop); - if result::is_err(&conn_result) { - assert!(false); - } - let sock_buf = @socket_buf(result::unwrap(conn_result)); - buf_write(sock_buf, expected_req); - - let buf_reader = sock_buf as @Reader; - let actual_response = str::from_bytes(buf_reader.read_whole_stream()); - debug!("Actual response: %s", actual_response); - assert!(expected_resp == actual_response); - } - - fn buf_write(w: &W, val: &str) { - debug!("BUF_WRITE: val len %?", val.len()); - let b_slice = val.as_bytes(); - debug!("BUF_WRITE: b_slice len %?", - b_slice.len()); - w.write(b_slice) - } - - fn buf_read(r: &R, len: uint) -> ~str { - let new_bytes = (*r).read_bytes(len); - debug!("in buf_read.. new_bytes len: %?", - new_bytes.len()); - str::from_bytes(new_bytes) - } - - fn run_tcp_test_server(server_ip: &str, server_port: uint, resp: ~str, - cont_ch: SharedChan<()>, - iotask: &IoTask) -> ~str { - let (server_po, server_ch) = stream::<~str>(); - let server_ch = SharedChan::new(server_ch); - let server_ip_addr = ip::v4::parse_addr(server_ip); - let resp_cell = Cell::new(resp); - let listen_result = listen(server_ip_addr, server_port, 128, - iotask, - // on_establish_cb -- called when listener is set up - |kill_ch| { - debug!("establish_cb %?", - kill_ch); - cont_ch.send(()); - }, - // risky to run this on the loop, but some users - // will want the POWER - |new_conn, kill_ch| { - let resp_cell2 = Cell::new(resp_cell.take()); - debug!("SERVER: new connection!"); - let (cont_po, cont_ch) = stream(); - let server_ch = server_ch.clone(); - do task::spawn_sched(task::ManualThreads(1u)) { - debug!("SERVER: starting worker for new req"); - - let accept_result = accept(new_conn); - debug!("SERVER: after accept()"); - if result::is_err(&accept_result) { - debug!("SERVER: error accept connection"); - let err_data = result::get_err(&accept_result); - kill_ch.send(Some(err_data)); - debug!( - "SERVER/WORKER: send on err cont ch"); - cont_ch.send(()); - } - else { - debug!("SERVER/WORKER: send on cont ch"); - cont_ch.send(()); - let sock = result::unwrap(accept_result); - let peer_addr = sock.get_peer_addr(); - debug!("SERVER: successfully accepted \ - connection from %s:%u", - ip::format_addr(&peer_addr), - ip::get_port(&peer_addr)); - let received_req_bytes = read(&sock, 0u); - match received_req_bytes { - result::Ok(data) => { - debug!("SERVER: got REQ str::from_bytes.."); - debug!("SERVER: REQ data len: %?", - data.len()); - server_ch.send( - str::from_bytes(data)); - debug!("SERVER: before write"); - let s = resp_cell2.take(); - tcp_write_single(&sock, s.as_bytes().to_owned()); - debug!("SERVER: after write.. die"); - kill_ch.send(None); - } - result::Err(err_data) => { - debug!("SERVER: error recvd: %s %s", - err_data.err_name, err_data.err_msg); - kill_ch.send(Some(err_data)); - server_ch.send(~""); - } - } - debug!("SERVER: worker spinning down"); - } - } - debug!("SERVER: waiting to recv on cont_ch"); - cont_po.recv(); - }); - // err check on listen_result - if result::is_err(&listen_result) { - match result::get_err(&listen_result) { - GenericListenErr(ref name, ref msg) => { - fail!("SERVER: exited abnormally name %s msg %s", *name, *msg); - } - AccessDenied => { - fail!("SERVER: exited abnormally, got access denied.."); - } - AddressInUse => { - fail!("SERVER: exited abnormally, got address in use..."); - } - } - } - let ret_val = server_po.recv(); - debug!("SERVER: exited and got return val: '%s'", ret_val); - ret_val - } - - fn run_tcp_test_server_fail(server_ip: &str, server_port: uint, - iotask: &IoTask) -> TcpListenErrData { - let server_ip_addr = ip::v4::parse_addr(server_ip); - let listen_result = listen(server_ip_addr, server_port, 128, - iotask, - // on_establish_cb -- called when listener is set up - |kill_ch| { - debug!("establish_cb %?", kill_ch); - }, - |new_conn, kill_ch| { - fail!("SERVER: shouldn't be called.. %? %?", new_conn, kill_ch); - }); - // err check on listen_result - if result::is_err(&listen_result) { - result::get_err(&listen_result) - } - else { - fail!("SERVER: did not fail as expected") - } - } - - fn run_tcp_test_client(server_ip: &str, server_port: uint, resp: &str, - iotask: &IoTask) -> result::Result<~str, - TcpConnectErrData> { - let server_ip_addr = ip::v4::parse_addr(server_ip); - - debug!("CLIENT: starting.."); - let connect_result = connect(server_ip_addr, server_port, - iotask); - if result::is_err(&connect_result) { - debug!("CLIENT: failed to connect"); - let err_data = result::get_err(&connect_result); - Err(err_data) - } - else { - let sock = result::unwrap(connect_result); - let resp_bytes = resp.as_bytes().to_owned(); - tcp_write_single(&sock, resp_bytes); - let read_result = sock.read(0u); - if read_result.is_err() { - debug!("CLIENT: failure to read"); - Ok(~"") - } - else { - let ret_val = str::from_bytes(read_result.get()); - debug!("CLIENT: after client_ch recv ret: '%s'", - ret_val); - Ok(ret_val) - } - } - } - - fn tcp_write_single(sock: &TcpSocket, val: ~[u8]) { - let mut write_result_future = sock.write_future(val); - let write_result = write_result_future.get(); - if result::is_err(&write_result) { - debug!("tcp_write_single: write failed!"); - let err_data = result::get_err(&write_result); - debug!("tcp_write_single err name: %s msg: %s", - err_data.err_name, err_data.err_msg); - // meh. torn on what to do here. - fail!("tcp_write_single failed"); - } - } -} diff --git a/src/libextra/net_url.rs b/src/libextra/net_url.rs deleted file mode 100644 index 9ac58efe793..00000000000 --- a/src/libextra/net_url.rs +++ /dev/null @@ -1,1071 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Types/fns concerning URLs (see RFC 3986) - -#[allow(missing_doc)]; - - -use std::cmp::Eq; -use std::io::{Reader, ReaderUtil}; -use std::io; -use std::hashmap::HashMap; -use std::to_bytes; -use std::uint; - -#[deriving(Clone, Eq)] -struct Url { - scheme: ~str, - user: Option, - host: ~str, - port: Option<~str>, - path: ~str, - query: Query, - fragment: Option<~str> -} - -#[deriving(Clone, Eq)] -struct UserInfo { - user: ~str, - pass: Option<~str> -} - -pub type Query = ~[(~str, ~str)]; - -impl Url { - pub fn new(scheme: ~str, - user: Option, - host: ~str, - port: Option<~str>, - path: ~str, - query: Query, - fragment: Option<~str>) - -> Url { - Url { - scheme: scheme, - user: user, - host: host, - port: port, - path: path, - query: query, - fragment: fragment, - } - } -} - -impl UserInfo { - pub fn new(user: ~str, pass: Option<~str>) -> UserInfo { - UserInfo { user: user, pass: pass } - } -} - -fn encode_inner(s: &str, full_url: bool) -> ~str { - do io::with_str_reader(s) |rdr| { - let mut out = ~""; - - while !rdr.eof() { - let ch = rdr.read_byte() as char; - match ch { - // unreserved: - 'A' .. 'Z' | - 'a' .. 'z' | - '0' .. '9' | - '-' | '.' | '_' | '~' => { - out.push_char(ch); - } - _ => { - if full_url { - match ch { - // gen-delims: - ':' | '/' | '?' | '#' | '[' | ']' | '@' | - - // sub-delims: - '!' | '$' | '&' | '"' | '(' | ')' | '*' | - '+' | ',' | ';' | '=' => { - out.push_char(ch); - } - - _ => out.push_str(fmt!("%%%X", ch as uint)) - } - } else { - out.push_str(fmt!("%%%X", ch as uint)); - } - } - } - } - - out - } -} - -/** - * Encodes a URI by replacing reserved characters with percent encoded - * character sequences. - * - * This function is compliant with RFC 3986. - */ -pub fn encode(s: &str) -> ~str { - encode_inner(s, true) -} - -/** - * Encodes a URI component by replacing reserved characters with percent - * encoded character sequences. - * - * This function is compliant with RFC 3986. - */ - -pub fn encode_component(s: &str) -> ~str { - encode_inner(s, false) -} - -fn decode_inner(s: &str, full_url: bool) -> ~str { - do io::with_str_reader(s) |rdr| { - let mut out = ~""; - - while !rdr.eof() { - match rdr.read_char() { - '%' => { - let bytes = rdr.read_bytes(2u); - let ch = uint::parse_bytes(bytes, 16u).get() as char; - - if full_url { - // Only decode some characters: - match ch { - // gen-delims: - ':' | '/' | '?' | '#' | '[' | ']' | '@' | - - // sub-delims: - '!' | '$' | '&' | '"' | '(' | ')' | '*' | - '+' | ',' | ';' | '=' => { - out.push_char('%'); - out.push_char(bytes[0u] as char); - out.push_char(bytes[1u] as char); - } - - ch => out.push_char(ch) - } - } else { - out.push_char(ch); - } - } - ch => out.push_char(ch) - } - } - - out - } -} - -/** - * Decode a string encoded with percent encoding. - * - * This will only decode escape sequences generated by encode. - */ -pub fn decode(s: &str) -> ~str { - decode_inner(s, true) -} - -/** - * Decode a string encoded with percent encoding. - */ -pub fn decode_component(s: &str) -> ~str { - decode_inner(s, false) -} - -fn encode_plus(s: &str) -> ~str { - do io::with_str_reader(s) |rdr| { - let mut out = ~""; - - while !rdr.eof() { - let ch = rdr.read_byte() as char; - match ch { - 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => { - out.push_char(ch); - } - ' ' => out.push_char('+'), - _ => out.push_str(fmt!("%%%X", ch as uint)) - } - } - - out - } -} - -/** - * Encode a hashmap to the 'application/x-www-form-urlencoded' media type. - */ -pub fn encode_form_urlencoded(m: &HashMap<~str, ~[~str]>) -> ~str { - let mut out = ~""; - let mut first = true; - - for m.iter().advance |(key, values)| { - let key = encode_plus(*key); - - for values.iter().advance |value| { - if first { - first = false; - } else { - out.push_char('&'); - first = false; - } - - out.push_str(fmt!("%s=%s", key, encode_plus(*value))); - } - } - - out -} - -/** - * Decode a string encoded with the 'application/x-www-form-urlencoded' media - * type into a hashmap. - */ -pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<~str, ~[~str]> { - do io::with_bytes_reader(s) |rdr| { - let mut m = HashMap::new(); - let mut key = ~""; - let mut value = ~""; - let mut parsing_key = true; - - while !rdr.eof() { - match rdr.read_char() { - '&' | ';' => { - if key != ~"" && value != ~"" { - let mut values = match m.pop(&key) { - Some(values) => values, - None => ~[], - }; - - values.push(value); - m.insert(key, values); - } - - parsing_key = true; - key = ~""; - value = ~""; - } - '=' => parsing_key = false, - ch => { - let ch = match ch { - '%' => { - let bytes = rdr.read_bytes(2u); - uint::parse_bytes(bytes, 16u).get() as char - } - '+' => ' ', - ch => ch - }; - - if parsing_key { - key.push_char(ch) - } else { - value.push_char(ch) - } - } - } - } - - if key != ~"" && value != ~"" { - let mut values = match m.pop(&key) { - Some(values) => values, - None => ~[], - }; - - values.push(value); - m.insert(key, values); - } - - m - } -} - - -fn split_char_first(s: &str, c: char) -> (~str, ~str) { - let len = s.len(); - let mut index = len; - let mut mat = 0; - do io::with_str_reader(s) |rdr| { - let mut ch; - while !rdr.eof() { - ch = rdr.read_byte() as char; - if ch == c { - // found a match, adjust markers - index = rdr.tell()-1; - mat = 1; - break; - } - } - } - if index+mat == len { - return (s.slice(0, index).to_owned(), ~""); - } else { - return (s.slice(0, index).to_owned(), - s.slice(index + mat, s.len()).to_owned()); - } -} - -fn userinfo_from_str(uinfo: &str) -> UserInfo { - let (user, p) = split_char_first(uinfo, ':'); - let pass = if p.is_empty() { - None - } else { - Some(p) - }; - return UserInfo::new(user, pass); -} - -fn userinfo_to_str(userinfo: &UserInfo) -> ~str { - match userinfo.pass { - Some(ref pass) => fmt!("%s:%s@", userinfo.user, *pass), - None => fmt!("%s@", userinfo.user), - } -} - -fn query_from_str(rawquery: &str) -> Query { - let mut query: Query = ~[]; - if !rawquery.is_empty() { - for rawquery.split_iter('&').advance |p| { - let (k, v) = split_char_first(p, '='); - query.push((decode_component(k), decode_component(v))); - }; - } - return query; -} - -pub fn query_to_str(query: &Query) -> ~str { - let mut strvec = ~[]; - for query.iter().advance |kv| { - match kv { - &(ref k, ref v) => { - strvec.push(fmt!("%s=%s", - encode_component(*k), - encode_component(*v)) - ); - } - } - } - return strvec.connect("&"); -} - -// returns the scheme and the rest of the url, or a parsing error -pub fn get_scheme(rawurl: &str) -> Result<(~str, ~str), ~str> { - for rawurl.iter().enumerate().advance |(i,c)| { - match c { - 'A' .. 'Z' | 'a' .. 'z' => loop, - '0' .. '9' | '+' | '-' | '.' => { - if i == 0 { - return Err(~"url: Scheme must begin with a letter."); - } - loop; - } - ':' => { - if i == 0 { - return Err(~"url: Scheme cannot be empty."); - } else { - return Ok((rawurl.slice(0,i).to_owned(), - rawurl.slice(i+1,rawurl.len()).to_owned())); - } - } - _ => { - return Err(~"url: Invalid character in scheme."); - } - } - }; - return Err(~"url: Scheme must be terminated with a colon."); -} - -#[deriving(Clone, Eq)] -enum Input { - Digit, // all digits - Hex, // digits and letters a-f - Unreserved // all other legal characters -} - -// returns userinfo, host, port, and unparsed part, or an error -fn get_authority(rawurl: &str) -> - Result<(Option, ~str, Option<~str>, ~str), ~str> { - if !rawurl.starts_with("//") { - // there is no authority. - return Ok((None, ~"", None, rawurl.to_str())); - } - - enum State { - Start, // starting state - PassHostPort, // could be in user or port - Ip6Port, // either in ipv6 host or port - Ip6Host, // are in an ipv6 host - InHost, // are in a host - may be ipv6, but don't know yet - InPort // are in port - } - - let len = rawurl.len(); - let mut st = Start; - let mut in = Digit; // most restricted, start here. - - let mut userinfo = None; - let mut host = ~""; - let mut port = None; - - let mut colon_count = 0; - let mut pos = 0; - let mut begin = 2; - let mut end = len; - - for rawurl.iter().enumerate().advance |(i,c)| { - if i < 2 { loop; } // ignore the leading // - - // deal with input class first - match c { - '0' .. '9' => (), - 'A' .. 'F' | 'a' .. 'f' => { - if in == Digit { - in = Hex; - } - } - 'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' | - '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => { - in = Unreserved; - } - ':' | '@' | '?' | '#' | '/' => { - // separators, don't change anything - } - _ => { - return Err(~"Illegal character in authority"); - } - } - - // now process states - match c { - ':' => { - colon_count += 1; - match st { - Start => { - pos = i; - st = PassHostPort; - } - PassHostPort => { - // multiple colons means ipv6 address. - if in == Unreserved { - return Err( - ~"Illegal characters in IPv6 address."); - } - st = Ip6Host; - } - InHost => { - pos = i; - // can't be sure whether this is an ipv6 address or a port - if in == Unreserved { - return Err(~"Illegal characters in authority."); - } - st = Ip6Port; - } - Ip6Port => { - if in == Unreserved { - return Err(~"Illegal characters in authority."); - } - st = Ip6Host; - } - Ip6Host => { - if colon_count > 7 { - host = rawurl.slice(begin, i).to_owned(); - pos = i; - st = InPort; - } - } - _ => { - return Err(~"Invalid ':' in authority."); - } - } - in = Digit; // reset input class - } - - '@' => { - in = Digit; // reset input class - colon_count = 0; // reset count - match st { - Start => { - let user = rawurl.slice(begin, i).to_owned(); - userinfo = Some(UserInfo::new(user, None)); - st = InHost; - } - PassHostPort => { - let user = rawurl.slice(begin, pos).to_owned(); - let pass = rawurl.slice(pos+1, i).to_owned(); - userinfo = Some(UserInfo::new(user, Some(pass))); - st = InHost; - } - _ => { - return Err(~"Invalid '@' in authority."); - } - } - begin = i+1; - } - - '?' | '#' | '/' => { - end = i; - break; - } - _ => () - } - end = i; - } - - let end = end; // make end immutable so it can be captured - - let host_is_end_plus_one: &fn() -> bool = || { - let xs = ['?', '#', '/']; - end+1 == len - && !xs.iter().any_(|x| *x == (rawurl[end] as char)) - }; - - // finish up - match st { - Start => { - if host_is_end_plus_one() { - host = rawurl.slice(begin, end+1).to_owned(); - } else { - host = rawurl.slice(begin, end).to_owned(); - } - } - PassHostPort | Ip6Port => { - if in != Digit { - return Err(~"Non-digit characters in port."); - } - host = rawurl.slice(begin, pos).to_owned(); - port = Some(rawurl.slice(pos+1, end).to_owned()); - } - Ip6Host | InHost => { - host = rawurl.slice(begin, end).to_owned(); - } - InPort => { - if in != Digit { - return Err(~"Non-digit characters in port."); - } - port = Some(rawurl.slice(pos+1, end).to_owned()); - } - } - - let rest = if host_is_end_plus_one() { ~"" } - else { rawurl.slice(end, len).to_owned() }; - return Ok((userinfo, host, port, rest)); -} - - -// returns the path and unparsed part of url, or an error -fn get_path(rawurl: &str, authority: bool) -> - Result<(~str, ~str), ~str> { - let len = rawurl.len(); - let mut end = len; - for rawurl.iter().enumerate().advance |(i,c)| { - match c { - 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.' - | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '=' - | '_' | '-' => { - loop; - } - '?' | '#' => { - end = i; - break; - } - _ => return Err(~"Invalid character in path.") - } - } - - if authority { - if end != 0 && !rawurl.starts_with("/") { - return Err(~"Non-empty path must begin with\ - '/' in presence of authority."); - } - } - - return Ok((decode_component(rawurl.slice(0, end)), - rawurl.slice(end, len).to_owned())); -} - -// returns the parsed query and the fragment, if present -fn get_query_fragment(rawurl: &str) -> - Result<(Query, Option<~str>), ~str> { - if !rawurl.starts_with("?") { - if rawurl.starts_with("#") { - let f = decode_component(rawurl.slice( - 1, - rawurl.len())); - return Ok((~[], Some(f))); - } else { - return Ok((~[], None)); - } - } - let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#'); - let f = if r.len() != 0 { - Some(decode_component(r)) } else { None }; - return Ok((query_from_str(q), f)); -} - -/** - * Parse a `str` to a `url` - * - * # Arguments - * - * `rawurl` - a string representing a full url, including scheme. - * - * # Returns - * - * a `url` that contains the parsed representation of the url. - * - */ - -pub fn from_str(rawurl: &str) -> Result { - // scheme - let (scheme, rest) = match get_scheme(rawurl) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - // authority - let (userinfo, host, port, rest) = match get_authority(rest) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - // path - let has_authority = if host == ~"" { false } else { true }; - let (path, rest) = match get_path(rest, has_authority) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - // query and fragment - let (query, fragment) = match get_query_fragment(rest) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - Ok(Url::new(scheme, userinfo, host, port, path, query, fragment)) -} - -impl FromStr for Url { - fn from_str(s: &str) -> Option { - match from_str(s) { - Ok(url) => Some(url), - Err(_) => None - } - } -} - -/** - * Format a `url` as a string - * - * # Arguments - * - * `url` - a url. - * - * # Returns - * - * a `str` that contains the formatted url. Note that this will usually - * be an inverse of `from_str` but might strip out unneeded separators. - * for example, "http://somehost.com?", when parsed and formatted, will - * result in just "http://somehost.com". - * - */ -pub fn to_str(url: &Url) -> ~str { - let user = match url.user { - Some(ref user) => userinfo_to_str(user), - None => ~"", - }; - - let authority = if url.host.is_empty() { - ~"" - } else { - fmt!("//%s%s", user, url.host) - }; - - let query = if url.query.is_empty() { - ~"" - } else { - fmt!("?%s", query_to_str(&url.query)) - }; - - let fragment = match url.fragment { - Some(ref fragment) => fmt!("#%s", encode_component(*fragment)), - None => ~"", - }; - - fmt!("%s:%s%s%s%s", url.scheme, authority, url.path, query, fragment) -} - -impl ToStr for Url { - pub fn to_str(&self) -> ~str { - to_str(self) - } -} - -impl IterBytes for Url { - fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool { - self.to_str().iter_bytes(lsb0, f) - } -} - -// Put a few tests outside of the 'test' module so they can test the internal -// functions and those functions don't need 'pub' - -#[test] -fn test_split_char_first() { - let (u,v) = split_char_first("hello, sweet world", ','); - assert_eq!(u, ~"hello"); - assert_eq!(v, ~" sweet world"); - - let (u,v) = split_char_first("hello sweet world", ','); - assert_eq!(u, ~"hello sweet world"); - assert_eq!(v, ~""); -} - -#[test] -fn test_get_authority() { - let (u, h, p, r) = get_authority( - "//user:pass@rust-lang.org/something").unwrap(); - assert_eq!(u, Some(UserInfo::new(~"user", Some(~"pass")))); - assert_eq!(h, ~"rust-lang.org"); - assert!(p.is_none()); - assert_eq!(r, ~"/something"); - - let (u, h, p, r) = get_authority( - "//rust-lang.org:8000?something").unwrap(); - assert!(u.is_none()); - assert_eq!(h, ~"rust-lang.org"); - assert_eq!(p, Some(~"8000")); - assert_eq!(r, ~"?something"); - - let (u, h, p, r) = get_authority( - "//rust-lang.org#blah").unwrap(); - assert!(u.is_none()); - assert_eq!(h, ~"rust-lang.org"); - assert!(p.is_none()); - assert_eq!(r, ~"#blah"); - - // ipv6 tests - let (_, h, _, _) = get_authority( - "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap(); - assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); - - let (_, h, p, _) = get_authority( - "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap(); - assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); - assert_eq!(p, Some(~"8000")); - - let (u, h, p, _) = get_authority( - "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah" - ).unwrap(); - assert_eq!(u, Some(UserInfo::new(~"us", Some(~"p")))); - assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334"); - assert_eq!(p, Some(~"8000")); - - // invalid authorities; - assert!(get_authority("//user:pass@rust-lang:something").is_err()); - assert!(get_authority("//user@rust-lang:something:/path").is_err()); - assert!(get_authority( - "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err()); - assert!(get_authority( - "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err()); - - // these parse as empty, because they don't start with '//' - let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap(); - assert_eq!(h, ~""); - let (_, h, _, _) = get_authority("rust-lang.org").unwrap(); - assert_eq!(h, ~""); -} - -#[test] -fn test_get_path() { - let (p, r) = get_path("/something+%20orother", true).unwrap(); - assert_eq!(p, ~"/something+ orother"); - assert_eq!(r, ~""); - let (p, r) = get_path("test@email.com#fragment", false).unwrap(); - assert_eq!(p, ~"test@email.com"); - assert_eq!(r, ~"#fragment"); - let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap(); - assert_eq!(p, ~"/gen/:addr="); - assert_eq!(r, ~"?q=v"); - - //failure cases - assert!(get_path("something?q", true).is_err()); -} - -#[cfg(test)] -mod tests { - - use net_url::*; - - use std::hashmap::HashMap; - - #[test] - fn test_url_parse() { - let url = ~"http://user:pass@rust-lang.org/doc?s=v#something"; - - let up = from_str(url); - let u = up.unwrap(); - assert!(u.scheme == ~"http"); - let userinfo = u.user.get_ref(); - assert!(userinfo.user == ~"user"); - assert!(userinfo.pass.get_ref() == &~"pass"); - assert!(u.host == ~"rust-lang.org"); - assert!(u.path == ~"/doc"); - assert!(u.query == ~[(~"s", ~"v")]); - assert!(u.fragment.get_ref() == &~"something"); - } - - #[test] - fn test_url_parse_host_slash() { - let urlstr = ~"http://0.42.42.42/"; - let url = from_str(urlstr).unwrap(); - assert!(url.host == ~"0.42.42.42"); - assert!(url.path == ~"/"); - } - - #[test] - fn test_url_with_underscores() { - let urlstr = ~"http://dotcom.com/file_name.html"; - let url = from_str(urlstr).unwrap(); - assert!(url.path == ~"/file_name.html"); - } - - #[test] - fn test_url_with_dashes() { - let urlstr = ~"http://dotcom.com/file-name.html"; - let url = from_str(urlstr).unwrap(); - assert!(url.path == ~"/file-name.html"); - } - - #[test] - fn test_no_scheme() { - assert!(get_scheme("noschemehere.html").is_err()); - } - - #[test] - fn test_invalid_scheme_errors() { - assert!(from_str("99://something").is_err()); - assert!(from_str("://something").is_err()); - } - - #[test] - fn test_full_url_parse_and_format() { - let url = ~"http://user:pass@rust-lang.org/doc?s=v#something"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_userless_url_parse_and_format() { - let url = ~"http://rust-lang.org/doc?s=v#something"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_queryless_url_parse_and_format() { - let url = ~"http://user:pass@rust-lang.org/doc#something"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_empty_query_url_parse_and_format() { - let url = ~"http://user:pass@rust-lang.org/doc?#something"; - let should_be = ~"http://user:pass@rust-lang.org/doc#something"; - assert_eq!(from_str(url).unwrap().to_str(), should_be); - } - - #[test] - fn test_fragmentless_url_parse_and_format() { - let url = ~"http://user:pass@rust-lang.org/doc?q=v"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_minimal_url_parse_and_format() { - let url = ~"http://rust-lang.org/doc"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_scheme_host_only_url_parse_and_format() { - let url = ~"http://rust-lang.org"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_pathless_url_parse_and_format() { - let url = ~"http://user:pass@rust-lang.org?q=v#something"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_scheme_host_fragment_only_url_parse_and_format() { - let url = ~"http://rust-lang.org#something"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_url_component_encoding() { - let url = ~"http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B"; - let u = from_str(url).unwrap(); - assert!(u.path == ~"/doc uments"); - assert!(u.query == ~[(~"ba%d ", ~"#&+")]); - } - - #[test] - fn test_url_without_authority() { - let url = ~"mailto:test@email.com"; - assert_eq!(from_str(url).unwrap().to_str(), url); - } - - #[test] - fn test_encode() { - assert_eq!(encode(""), ~""); - assert_eq!(encode("http://example.com"), ~"http://example.com"); - assert_eq!(encode("foo bar% baz"), ~"foo%20bar%25%20baz"); - assert_eq!(encode(" "), ~"%20"); - assert_eq!(encode("!"), ~"!"); - assert_eq!(encode("\""), ~"\""); - assert_eq!(encode("#"), ~"#"); - assert_eq!(encode("$"), ~"$"); - assert_eq!(encode("%"), ~"%25"); - assert_eq!(encode("&"), ~"&"); - assert_eq!(encode("'"), ~"%27"); - assert_eq!(encode("("), ~"("); - assert_eq!(encode(")"), ~")"); - assert_eq!(encode("*"), ~"*"); - assert_eq!(encode("+"), ~"+"); - assert_eq!(encode(","), ~","); - assert_eq!(encode("/"), ~"/"); - assert_eq!(encode(":"), ~":"); - assert_eq!(encode(";"), ~";"); - assert_eq!(encode("="), ~"="); - assert_eq!(encode("?"), ~"?"); - assert_eq!(encode("@"), ~"@"); - assert_eq!(encode("["), ~"["); - assert_eq!(encode("]"), ~"]"); - } - - #[test] - fn test_encode_component() { - assert_eq!(encode_component(""), ~""); - assert!(encode_component("http://example.com") == - ~"http%3A%2F%2Fexample.com"); - assert!(encode_component("foo bar% baz") == - ~"foo%20bar%25%20baz"); - assert_eq!(encode_component(" "), ~"%20"); - assert_eq!(encode_component("!"), ~"%21"); - assert_eq!(encode_component("#"), ~"%23"); - assert_eq!(encode_component("$"), ~"%24"); - assert_eq!(encode_component("%"), ~"%25"); - assert_eq!(encode_component("&"), ~"%26"); - assert_eq!(encode_component("'"), ~"%27"); - assert_eq!(encode_component("("), ~"%28"); - assert_eq!(encode_component(")"), ~"%29"); - assert_eq!(encode_component("*"), ~"%2A"); - assert_eq!(encode_component("+"), ~"%2B"); - assert_eq!(encode_component(","), ~"%2C"); - assert_eq!(encode_component("/"), ~"%2F"); - assert_eq!(encode_component(":"), ~"%3A"); - assert_eq!(encode_component(";"), ~"%3B"); - assert_eq!(encode_component("="), ~"%3D"); - assert_eq!(encode_component("?"), ~"%3F"); - assert_eq!(encode_component("@"), ~"%40"); - assert_eq!(encode_component("["), ~"%5B"); - assert_eq!(encode_component("]"), ~"%5D"); - } - - #[test] - fn test_decode() { - assert_eq!(decode(""), ~""); - assert_eq!(decode("abc/def 123"), ~"abc/def 123"); - assert_eq!(decode("abc%2Fdef%20123"), ~"abc%2Fdef 123"); - assert_eq!(decode("%20"), ~" "); - assert_eq!(decode("%21"), ~"%21"); - assert_eq!(decode("%22"), ~"%22"); - assert_eq!(decode("%23"), ~"%23"); - assert_eq!(decode("%24"), ~"%24"); - assert_eq!(decode("%25"), ~"%"); - assert_eq!(decode("%26"), ~"%26"); - assert_eq!(decode("%27"), ~"'"); - assert_eq!(decode("%28"), ~"%28"); - assert_eq!(decode("%29"), ~"%29"); - assert_eq!(decode("%2A"), ~"%2A"); - assert_eq!(decode("%2B"), ~"%2B"); - assert_eq!(decode("%2C"), ~"%2C"); - assert_eq!(decode("%2F"), ~"%2F"); - assert_eq!(decode("%3A"), ~"%3A"); - assert_eq!(decode("%3B"), ~"%3B"); - assert_eq!(decode("%3D"), ~"%3D"); - assert_eq!(decode("%3F"), ~"%3F"); - assert_eq!(decode("%40"), ~"%40"); - assert_eq!(decode("%5B"), ~"%5B"); - assert_eq!(decode("%5D"), ~"%5D"); - } - - #[test] - fn test_decode_component() { - assert_eq!(decode_component(""), ~""); - assert_eq!(decode_component("abc/def 123"), ~"abc/def 123"); - assert_eq!(decode_component("abc%2Fdef%20123"), ~"abc/def 123"); - assert_eq!(decode_component("%20"), ~" "); - assert_eq!(decode_component("%21"), ~"!"); - assert_eq!(decode_component("%22"), ~"\""); - assert_eq!(decode_component("%23"), ~"#"); - assert_eq!(decode_component("%24"), ~"$"); - assert_eq!(decode_component("%25"), ~"%"); - assert_eq!(decode_component("%26"), ~"&"); - assert_eq!(decode_component("%27"), ~"'"); - assert_eq!(decode_component("%28"), ~"("); - assert_eq!(decode_component("%29"), ~")"); - assert_eq!(decode_component("%2A"), ~"*"); - assert_eq!(decode_component("%2B"), ~"+"); - assert_eq!(decode_component("%2C"), ~","); - assert_eq!(decode_component("%2F"), ~"/"); - assert_eq!(decode_component("%3A"), ~":"); - assert_eq!(decode_component("%3B"), ~";"); - assert_eq!(decode_component("%3D"), ~"="); - assert_eq!(decode_component("%3F"), ~"?"); - assert_eq!(decode_component("%40"), ~"@"); - assert_eq!(decode_component("%5B"), ~"["); - assert_eq!(decode_component("%5D"), ~"]"); - } - - #[test] - fn test_encode_form_urlencoded() { - let mut m = HashMap::new(); - assert_eq!(encode_form_urlencoded(&m), ~""); - - m.insert(~"", ~[]); - m.insert(~"foo", ~[]); - assert_eq!(encode_form_urlencoded(&m), ~""); - - let mut m = HashMap::new(); - m.insert(~"foo", ~[~"bar", ~"123"]); - assert_eq!(encode_form_urlencoded(&m), ~"foo=bar&foo=123"); - - let mut m = HashMap::new(); - m.insert(~"foo bar", ~[~"abc", ~"12 = 34"]); - assert!(encode_form_urlencoded(&m) == - ~"foo+bar=abc&foo+bar=12+%3D+34"); - } - - #[test] - fn test_decode_form_urlencoded() { - // FIXME #4449: Commented out because this causes an ICE, but only - // on FreeBSD - /* - assert_eq!(decode_form_urlencoded([]).len(), 0); - - let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes(); - let form = decode_form_urlencoded(s); - assert_eq!(form.len(), 2); - assert_eq!(form.get_ref(&~"a"), &~[~"1"]); - assert_eq!(form.get_ref(&~"foo bar"), &~[~"abc", ~"12 = 34"]); - */ - } -} -- cgit 1.4.1-3-g733a5