diff --git a/examples/spdycat.cc b/examples/spdycat.cc new file mode 100644 index 00000000..04ef47f9 --- /dev/null +++ b/examples/spdycat.cc @@ -0,0 +1,236 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "spdylay_ssl.h" +#include "uri.h" + +namespace spdylay { + +std::string target_path; +int32_t target_stream_id; +bool debug = false; +extern bool ssl_debug; + +void on_data_chunk_recv_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, void *user_data) +{ + if(!debug && stream_id == target_stream_id) { + std::cout.write(reinterpret_cast(data), len); + } +} + +void check_stream_id(spdylay_frame_type type, spdylay_frame *frame) +{ + if(type == SPDYLAY_SYN_STREAM) { + for(int i = 0; frame->syn_stream.nv[i]; i += 2) { + if(strcmp("url", frame->syn_stream.nv[i]) == 0) { + if(target_path == frame->syn_stream.nv[i+1]) { + target_stream_id = frame->syn_stream.stream_id; + } + break; + } + } + } +} + +void on_ctrl_send_callback2 +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + check_stream_id(type, frame); +} + +void on_ctrl_send_callback3 +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + check_stream_id(type, frame); + on_ctrl_send_callback(session, type, frame, user_data); +} + +void on_stream_close_callback +(spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, + void *user_data) +{ + if(target_stream_id == stream_id) { + spdylay_submit_goaway(session); + } +} + +int communicate(const std::string& host, uint16_t port, + const std::string& path, + const spdylay_session_callbacks *callbacks) +{ + target_path = path; + ssl_debug = debug; + int fd = connect_to(host, port); + if(fd == -1) { + std::cerr << "Could not connect to the host" << std::endl; + return -1; + } + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if(!ssl_ctx) { + std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; + return -1; + } + setup_ssl_ctx(ssl_ctx); + SSL *ssl = SSL_new(ssl_ctx); + if(!ssl) { + std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; + return -1; + } + if(ssl_handshake(ssl, fd) == -1) { + return -1; + } + make_non_block(fd); + Spdylay sc(fd, ssl, callbacks); + int epollfd = epoll_create(1); + if(epollfd == -1) { + perror("epoll_create"); + return -1; + } + sc.submit_request(path, 3); + + ctl_epollev(epollfd, EPOLL_CTL_ADD, &sc); + static const size_t MAX_EVENTS = 1; + epoll_event events[MAX_EVENTS]; + bool ok = true; + while(sc.want_read() || sc.want_write()) { + int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); + if(nfds == -1) { + perror("epoll_wait"); + return -1; + } + for(int n = 0; n < nfds; ++n) { + if(((events[n].events & EPOLLIN) && sc.recv() != 0) || + ((events[n].events & EPOLLOUT) && sc.send() != 0)) { + ok = false; + std::cout << "Fatal" << std::endl; + break; + } + if((events[n].events & EPOLLHUP) || (events[n].events & EPOLLERR)) { + std::cout << "HUP" << std::endl; + ok = false; + break; + } + } + if(!ok) { + break; + } + ctl_epollev(epollfd, EPOLL_CTL_MOD, &sc); + } + + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + shutdown(fd, SHUT_WR); + close(fd); + return ok ? 0 : -1; +} + +int run(const std::string& host, uint16_t port, const std::string& path) +{ + spdylay_session_callbacks callbacks; + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.send_callback = send_callback; + callbacks.recv_callback = recv_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + if(debug) { + callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; + callbacks.on_data_recv_callback = on_data_recv_callback; + callbacks.on_ctrl_send_callback = on_ctrl_send_callback3; + } else { + callbacks.on_ctrl_send_callback = on_ctrl_send_callback2; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + } + return communicate(host, port, path, &callbacks); +} + +} // namespace spdylay + +void print_usage(const char *prog) +{ + std::cerr << "Usage: " << prog << " [-d] URI" << std::endl; + std::cerr << " Get the resource identified by URI via spdy/2 protocol and\n" + << " output the resource.\n" + << "\n" + << " -d: Output debug information instead of output the resource." + << std::endl; +} + +int main(int argc, char **argv) +{ + std::string uri; + if(argc == 2) { + spdylay::debug = false; + uri = argv[1]; + } else if(argc == 3 && strcmp(argv[1], "-d") == 0) { + spdylay::debug = true; + uri = argv[2]; + } else { + print_usage(basename(argv[0])); + exit(EXIT_FAILURE); + } + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + SSL_load_error_strings(); + SSL_library_init(); + spdylay::uri::UriStruct us; + if(!spdylay::uri::parse(us, uri)) { + std::cerr << "Could not parse URI" << std::endl; + exit(EXIT_FAILURE); + } + spdylay::run(us.host, us.port, us.dir+us.file+us.query); + return 0; +} diff --git a/examples/spdycl.cc b/examples/spdylay_ssl.cc similarity index 51% rename from examples/spdycl.cc rename to examples/spdylay_ssl.cc index 89fcb725..3b7ea78a 100644 --- a/examples/spdycl.cc +++ b/examples/spdylay_ssl.cc @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -44,20 +43,99 @@ #include #include -#include -#include -#include +#include "spdylay_ssl.h" -int connect_to(const char *host, const char *service) +namespace spdylay { + +bool ssl_debug = false; + +Spdylay::Spdylay(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks) + : fd_(fd), ssl_(ssl), want_write_(false) +{ + spdylay_session_client_new(&session_, callbacks, this); +} + +Spdylay::~Spdylay() +{ + spdylay_session_del(session_); +} + +int Spdylay::recv() +{ + return spdylay_session_recv(session_); +} + +int Spdylay::send() +{ + return spdylay_session_send(session_); +} + +ssize_t Spdylay::send_data(const uint8_t *data, size_t len, int flags) +{ + ssize_t r; + r = SSL_write(ssl_, data, len); + return r; +} + +ssize_t Spdylay::recv_data(uint8_t *data, size_t len, int flags) +{ + ssize_t r; + want_write_ = false; + r = SSL_read(ssl_, data, len); + if(r < 0) { + if(SSL_get_error(ssl_, r) == SSL_ERROR_WANT_WRITE) { + want_write_ = true; + } + } + return r; +} + +bool Spdylay::want_read() +{ + return spdylay_session_want_read(session_); +} + +bool Spdylay::want_write() +{ + return spdylay_session_want_write(session_) || want_write_; +} + +int Spdylay::fd() const +{ + return fd_; +} + +int Spdylay::submit_request(const std::string& path, uint8_t pri) +{ + const char *nv[] = { + "method", "GET", + "scheme", "https", + "url", path.c_str(), + "user-agent", "spdylay/0.0.0", + "version", "HTTP/1.1", + NULL + }; + return spdylay_submit_request(session_, pri, nv, NULL); +} + +bool Spdylay::would_block(int r) +{ + int e = SSL_get_error(ssl_, r); + return e == SSL_ERROR_WANT_WRITE || e == SSL_ERROR_WANT_READ; +} + +int connect_to(const std::string& host, uint16_t port) { struct addrinfo hints; int fd = -1; int r; + char service[10]; + snprintf(service, sizeof(service), "%u", port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; struct addrinfo *res; - r = getaddrinfo(host, service, &hints, &res); + r = getaddrinfo(host.c_str(), service, &hints, &res); if(r != 0) { std::cerr << "getaddrinfo: " << gai_strerror(r) << std::endl; return -1; @@ -93,89 +171,11 @@ int make_non_block(int fd) return 0; } -class SpdylayClient { -public: - SpdylayClient(int fd, SSL *ssl, - const spdylay_session_callbacks *callbacks) - : fd_(fd), ssl_(ssl), want_write_(false) - { - spdylay_session_client_new(&session_, callbacks, this); - } - ~SpdylayClient() - { - spdylay_session_del(session_); - SSL_shutdown(ssl_); - shutdown(fd_, SHUT_WR); - close(fd_); - SSL_free(ssl_); - } - int on_read_event() - { - return spdylay_session_recv(session_); - } - int on_write_event() - { - return spdylay_session_send(session_); - } - ssize_t send_data(const uint8_t *data, size_t len, int flags) - { - ssize_t r; - r = SSL_write(ssl_, data, len); - return r; - } - ssize_t recv_data(uint8_t *data, size_t len, int flags) - { - ssize_t r; - want_write_ = false; - r = SSL_read(ssl_, data, len); - if(r < 0) { - if(SSL_get_error(ssl_, r) == SSL_ERROR_WANT_WRITE) { - want_write_ = true; - } - } - return r; - } - bool want_read() - { - return spdylay_session_want_read(session_); - } - bool want_write() - { - return spdylay_session_want_write(session_) || want_write_; - } - int fd() const - { - return fd_; - } - int submit_request(const char *path) - { - const char *nv[] = { - "method", "GET", - "scheme", "https", - "url", path, - "user-agent", "spdylay/0.0.0", - "version", "HTTP/1.1", - NULL - }; - return spdylay_submit_request(session_, 3, nv); - } - bool would_block(int r) - { - int e = SSL_get_error(ssl_, r); - return e == SSL_ERROR_WANT_WRITE || e == SSL_ERROR_WANT_READ; - } -private: - int fd_; - SSL *ssl_; - spdylay_session *session_; - bool want_write_; -}; - ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len, int flags, void *user_data) { - SpdylayClient *sc = (SpdylayClient*)user_data; + Spdylay *sc = (Spdylay*)user_data; ssize_t r = sc->send_data(data, len, flags); if(r < 0) { if(sc->would_block(r)) { @@ -190,7 +190,7 @@ ssize_t send_callback(spdylay_session *session, ssize_t recv_callback(spdylay_session *session, uint8_t *data, size_t len, int flags, void *user_data) { - SpdylayClient *sc = (SpdylayClient*)user_data; + Spdylay *sc = (Spdylay*)user_data; ssize_t r = sc->recv_data(data, len, flags); if(r < 0) { if(sc->would_block(r)) { @@ -204,14 +204,6 @@ ssize_t recv_callback(spdylay_session *session, return r; } -void print_nv(char **nv) -{ - int i; - for(i = 0; nv[i]; i += 2) { - printf(" %s: %s\n", nv[i], nv[i+1]); - } -} - static const char *ctrl_names[] = { "SYN_STREAM", "SYN_REPLY", @@ -223,6 +215,14 @@ static const char *ctrl_names[] = { "HEADERS" }; +void print_nv(char **nv) +{ + int i; + for(i = 0; nv[i]; i += 2) { + printf(" %s: %s\n", nv[i], nv[i+1]); + } +} + void on_ctrl_recv_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data) @@ -235,26 +235,25 @@ void on_ctrl_recv_callback frame->syn_reply.hd.length); print_nv(frame->syn_reply.nv); break; + case SPDYLAY_PING: + printf("(unique_id=%d)\n", frame->ping.unique_id); + break; default: + printf("\n"); break; } fflush(stdout); } -void on_data_chunk_recv_callback -(spdylay_session *session, uint8_t flags, int32_t stream_id, - const uint8_t *data, size_t len, void *user_data) -{} - void on_data_recv_callback (spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, void *user_data) { printf("recv DATA frame (stream_id=%d, flags=%d, length=%d)\n", stream_id, flags, length); - if(flags & SPDYLAY_FLAG_FIN) { - spdylay_submit_goaway(session); - } + // if(flags & SPDYLAY_FLAG_FIN) { + // spdylay_submit_ping(session); + // } fflush(stdout); } @@ -270,25 +269,30 @@ void on_ctrl_send_callback frame->syn_stream.hd.length); print_nv(frame->syn_stream.nv); break; + case SPDYLAY_PING: + printf("(unique_id=%d)\n", frame->ping.unique_id); + break; case SPDYLAY_GOAWAY: printf("(last_good_stream_id=%d)\n", frame->goaway.last_good_stream_id); + break; default: + printf("\n"); break; } fflush(stdout); } -void ctl_epollev(int epollfd, int op, SpdylayClient& sc) +void ctl_epollev(int epollfd, int op, Spdylay *sc) { epoll_event ev; memset(&ev, 0, sizeof(ev)); - if(sc.want_read()) { + if(sc->want_read()) { ev.events |= EPOLLIN; } - if(sc.want_write()) { + if(sc->want_write()) { ev.events |= EPOLLOUT; } - if(epoll_ctl(epollfd, op, sc.fd(), &ev) == -1) { + if(epoll_ctl(epollfd, op, sc->fd(), &ev) == -1) { perror("epoll_ctl"); exit(EXIT_FAILURE); } @@ -301,143 +305,44 @@ int select_next_proto_cb(SSL* ssl, { *out = (unsigned char*)in+1; *outlen = in[0]; - std::cout << "NPN select next proto: server offers:" << std::endl; + if(ssl_debug) { + std::cout << "NPN select next proto: server offers:" << std::endl; + } for(unsigned int i = 0; i < inlen; i += in[i]+1) { - std::cout << "* " << std::string(&in[i+1], &in[i+1]+in[i]) << std::endl; + if(ssl_debug) { + std::cout << " * "; + std::cout.write(reinterpret_cast(&in[i+1]), in[i]); + std::cout << std::endl; + } if(in[i] == 6 && memcmp(&in[i+1], "spdy/2", in[i]) == 0) { *out = (unsigned char*)in+i+1; *outlen = in[i]; } } return SSL_TLSEXT_ERR_OK; - - - int status = SSL_select_next_proto(out, outlen, in, inlen, - (const unsigned char*)"spdy/2", 6); - switch(status) { - case OPENSSL_NPN_UNSUPPORTED: - std::cerr << "npn unsupported" << std::endl; - break; - case OPENSSL_NPN_NEGOTIATED: - std::cout << "npn negotiated" << std::endl; - break; - case OPENSSL_NPN_NO_OVERLAP: - std::cout << "npn no overlap" << std::endl; - break; - default: - std::cout << "not reached?" << std::endl; - } - return SSL_TLSEXT_ERR_OK; } -int communicate(const char *host, const char *service, const char *path, - const spdylay_session_callbacks *callbacks) +void setup_ssl_ctx(SSL_CTX *ssl_ctx) { - int r; - int fd = connect_to(host, service); - if(fd == -1) { - std::cerr << "Could not connect to the host" << std::endl; - return -1; - } - SSL_CTX *ssl_ctx; - ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if(!ssl_ctx) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } /* Disable SSLv2 and enable all workarounds for buggy servers */ SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, 0); +} - SSL *ssl = SSL_new(ssl_ctx); - if(!ssl) { - std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; - return -1; - } - +int ssl_handshake(SSL *ssl, int fd) +{ if(SSL_set_fd(ssl, fd) == 0) { std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; return -1; } - r = SSL_connect(ssl); + int r = SSL_connect(ssl); if(r <= 0) { std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; return -1; } - - make_non_block(fd); - SpdylayClient sc(fd, ssl, callbacks); - int epollfd = epoll_create(1); - if(epollfd == -1) { - perror("epoll_create"); - return -1; - } - - sc.submit_request(path); - - ctl_epollev(epollfd, EPOLL_CTL_ADD, sc); - static const size_t MAX_EVENTS = 1; - epoll_event events[MAX_EVENTS]; - bool ok = true; - while(sc.want_read() || sc.want_write()) { - int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); - if(nfds == -1) { - perror("epoll_wait"); - return -1; - } - for(int n = 0; n < nfds; ++n) { - if(((events[n].events & EPOLLIN) && sc.on_read_event() != 0) || - ((events[n].events & EPOLLOUT) && sc.on_write_event() != 0)) { - ok = false; - std::cout << "Fatal" << std::endl; - break; - } - if((events[n].events & EPOLLHUP) || (events[n].events & EPOLLERR)) { - std::cout << "HUP" << std::endl; - ok = false; - break; - } - } - if(!ok) { - break; - } - ctl_epollev(epollfd, EPOLL_CTL_MOD, sc); - } - - - return ok ? 0 : -1; -} - -int run(const char *host, const char *service, const char *path) -{ - spdylay_session_callbacks callbacks; - memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); - callbacks.send_callback = send_callback; - callbacks.recv_callback = recv_callback; - callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; - callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; - callbacks.on_data_recv_callback = on_data_recv_callback; - callbacks.on_ctrl_send_callback = on_ctrl_send_callback; - return communicate(host, service, path, &callbacks); -} - -int main(int argc, char **argv) -{ - if(argc < 2) { - std::cerr << "Usage: " << argv[0] << " HOST PORT PATH" << std::endl; - exit(EXIT_FAILURE); - } - struct sigaction act; - memset(&act, 0, sizeof(struct sigaction)); - act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, 0); - const char *host = argv[1]; - const char *service = argv[2]; - const char *path = argv[3]; - SSL_load_error_strings(); - SSL_library_init(); - run(host, service, path); return 0; } + +} // namespace spdylay diff --git a/examples/spdylay_ssl.h b/examples/spdylay_ssl.h new file mode 100644 index 00000000..fb7d7e1a --- /dev/null +++ b/examples/spdylay_ssl.h @@ -0,0 +1,98 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SPDYLAY_SSL_H +#define SPDYLAY_SSL_H + +#include +#include + +#include +#include +#include + +namespace spdylay { + +extern bool ssl_debug; + +class Spdylay { +public: + Spdylay(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks); + ~Spdylay(); + int recv(); + int send(); + ssize_t send_data(const uint8_t *data, size_t len, int flags); + ssize_t recv_data(uint8_t *data, size_t len, int flags); + bool want_read(); + bool want_write(); + int fd() const; + int submit_request(const std::string& path, uint8_t pri); + bool would_block(int r); +private: + int fd_; + SSL *ssl_; + spdylay_session *session_; + bool want_write_; + bool debug_; +}; + +int connect_to(const std::string& host, uint16_t port); + +int make_non_block(int fd); + +ssize_t send_callback(spdylay_session *session, + const uint8_t *data, size_t len, int flags, + void *user_data); + +ssize_t recv_callback(spdylay_session *session, + uint8_t *data, size_t len, int flags, void *user_data); + +void print_nv(char **nv); + +void on_ctrl_recv_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data); + +void on_data_recv_callback +(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, + void *user_data); + +void on_ctrl_send_callback +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data); + +void ctl_epollev(int epollfd, int op, Spdylay *sc); + +int select_next_proto_cb(SSL* ssl, + unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg); + +void setup_ssl_ctx(SSL_CTX *ssl_ctx); + +int ssl_handshake(SSL *ssl, int fd); + +} // namespace spdylay + +#endif // SPDYLAY_SSL_H diff --git a/examples/uri.cc b/examples/uri.cc new file mode 100644 index 00000000..f9da82cb --- /dev/null +++ b/examples/uri.cc @@ -0,0 +1,330 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "uri.h" + +#include +#include +#include + +#include "util.h" + +namespace spdylay { + +namespace uri { + +UriStruct::UriStruct() + : port(0), hasPassword(false), ipv6LiteralAddress(false) +{} + +UriStruct::UriStruct(const UriStruct& c) + : protocol(c.protocol), + host(c.host), + port(c.port), + dir(c.dir), + file(c.file), + query(c.query), + username(c.username), + password(c.password), + hasPassword(c.hasPassword), + ipv6LiteralAddress(c.ipv6LiteralAddress) +{} + +UriStruct::~UriStruct() {} + +UriStruct& UriStruct::operator=(const UriStruct& c) +{ + if(this != &c) { + protocol = c.protocol; + host = c.host; + port = c.port; + dir = c.dir; + file = c.file; + query = c.query; + username = c.username; + password = c.password; + hasPassword = c.hasPassword; + ipv6LiteralAddress = c.ipv6LiteralAddress; + } + return *this; +} + +void UriStruct::swap(UriStruct& other) +{ + using std::swap; + if(this != &other) { + swap(protocol, other.protocol); + swap(host, other.host); + swap(port, other.port); + swap(dir, other.dir); + swap(file, other.file); + swap(query, other.query); + swap(username, other.username); + swap(password, other.password); + swap(hasPassword, other.hasPassword); + swap(ipv6LiteralAddress, other.ipv6LiteralAddress); + } +} + +void swap(UriStruct& lhs, UriStruct& rhs) +{ + lhs.swap(rhs); +} + +bool parse(UriStruct& result, const std::string& uri) +{ + // http://user:password@aria2.sourceforge.net:80/dir/file?query#fragment + // | || || | | | | + // | || hostLast| | | | | + // | || portFirst| | | | + // authorityFirst || authorityLast | | | + // || | | | | + // userInfoLast | | | | + // | | | | | + // hostPortFirst | | | | + // | | | | + // dirFirst dirLast| | + // | | + // queryFirst fragmentFirst + + // find fragment part + std::string::const_iterator fragmentFirst = uri.begin(); + for(; fragmentFirst != uri.end(); ++fragmentFirst) { + if(*fragmentFirst == '#') break; + } + // find query part + std::string::const_iterator queryFirst = uri.begin(); + for(; queryFirst != fragmentFirst; ++queryFirst) { + if(*queryFirst == '?') break; + } + result.query.assign(queryFirst, fragmentFirst); + // find protocol + std::string::size_type protocolOffset = uri.find("://"); + if(protocolOffset == std::string::npos) return false; + result.protocol.assign(uri.begin(), uri.begin()+protocolOffset); + uint16_t defPort; + if(result.protocol == "http") { + defPort = 80; + } else if(result.protocol == "https") { + defPort = 443; + } else { + return false; + } + // find authority + std::string::const_iterator authorityFirst = uri.begin()+protocolOffset+3; + std::string::const_iterator authorityLast = authorityFirst; + for(; authorityLast != queryFirst; ++authorityLast) { + if(*authorityLast == '/') break; + } + if(authorityFirst == authorityLast) { + // No authority found + return false; + } + // find userinfo(username and password) in authority if they exist + result.username = ""; + result.password = ""; + result.hasPassword = false; + std::string::const_iterator userInfoLast = authorityLast; + std::string::const_iterator hostPortFirst = authorityFirst; + for(; userInfoLast != authorityFirst-1; --userInfoLast) { + if(*userInfoLast == '@') { + hostPortFirst = userInfoLast; + ++hostPortFirst; + std::string::const_iterator userLast = authorityFirst; + for(; userLast != userInfoLast; ++userLast) { + if(*userLast == ':') { + result.password = + util::percentDecode(userLast+1,userInfoLast); + result.hasPassword = true; + break; + } + } + result.username = + util::percentDecode(authorityFirst, userLast); + break; + } + } + std::string::const_iterator hostLast = hostPortFirst; + std::string::const_iterator portFirst = authorityLast; + result.ipv6LiteralAddress = false; + if(*hostPortFirst == '[') { + // Detected IPv6 literal address in square brackets + for(; hostLast != authorityLast; ++hostLast) { + if(*hostLast == ']') { + ++hostLast; + if(hostLast == authorityLast) { + result.ipv6LiteralAddress = true; + } else { + if(*hostLast == ':') { + portFirst = hostLast; + ++portFirst; + result.ipv6LiteralAddress = true; + } + } + break; + } + } + if(!result.ipv6LiteralAddress) { + return false; + } + } else { + for(; hostLast != authorityLast; ++hostLast) { + if(*hostLast == ':') { + portFirst = hostLast; + ++portFirst; + break; + } + } + } + if(hostPortFirst == hostLast) { + // No host + return false; + } + if(portFirst == authorityLast) { + // If port is not specified, then we set it to default port of + // its protocol.. + result.port = defPort; + } else { + errno = 0; + uint32_t tempPort = strtol(std::string(portFirst, authorityLast).c_str(), + 0, 10); + if(errno != 0) { + return false; + } else if(65535 < tempPort) { + return false; + } + } + if(result.ipv6LiteralAddress) { + result.host.assign(hostPortFirst+1, hostLast-1); + } else { + result.host.assign(hostPortFirst, hostLast); + } + // find directory and file part + std::string::const_iterator dirLast = authorityLast; + for(std::string::const_iterator i = authorityLast; + i != queryFirst; ++i) { + if(*i == '/') { + dirLast = i+1; + } + } + if(dirLast == queryFirst) { + result.file = ""; + } else { + result.file.assign(dirLast, queryFirst); + } + // dirFirst == authorityLast + if(authorityLast == dirLast) { + result.dir = "/"; + } else { + result.dir.assign(authorityLast, dirLast); + } + return true; +} + +std::string construct(const UriStruct& us) +{ + std::string res; + res += us.protocol; + res += "://"; + if(!us.username.empty()) { + res += util::percentEncode(us.username); + if(us.hasPassword) { + res += ":"; + res += util::percentEncode(us.password); + } + res += "@"; + } + if(us.ipv6LiteralAddress) { + res += "["; + res += us.host; + res += "]"; + } else { + res += us.host; + } + uint16_t defPort; + if(us.protocol == "http") { + defPort = 80; + } else if(us.protocol == "https") { + defPort = 443; + } else { + defPort = 0; + } + if(us.port != 0 && defPort != us.port) { + char temp[10]; + snprintf(temp, sizeof(temp), ":%u", us.port); + res += temp; + } + res += us.dir; + if(us.dir.empty() || us.dir[us.dir.size()-1] != '/') { + res += "/"; + } + res += us.file; + res += us.query; + return res; +} + +std::string joinUri(const std::string& baseUri, const std::string& uri) +{ + UriStruct us; + if(parse(us, uri)) { + return uri; + } else { + UriStruct bus; + if(!parse(bus, baseUri)) { + return uri; + } + std::vector parts; + if(uri.empty() || uri[0] != '/') { + util::split(bus.dir.begin(), bus.dir.end(), std::back_inserter(parts), + '/'); + } + std::string::const_iterator qend; + for(qend = uri.begin(); qend != uri.end(); ++qend) { + if(*qend == '#') { + break; + } + } + std::string::const_iterator end; + for(end = uri.begin(); end != qend; ++end) { + if(*end == '?') { + break; + } + } + util::split(uri.begin(), end, std::back_inserter(parts), '/'); + bus.dir.clear(); + bus.file.clear(); + bus.query.clear(); + std::string res = construct(bus); + res += util::joinPath(parts.begin(), parts.end()); + if((uri.begin() == end || *(end-1) == '/') && *(res.end()-1) != '/') { + res += "/"; + } + res.append(end, qend); + return res; + } +} + +} // namespace uri + +} // namespace spdylay diff --git a/examples/uri.h b/examples/uri.h new file mode 100644 index 00000000..fc0baf1e --- /dev/null +++ b/examples/uri.h @@ -0,0 +1,71 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef URI_H +#define URI_H + +#include + +#include + +namespace spdylay { + +namespace uri { + +struct UriStruct { + std::string protocol; + std::string host; + uint16_t port; + std::string dir; + std::string file; + std::string query; + std::string username; + std::string password; + bool hasPassword; + bool ipv6LiteralAddress; + + UriStruct(); + UriStruct(const UriStruct& c); + ~UriStruct(); + + UriStruct& operator=(const UriStruct& c); + void swap(UriStruct& other); +}; + +void swap(UriStruct& lhs, UriStruct& rhs); + +// Splits URI uri into components and stores them into result. On +// success returns true. Otherwise returns false and result is +// undefined. +bool parse(UriStruct& result, const std::string& uri); + +std::string construct(const UriStruct& us); + +std::string joinUri(const std::string& baseUri, const std::string& uri); + +} // namespace uri + +} // namespace spdylay + +#endif // URI_H diff --git a/examples/util.cc b/examples/util.cc new file mode 100644 index 00000000..189a5aa5 --- /dev/null +++ b/examples/util.cc @@ -0,0 +1,102 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#include + +namespace spdylay { + +namespace util { + +const std::string DEFAULT_STRIP_CHARSET("\r\n\t "); + +bool isAlpha(const char c) +{ + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); +} + +bool isDigit(const char c) +{ + return '0' <= c && c <= '9'; +} + +bool isHexDigit(const char c) +{ + return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); +} + +bool inRFC3986UnreservedChars(const char c) +{ + static const char unreserved[] = { '-', '.', '_', '~' }; + return isAlpha(c) || isDigit(c) || + std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4]; +} + +std::string percentEncode(const unsigned char* target, size_t len) +{ + std::string dest; + for(size_t i = 0; i < len; ++i) { + if(inRFC3986UnreservedChars(target[i])) { + dest += target[i]; + } else { + char temp[4]; + snprintf(temp, sizeof(temp), "%%%02X", target[i]); + dest.append(temp); + //dest.append(fmt("%%%02X", target[i])); + } + } + return dest; +} + +std::string percentEncode(const std::string& target) +{ + return percentEncode(reinterpret_cast(target.c_str()), + target.size()); +} + +std::string percentDecode +(std::string::const_iterator first, std::string::const_iterator last) +{ + std::string result; + for(; first != last; ++first) { + if(*first == '%') { + if(first+1 != last && first+2 != last && + isHexDigit(*(first+1)) && isHexDigit(*(first+2))) { + std::string numstr(first+1, first+3); + result += strtol(numstr.c_str(), 0, 16); + first += 2; + } else { + result += *first; + } + } else { + result += *first; + } + } + return result; +} + +} // namespace util + +} // namespace spdylay diff --git a/examples/util.h b/examples/util.h new file mode 100644 index 00000000..2ee7d533 --- /dev/null +++ b/examples/util.h @@ -0,0 +1,168 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +namespace spdylay { + +namespace util { + +extern const std::string DEFAULT_STRIP_CHARSET; + +template +std::pair stripIter +(InputIterator first, InputIterator last, + const std::string& chars = DEFAULT_STRIP_CHARSET) +{ + for(; first != last && + std::find(chars.begin(), chars.end(), *first) != chars.end(); ++first); + if(first == last) { + return std::make_pair(first, last); + } + InputIterator left = last-1; + for(; left != first && + std::find(chars.begin(), chars.end(), *left) != chars.end(); --left); + return std::make_pair(first, left+1); +} + +template +OutputIterator splitIter +(InputIterator first, + InputIterator last, + OutputIterator out, + char delim, + bool doStrip = false, + bool allowEmpty = false) +{ + for(InputIterator i = first; i != last;) { + InputIterator j = std::find(i, last, delim); + std::pair p(i, j); + if(doStrip) { + p = stripIter(i, j); + } + if(allowEmpty || p.first != p.second) { + *out++ = p; + } + i = j; + if(j != last) { + ++i; + } + } + if(allowEmpty && + (first == last || *(last-1) == delim)) { + *out++ = std::make_pair(last, last); + } + return out; +} + +template +OutputIterator split +(InputIterator first, + InputIterator last, + OutputIterator out, + char delim, + bool doStrip = false, + bool allowEmpty = false) +{ + for(InputIterator i = first; i != last;) { + InputIterator j = std::find(i, last, delim); + std::pair p(i, j); + if(doStrip) { + p = stripIter(i, j); + } + if(allowEmpty || p.first != p.second) { + *out++ = std::string(p.first, p.second); + } + i = j; + if(j != last) { + ++i; + } + } + if(allowEmpty && + (first == last || *(last-1) == delim)) { + *out++ = std::string(last, last); + } + return out; +} + +template +std::string strjoin(InputIterator first, InputIterator last, + const DelimiterType& delim) +{ + std::string result; + if(first == last) { + return result; + } + InputIterator beforeLast = last-1; + for(; first != beforeLast; ++first) { + result += *first; + result += delim; + } + result += *beforeLast; + return result; +} + +template +std::string joinPath(InputIterator first, InputIterator last) +{ + std::vector elements; + for(;first != last; ++first) { + if(*first == "..") { + if(!elements.empty()) { + elements.pop_back(); + } + } else if(*first == ".") { + // do nothing + } else { + elements.push_back(*first); + } + } + return strjoin(elements.begin(), elements.end(), "/"); +} + +bool isAlpha(const char c); + +bool isDigit(const char c); + +bool isHexDigit(const char c); + +bool inRFC3986UnreservedChars(const char c); + +std::string percentEncode(const unsigned char* target, size_t len); + +std::string percentEncode(const std::string& target); + +std::string percentDecode +(std::string::const_iterator first, std::string::const_iterator last); + +} // namespace util + +} // namespace spdylay + +#endif // UTIL_H