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.
// 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;
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Ipv4Rep, ~str> {
+ 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<IpAddr,ParseAddrErr> {
+ 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<IpAddr,ParseAddrErr> {
+ 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<result::Result<~[IpAddr],IpGetAddrErr>>
+}
+
+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::<addrinfo>()) {
+ 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::<addrinfo>() 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::<addrinfo>()) {
+ 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));
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<TcpSocket, TcpConnectErrData> {
+ unsafe {
+ let (result_po, result_ch) = stream::<ConnAttempt>();
+ 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::<Result<~[u8], TcpErrData>>();
+ 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<result::Result<(), TcpErrData>>
+{
+ 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<Result<~[u8], TcpErrData>>` 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<result::Result<~[u8],
+ TcpErrData>>,
+ 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<result::Result<~[u8],TcpErrData>> {
+ 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::<option::Option<TcpErrData>>();
+ * 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<TcpSocket, TcpErrData> {
+ 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::<Option<TcpErrData>>();
+ 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<Option<tcp_err_data>>`.
+ * 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<Option<TcpErrData>>),
+ new_connect_cb: ~fn(TcpNewConnection,
+ SharedChan<Option<TcpErrData>>))
+ -> 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<Option<TcpErrData>>),
+ 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::<Option<TcpErrData>>();
+ 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<result::Result<~[u8], TcpErrData>> {
+ 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<result::Result<(), TcpErrData>> {
+ 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::<Option<TcpErrData>>();
+ 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::<Option<uv::ll::uv_err_data>>();
+ 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::<TcpWriteResult>();
+ 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<Option<TcpErrData>>,
+ 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<TcpReadResult>),
+ 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<TcpWriteResult>,
+}
+
+struct ConnectReqData {
+ result_ch: SharedChan<ConnAttempt>,
+ 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<result::Result<~[u8], TcpErrData>>,
+ reader_ch: SharedChan<result::Result<~[u8], TcpErrData>>,
+ 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:io::Writer>(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:io::Reader>(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");
+ }
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<UserInfo>,
+ 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<UserInfo>,
+ 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<UserInfo>, ~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<Url, ~str> {
+ // 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<Url> {
+ 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"]);
+ */
+ }
+}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Ipv4Rep, ~str> {
- 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<IpAddr,ParseAddrErr> {
- 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<IpAddr,ParseAddrErr> {
- 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<result::Result<~[IpAddr],IpGetAddrErr>>
-}
-
-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::<addrinfo>()) {
- 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::<addrinfo>() 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::<addrinfo>()) {
- 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));
- }
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<TcpSocket, TcpConnectErrData> {
- unsafe {
- let (result_po, result_ch) = stream::<ConnAttempt>();
- 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::<Result<~[u8], TcpErrData>>();
- 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<result::Result<(), TcpErrData>>
-{
- 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<Result<~[u8], TcpErrData>>` 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<result::Result<~[u8],
- TcpErrData>>,
- 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<result::Result<~[u8],TcpErrData>> {
- 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::<option::Option<TcpErrData>>();
- * 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<TcpSocket, TcpErrData> {
- 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::<Option<TcpErrData>>();
- 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<Option<tcp_err_data>>`.
- * 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<Option<TcpErrData>>),
- new_connect_cb: ~fn(TcpNewConnection,
- SharedChan<Option<TcpErrData>>))
- -> 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<Option<TcpErrData>>),
- 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::<Option<TcpErrData>>();
- 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<result::Result<~[u8], TcpErrData>> {
- 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<result::Result<(), TcpErrData>> {
- 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::<Option<TcpErrData>>();
- 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::<Option<uv::ll::uv_err_data>>();
- 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::<TcpWriteResult>();
- 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<Option<TcpErrData>>,
- 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<TcpReadResult>),
- 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<TcpWriteResult>,
-}
-
-struct ConnectReqData {
- result_ch: SharedChan<ConnAttempt>,
- 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<result::Result<~[u8], TcpErrData>>,
- reader_ch: SharedChan<result::Result<~[u8], TcpErrData>>,
- 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:io::Writer>(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:io::Reader>(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");
- }
- }
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<UserInfo>,
- 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<UserInfo>,
- 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<UserInfo>, ~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<Url, ~str> {
- // 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<Url> {
- 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"]);
- */
- }
-}