commit 9a3a42a6441fd64cdff2adb6bf2822c1356067a8 Author: Vinnie Falco Date: Thu Jul 20 08:01:46 2017 -0700 Truncate history, version 1.0.0-b2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8d9c7343 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Set default behaviour, in case users don't have core.autocrlf set. +* text=auto + +# Github +.md text eol=lf + +# Visual Studio +*.sln text eol=crlf +*.vcproj text eol=crlf +*.vcxproj text eol=crlf +*.props text eol=crlf +*.filters text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cc8f6cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +docs/ +._* +*.mode1v3 +*.pbxuser +*.perspectivev3 +*.user +*.ncb +*.suo +*.obj +*.ilk +*.pch +*.pdb +*.dep +*.idb +*.manifest +*.manifest.res +*.o +*.opensdf +*.d +*.sdf +xcuserdata +contents.xcworkspacedata +.DS_Store +.svn +profile +bin/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4ffc027c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,69 @@ +sudo: false +language: cpp + +env: + global: + # Maintenance note: to move to a new version + # of boost, update both BOOST_ROOT and BOOST_URL. + # Note that for simplicity, BOOST_ROOT's final + # namepart must match the folder name internal + # to boost's .tar.gz. + - BOOST_ROOT=$HOME/boost_1_60_0 + - BOOST_URL='http://downloads.sourceforge.net/project/boost/boost/1.60.0/boost_1_60_0.tar.gz?r=https%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2F1.60.0%2Fboost_1_60_0.tar.gz&ts=1460417589&use_mirror=netix' +packages: &gcc5_pkgs + - gcc-5 + - g++-5 + - python-software-properties + - protobuf-compiler + - libstdc++6 + - binutils-gold + # Provides a backtrace if the unittests crash + - gdb + +packages: &clang36_pkgs + - clang-3.6 + - g++-5 + - python-software-properties + - libssl-dev + - libstdc++6 + - binutils-gold + # Provides a backtrace if the unittests crash + - gdb + +matrix: + include: + - compiler: gcc + env: GCC_VER=5 VARIANT=debug + addons: &ao_gcc5 + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: *gcc5_pkgs + + - compiler: gcc + env: GCC_VER=5 VARIANT=release + addons: *ao_gcc5 + + - compiler: clang + env: GCC_VER=5 VARIANT=debug CLANG_VER=3.6 + addons: &ao_clang36 + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] + packages: *clang36_pkgs + + - compiler: clang + env: GCC_VER=5 VARIANT=release CLANG_VER=3.6 + addons: *ao_clang36 + +cache: + directories: + - $BOOST_ROOT + +before_install: + - scripts/install-dependencies.sh + +script: + - scripts/build-and-test.sh + +notifications: + email: + false diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d1600df9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +# Part of Beast + +cmake_minimum_required (VERSION 3.5) + +project (Beast) + +set_property (GLOBAL PROPERTY USE_FOLDERS ON) + +if (WIN32) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /wd4100 /D_SCL_SECURE_NO_WARNINGS=1 /D_CRT_SECURE_NO_WARNINGS=1") +else() +endif() + +message ("cxx Flags: " ${CMAKE_CXX_FLAGS}) + +macro(GroupSources curdir) + file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) + foreach(child ${children}) + if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + GroupSources(${curdir}/${child}) + elseif(${child} STREQUAL "CMakeLists.txt") + source_group("" FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + else() + string(REPLACE "/" "\\" groupname ${curdir}) + string(REPLACE "src" "Common" groupname ${groupname}) + source_group(${groupname} FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + endif() + endforeach() +endmacro() + +include_directories (include) + +file(GLOB_RECURSE BEAST_INCLUDES + ${PROJECT_SOURCE_DIR}/include/beast/*.hpp + ${PROJECT_SOURCE_DIR}/include/beast/*.ipp +) + +add_subdirectory (test) +add_subdirectory (examples) + +#enable_testing() diff --git a/Jamroot b/Jamroot new file mode 100644 index 00000000..8d8f8c60 --- /dev/null +++ b/Jamroot @@ -0,0 +1,92 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; +import feature ; +import boost ; + +boost.use-project ; + +if [ os.name ] = SOLARIS +{ + lib socket ; + lib nsl ; +} +else if [ os.name ] = NT +{ + lib ws2_32 ; + lib mswsock ; +} +else if [ os.name ] = HPUX +{ + lib ipv6 ; +} +else if [ os.name ] = QNXNTO +{ + lib socket ; +} +else if [ os.name ] = HAIKU +{ + lib network ; +} + +if [ os.name ] = NT +{ + lib ssl : : ssleay32 ; + lib crypto : : libeay32 ; +} +else +{ + lib ssl ; + lib crypto ; +} + +project beast + : requirements + . + ./include + #/boost//headers + /boost/system//boost_system + /boost/filesystem//boost_filesystem + /boost/program_options//boost_program_options +# ssl +# crypto + BOOST_ALL_NO_LIB=1 + BOOST_SYSTEM_NO_DEPRECATED=1 + multi + static + shared + on + gcc:-std=c++11 + gcc:-Wno-unused-variable + clang:-std=c++11 + msvc:_SCL_SECURE_NO_WARNINGS=1 + msvc:_CRT_SECURE_NO_WARNINGS=1 + LINUX:_XOPEN_SOURCE=600 + LINUX:_GNU_SOURCE=1 + SOLARIS:_XOPEN_SOURCE=500 + SOLARIS:__EXTENSIONS__ + SOLARIS:socket + SOLARIS:nsl + NT:_WIN32_WINNT=0x0501 + NT,cw:ws2_32 + NT,cw:mswsock + NT,gcc:ws2_32 + NT,gcc:mswsock + NT,gcc-cygwin:__USE_W32_SOCKETS + HPUX,gcc:_XOPEN_SOURCE_EXTENDED + HPUX:ipv6 + QNXNTO:socket + HAIKU:network + : usage-requirements + . + : + build-dir bin + ; + +build-project test ; +build-project examples ; diff --git a/LICENSE_1_0.txt b/LICENSE_1_0.txt new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..d00ba300 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Beast [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) + +Beast provides implementations of the HTTP and WebSocket protocols +built on top of Boost.Asio and other parts of boost. + +Requirements: + +* Boost +* C++11 or greater +* OpenSSL (optional) + +Linking applications with beast: + +You need to include the .cpp file `src/beast_http_nodejs_parser.cpp` +in the build script or Makefile for your program in order to link. + +Links: + +* [Home](http://vinniefalco.github.io/) +* [Repository](https://github.com/vinniefalco/Beast) +* [Documentation](http://vinniefalco.github.io/beast/) +* [Autobahn.testsuite results](http://vinniefalco.github.io/autobahn/index.html) + +Please report issues or questions here: +https://github.com/vinniefalco/Beast/issues diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 00000000..2de8add5 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,32 @@ +* Change build options to C++11 only +* Replace Jamroot with Jamfile +* Fix failing test/message.cpp +* Complete allocator testing in basic_streambuf, basic_headers +* Tidy up type_checks + - Derive from std::integral_constant +* Check DOXYGEN, GENERATIC_DOCS directives in source + - See if we can include them now that xsl is fixed +* Go over each header and split header material into detail and impl files +* Make buffers_debug a detail +* Roll header-only http parser +* Define Parser concept in HTTP +* melpon sandbox? +* invokable unit test +* trim public interface of rfc2616.h to essentials only +* Use new http routines in JSONRPCClient +* Remove or change http::headers alias +* Do something about the methods.hpp and fields.hpp type headers +* Fix index in docs +* Fix integer warning in file_body.hpp +* Use make_error_code in websocket to set the category right +* Figure out why namespace rfc2616 is included in docs + (currently disabled via GENERATING_DOCS macro) +* Include Example program listings in the docs +* Update for rfc7230 +* HTTP parser size limit with test (configurable?) +* HTTP parser trailers with test +* URL parser, strong URL checking in HTTP parser +* Fix method, use string instead of enum +* More fine grained parser errors +* Fix all the warnings in all projects/build configs +* Fix bidirectional buffers iterators operator->() diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 00000000..b775bdcf --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,4 @@ +html +temp +reference.qbk +out.txt diff --git a/doc/Jamfile b/doc/Jamfile new file mode 100644 index 00000000..48e72a0a --- /dev/null +++ b/doc/Jamfile @@ -0,0 +1,83 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +local broot = [ os.environ BOOST_ROOT ] ; + +project beast/doc ; + +using boostbook ; +using quickbook ; +using doxygen ; + +xml beast_boostbook : beast.qbk ; + +path-constant out : . ; + +install stylesheets + : + $(broot)/doc/src/boostbook.css + : + $(out)/html + ; + +explicit stylesheets ; + +install images + : + [ glob $(broot)/doc/src/images/*.png ] + images/beast.png + images/body.png + images/message.png + : + $(out)/html/images + ; + +explicit images ; + +install callouts + : + [ glob $(broot)/doc/src/images/callouts/*.png ] + : + $(out)/html/images/callouts + ; + +explicit callout ; + +boostbook doc + : + beast_boostbook + : + chapter.autolabel=0 + boost.image.src=images/beast.png + boost.image.alt="Beast Logo" + boost.image.w=1007 + boost.image.h=107 + boost.root=$(broot) + chapter.autolabel=0 + chunk.first.sections=1 # Chunk the first top-level section? + chunk.section.depth=8 # Depth to which sections should be chunked + generate.section.toc.level=1 # Control depth of TOC generation in sections + toc.max.depth=2 # How many levels should be created for each TOC? + toc.section.depth=2 # How deep should recursive sections appear in the TOC? + : + temp + stylesheets + images + ; + +#explicit doc ; +# nav.layout=none +# html:location=../bin/doc/html +# generate.toc="chapter nop section nop" +# root.filename=index +# output-root="../bin/html" + + +#[include reference.qbk] +#[xinclude index.xml] diff --git a/doc/beast.dox b/doc/beast.dox new file mode 100644 index 00000000..007fc36e --- /dev/null +++ b/doc/beast.dox @@ -0,0 +1,373 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "Beast" +PROJECT_NUMBER = +PROJECT_BRIEF = C++ Library +PROJECT_LOGO = images/beast.png +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = NO +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = YES +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = NO +SHOW_NAMESPACES = NO +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = \ + ../include/beast/async_completion.hpp \ + ../include/beast/basic_streambuf.hpp \ + ../include/beast/bind_handler.hpp \ + ../include/beast/buffer_cat.hpp \ + ../include/beast/buffers_adapter.hpp \ + ../include/beast/buffers_debug.hpp \ + ../include/beast/consuming_buffers.hpp \ + ../include/beast/handler_alloc.hpp \ + ../include/beast/http.hpp \ + ../include/beast/placeholders.hpp \ + ../include/beast/prepare_buffers.hpp \ + ../include/beast/static_streambuf.hpp \ + ../include/beast/streambuf.hpp \ + ../include/beast/streambuf_readstream.hpp \ + ../include/beast/type_check.hpp \ + ../include/beast/websocket.hpp \ + ../include/beast/write_streambuf.hpp \ + ../include/beast/http/basic_headers.hpp \ + ../include/beast/http/basic_parser.hpp \ + ../include/beast/http/chunk_encode.hpp \ + ../include/beast/http/empty_body.hpp \ + ../include/beast/http/error.hpp \ + ../include/beast/http/fields.hpp \ + ../include/beast/http/headers.hpp \ + ../include/beast/http/message.hpp \ + ../include/beast/http/method.hpp \ + ../include/beast/http/parse_error.hpp \ + ../include/beast/http/parser.hpp \ + ../include/beast/http/read.hpp \ + ../include/beast/http/resume_context.hpp \ + ../include/beast/http/rfc2616.hpp \ + ../include/beast/http/streambuf_body.hpp \ + ../include/beast/http/string_body.hpp \ + ../include/beast/http/type_check.hpp \ + ../include/beast/http/write.hpp \ + ../include/beast/websocket/error.hpp \ + ../include/beast/websocket/option.hpp \ + ../include/beast/websocket/rfc6455.hpp \ + ../include/beast/websocket/ssl.hpp \ + ../include/beast/websocket/static_string.hpp \ + ../include/beast/websocket/stream.hpp \ + ../include/beast/websocket/teardown.hpp \ + +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = dhtm +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = ../bin/doc/xml +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = ../ +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN \ + GENERATING_DOCS +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/doc/beast.qbk b/doc/beast.qbk new file mode 100644 index 00000000..47a26c6d --- /dev/null +++ b/doc/beast.qbk @@ -0,0 +1,197 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[library Beast + [quickbook 1.6] + [copyright 2013 - 2016 Vinnie Falco] + [purpose C++ Library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [authors [Falco, Vinnie]] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] +[template ticket[number]''''''#[number]''''''] +[def __POSIX__ /POSIX/] +[def __Windows__ /Windows/] +[def __accept__ [@http://www.opengroup.org/onlinepubs/000095399/functions/accept.html `accept()`]] +[def __connect__ [@http://www.opengroup.org/onlinepubs/000095399/functions/connect.html `connect()`]] +[def __getpeername__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getpeername.html `getpeername()`]] +[def __getsockname__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockname.html `getsockname()`]] +[def __getsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockopt.html `getsockopt()`]] +[def __ioctl__ [@http://www.opengroup.org/onlinepubs/000095399/functions/ioctl.html `ioctl()`]] +[def __recvfrom__ [@http://www.opengroup.org/onlinepubs/000095399/functions/recvfrom.html `recvfrom()`]] +[def __sendto__ [@http://www.opengroup.org/onlinepubs/000095399/functions/sendto.html `sendto()`]] +[def __setsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/setsockopt.html `setsockopt()`]] +[def __socket__ [@http://www.opengroup.org/onlinepubs/000095399/functions/socket.html `socket()`]] + + + +[section:intro Introduction] + +Beast is a cross-platform C++ library built on Boost, containing two modules +implementing widely used network protocols. Beast.HTTP offers a universal +model for describing, sending, and receiving HTTP messages while Beast.WebSocket +provides a complete implementation of the WebSocket protocol. Their design +achieves these goals: + +* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be +used to build clients, servers, or both. + +* [*Ease of Use.] HTTP messages are modeled using simple, readily +accessible objects. Functions and classes used to send and receive HTTP +or WebSocket messages are designed to resemble Boost.Asio as closely as +possible. Users familiar with Boost.Asio will be immediately comfortable +using this library. + +* [*Flexibility.] Interfaces do not mandate specific implementation +strategies; important decisions such as buffer or thread management are +left to users of the library. + +* [*Performance.] The implementation performs competitively, making it a +realistic choice for building a high performance network server. + +* [*Scalability.] Development of network applications that scale to thousands +of concurrent connections is possible with the implementation. + +* [*Basis for further abstraction.] The interfaces facilitate the +development of other libraries that provide higher levels of abstraction. + + + +[section:requirements Requirements] + +Beast requires: + +* [*C++11.] A minimum of C++11 is needed. +* [*Boost.] Beast is built on Boost, especially Boost.Asio. +* [*OpenSSL.] If using TLS/Secure sockets (optional). + +[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] + +The library is [*header-only]. It is not necessary to add any .cpp files, +or to edit your existing build script or project file except to provide +that the include/ directory for beast is searched for include files. + +[endsect] + + + +[section:example Examples] + +These usage examples are intended to quickly impress upon readers the +flavor of the library. They are complete programs which may be built +and run. Source code and build scripts for these programs may be found +in the examples directory. + +Use HTTP to request the root page from a website and print the response: +``` +#include +#include +#include +#include + +int main() +{ + // Normal boost::asio setup + std::string const host = "boost.org"; + boost::asio::io_service ios; + boost::asio::ip::tcp::resolver r(ios); + boost::asio::ip::tcp::socket sock(ios); + boost::asio::connect(sock, + r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); + + using namespace beast::http; + + // Send HTTP request using beast + request req({method_t::http_get, "/", 11}); + req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); + req.headers.replace("User-Agent", "Beast"); + write(sock, req); + + // Receive and print HTTP response using beast + beast::streambuf sb; + response resp; + read(sock, sb, resp); + std::cout << resp; +} +``` + +Establish a WebSocket connection, send a message and receive the reply: +``` +#include +#include +#include +#include +#include + +int main() +{ + // Normal boost::asio setup + std::string const host = "echo.websocket.org"; + boost::asio::io_service ios; + boost::asio::ip::tcp::resolver r(ios); + boost::asio::ip::tcp::socket sock(ios); + boost::asio::connect(sock, + r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); + + using namespace beast::websocket; + + // WebSocket connect and send message using beast + stream ws(sock); + ws.handshake(host, "/"); + ws.write(boost::asio::buffer("Hello, world!")); + + // Receive WebSocket message, print and close using beast + beast::streambuf sb; + opcode op; + ws.read(op, sb); + ws.close(close_code::normal); + std::cout << + beast::debug::buffers_to_string(sb.data()) << "\n"; +} +``` + +[endsect] + + + +[section:credits Credits] + +Beast would not be possible without the considerable time and patience +contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, +Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for +supporting its development. Thanks also to Christopher Kohloff, whose Asio +C++ library is the inspiration behind which much of the design and +documentation is based. + +[endsect] + + + +[endsect] + + + +[include http.qbk] +[include websocket.qbk] +[include types.qbk] +[include design.qbk] +[section:quickref Quick Reference] +[xinclude quickref.xml] +[endsect] +[include reference.qbk] +[section:idx Index] +[xinclude index.xml] +[endsect] diff --git a/doc/boostbook.dtd b/doc/boostbook.dtd new file mode 100644 index 00000000..bd4c3f87 --- /dev/null +++ b/doc/boostbook.dtd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%DocBook; diff --git a/doc/design.qbk b/doc/design.qbk new file mode 100644 index 00000000..316b83b6 --- /dev/null +++ b/doc/design.qbk @@ -0,0 +1,213 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:design Design choices] + +The implementations are driven by business needs of cryptocurrency server +applications ([link https://ripple.com Ripple] written in C++. These +needs were not met by existing solutions so new code was written. The new +code tries to avoid design flaws encountered in the already-existing software +libraries: + +* Don't sacrifice performance. + +* Don't do too much, otherwise interfaces become rigid. + +* Symmetric interfaces (client and server the same, or close to it). + +* Emulate Boost.Asio interfaces as much as possible, since Asio is + proven and it is familiar to users. + +* Let library users make the important decisions such as how to + allocate memory or how to leverage flow control. + +Beast formalizes the [link beast.types.Streambuf [*`Streambuf`]] concept, +and relies heavily on the Boost.Asio [*`ConstBufferSequence`] and +[*`MutableBufferSequence`] concepts for passing buffers to functions. +The authors have found the `Streambuf` and buffer sequence interfaces +to be optimal for interacting with Asio, and for other tasks such as +incremental parsing of data in buffers (for example, parsing websocket +frames stored in a [link beast.ref.static_streambuf `static_streambuf`]). + +During the development of Beast the authors have studied other software +packages and in particular the comments left during the Boost Review process +of other packages offering similar functionality. In this section we attempt +to address those issues. + +[variablelist +[[ + "I would also like to see instances of this library being used + in production. That would give some evidence that the design + works in practice."" +][ + Beast.HTTP and Beast.WebSocket will be used in [*rippled], an + asynchronous peer to peer server that implements the + [*Ripple Consensus Protocol]. These servers are deployed in multiple + production environments, with banks in many countries running client + applications that connect to [*rippled]. +]] + +] + + +[section:http HTTP] + +For HTTP we to model the message to maximize flexibility of implementation +strategies while allowing familiar verbs such as [*`read`] and [*`write`]. +The HTTP interface is further driven by the needs of the WebSocket module, +as a WebSocket session requires a HTTP Upgrade handshake exchange at the +start. Other design goals: + +* Don't try to invent a complete web server or client + +* Have simple free functions to send and receive messages. + +* Allow the message object to be customized, + +[variablelist + +[[ + "Some more advanced examples, e.g. including TLS with client/server + certificates would help."" +][ + The HTTP interface doesn't try to reinvent the wheel, it just uses + the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that + you set up beforehand. Callers use the interfaces already existing + on those objects to make outgoing connections, accept incoming connections, + or establish TLS sessions with certificates. We find the available Asio + examples for performing these tasks sufficient. +]] + +[[ + "A built-in router?" +][ + We presume this means a facility to match expressions against the URI + in HTTP requests, and dispatch them to calling code. The authors feel + that this is a responsibility of higher level code. Beast.HTTP does + not try to offer a web server. +]] + +[[ + "Cookies? Forms/File Uploads?"" +][ + Cookies, or managing these types of HTTP headers in general, is the + responsibility of higher levels. Beast.HTTP just tries to get complete + messages to and from the calling code. It deals in the HTTP headers just + enough to process the message body and leaves the rest to callers. However, + for forms and file uploads the symmetric interface of the message class + allows HTTP requests to include arbitrary body types including those needed + to upload a file or fill out a form. +]] + +[[ + "...supporting TLS (is this a feature? If not this would be a show-stopper), + etc. +][ + Beast.HTTP does not provide direct facilities for implementing TLS + connections; however, the interfaces already existing on the + `boost::asio::ssl::stream` are available and can be used to establish + secure connections. Then, functions like `http::read` or `http::async_write` + can work with those encrypted connections with no problem. +]] + +[[ + "There should also be more examples of how to integrate the http service + with getting files from the file system, generating responses CGI-style" +][ + The design goal for the library is to not try to invent a web server. + We feel that there is a strong need for a basic implementation that + models the HTTP message and provides functions to send and receive them + over Asio. Such an implementation should serve as a building block upon + which higher abstractions such as the aforementioned HTTP service or + cgi-gateway can be built. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + These behaviors are best left to the calling software. A future library + can build on Beast.HTTP to provide these behaviors. +]] + +[[ + "What about HTTP/2?"" +][ + Many reviewers feel that HTTP/2 support is an essential feature of + a HTTP library. The authors agree that HTTP/2 is important but also + feel that the most sensible implementation is one that does not re-use + the same network reading and writing interface for 2 as that for 1.0 + and 1.1. + + The Beast.HTTP message model is suitable for HTTP/2 and can be re-used. + The IEFT HTTP Working Group adopted message compatiblity with HTTP/1.x + as an explicit goal. A parser can simply emit full headers after + decoding the compressed HTTP/2 headers. The stream ID is not logicaly + part of the message but rather message metadata and should be + communicated out-of-band (see below). HTTP/2 sessions begin with a + traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket + upgrade. A HTTP/2 implementation can use existing Beast.HTTP primitives + to perform this handshake. + + Free functions for HTTP/2 sessions are not possible because of the + requirement to maintain per-session state. For example, to decode the + compressed headers. Or to remember and respect the remote peer's window + settings. The authors propose that a HTTP/2 implementation be written + as a separate class template, similar to the `websocket::stream` but with + additional interfaces to support version 2 features. We feel that + Beast.HTTP offers enough useful functionality to justify inclusion, + so that developers can take advantage of it right away instead of + waiting. +]] + +] + + +[endsect] + +[section:websocket WebSocket] + +[variablelist +[[ + What about message compression? +][ + The feature is not currently present in the library, but the choice + of type requirements for buffers passed to the read functions have been + made with compression in mind. There is the plan to add this feature; + however, we feel that even without compression users can begin taking + advantage of the WebSocket protocol immediately with this library. +]] + +[[ + Where is the TLS/SSL interface? +][ + The `websocket::stream` just wraps the socket or stream that you + provide (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection + using the interface on `ssl::stream` like shown in all of the Asio + examples, they construct your `websocket::stream` around it. + It works perfectly fine - Beast.WebSocket doesn't try to reinvent the + wheel or put a fresh coat of interface paint on the `ssl::stream`. + + The WebSocket implementation [*does] provides support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.wsproto__teardown `teardown`] and + [link beast.ref.wsproto__async_teardown `async_teardown`]. These will + properly close the connection as per rfc6455 and overloads are available + for TLS streams. Callers may provide their own overloads of these functions + for user-defined next layer types. +]] +] + +[endsect] + + + + + + + +[endsect] diff --git a/doc/http.qbk b/doc/http.qbk new file mode 100644 index 00000000..85f91b66 --- /dev/null +++ b/doc/http.qbk @@ -0,0 +1,367 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:http HTTP] + +Beast.HTTP offers programmers simple and performant models of HTTP messages and +their associated operations including synchronous and asynchronous reading and +writing using Boost.Asio. + +The HTTP protocol is described fully in +[@https://tools.ietf.org/html/rfc2616 rfc2616] + + + +[section:motivation Motivation] + +The HTTP protocol is pervasive in network applications. As C++ is a logical +choice for high performance network servers, there is great utility in solid +building blocks for manipulating, sending, and receiving HTTP messages +compliant with the Hypertext Transfer Protocol and the supplements that +follow. Unfortunately reliable implementations or industry standards do not +exist in C++. + +Beast.HTTP is built on Boost.Asio and uses HTTP parser from NodeJS, which is +extensively field tested and exceptionally robust. A proposal to add networking +functionality to the C++ standard library, based on Boost.Asio, is under +consideration by the standards committee. Since the final approved networking +interface for the C++ standard library will likely closely resemble the current +interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its +network transport. + +[heading Scope] + +The scope of this library is meant to include only the functionality of +modeling the HTTP message, serializing and deserializing the message, and +sending and receiving messages on sockets or streams. It is designed to +be a building block for creating higher level abstractions. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the HTTP protocol specification described in +[@https://tools.ietf.org/html/rfc7230 rfc7230] ] + +[endsect] + + + +[section:usage Usage] + +[note + Sample code and identifiers mentioned in this section are written + as if the following declarations are in effect: + ``` + #include + using namespace beast; + ``` +] + +A HTTP message (referred to hereafter as "message") contains a request or +response line, a series of zero or more name/value pairs (collectively +termed "headers"), and a series of octets called the message body which may +be zero in length. The HTTP protocol defines the client and server roles: +clients send messages called requests and servers send back messages called +responses. `http::message` models both requests and responses. The library +provides interfaces to perform these operations on messages: + +* [*Parse] a new message from a series of octets. + +* [*Assemble] a new message from scratch or from an existing message. + +* [*Serialize] a message into a series of octets. + +* [*Read] a message from a stream. This can be thought of as a compound +operation; a network read, followed by a [*parse]. + +* [*Write] a message to a stream. This can be thought of as a compound +operation: a [*serialize] followed by a network write. + +In the paragraphs that follow we describe simple interfaces that will serve +the majority of users looking merely to interact with a HTTP server, or +handle simple HTTP requests from clients. Subsequent sections cover the +message model in more depth, for advanced applications. + +[heading Declarations] + +To do anything, a message must be declared. The message class template +requires at mininum, a bool indicating whether the message is a request +(versus a response), and a `Body` type. The choice of `Body` determines the +kind of container used to represent the message body. Here we will +declare a request that has a `std::string` for the body container: +``` + http::message req; +``` + +Two type aliases are provided for notational convenience when declaring +messages. These two statements declare a request and a response respectively: +``` + http::request req; + http::response resp; +``` + +[heading Members] + +Message objects are default constructible, with public access to data members. +Request and response objects have some common members, and some members unique +to the message type. These statements set all the members in each message: +``` + http::request req; + req.method = http::method_t::http_get; + req.url = "/index.html"; + req.version = 11; // HTTP/1.1 + req.headers.insert("User-Agent", "hello_world"); + req.body = ""; + + http::response resp; + resp.status = 404; + resp.reason = "Not Found"; + resp.version = 10; // HTTP/1.0 + resp.headers.insert("Server", "Beast.HTTP"); + resp.body = "The requested resource was not found."; +``` + +The following statements achieve the same effects as the statements above: +``` + http::request req({http::method_t::http_get, "/index.html", 11}); + req.headers.insert("User-Agent", "hello_world"); + req.body = ""; + + http::response resp({404, "Not Found", 10}); + resp.headers.insert("Server", "Beast.HTTP"); + resp.body = "The requested resource was not found."; +``` + +[heading Headers] + +The `message::headers` member is a container for setting the field/value +pairs in the message. These statements change the values of the headers +in the message passed: +``` + template + void set_fields(http::request& req) + { + if(! req.exists("User-Agent")) + req.insert("User-Agent", "myWebClient"); + + if(req.exists("Accept-Charset")) + req.erase("Accept-Charset"); + + req.replace("Accept", "text/plain"); + } +``` + +[heading Body] + +The `message::body` member represents the message body. Depending on the +`Body` template argument type, this could be a writable container. The +following types, provided by the library, are suitable choices for the +`Body` type: + +* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. +Used in GET requests where there is no message body. Example: +``` + http::request req({http::method_t::http_get, "/index.html", 11}); +``` + +* [link beast.ref.http__string_body [*`string_body`:]] A body with a +`value_type` as `std::string`. Useful for quickly putting together a request +or response with simple text in the message body (such as an error message). +Has the same insertion complexity of `std::string`. This is the type of body +used in the examples: +``` + http::response resp; + static_assert(std::is_same::value); + resp.body = "Here is the data you requested"; +``` + +* [link beast.ref.http__basic_streambuf_body [*`basic_streambuf_body`:]] A body with a +`value_type` of `streambuf`. A streambuf is an efficient storage object which +uses multiple octet arrays of varying lengths to represent data. Here is +a body that uses a `boost::asio::streambuf` as its container: +``` + template + http::response> + make_response(ConstBufferSequence const& buffers) + { + http::response> resp; + resp.body.commit(boost::asio::buffer_copy(resp.body.prepare( + boost::asio::buffer_size(buffers)), buffers)); + return resp; + } +``` + +[heading Sockets] + +The library provides simple free functions modeled after Boost.Asio to +send and receive messages on TCP/IP sockets, SSL streams, or any object +which meets the Boost.Asio type requirements (SyncReadStream, SyncWriteStream, +AsyncReadStream, and AsyncWriteStream depending on the types of operations +performed). To send messages synchronously, use one of the `http:write` +functions: +``` + void send_request(boost::asio::ip::tcp::socket& sock) + { + http::request req({http::method_t::http_get, "/index.html", 11}); + ... + http::write(sock, req); // Throws exception on error + ... + // Alternatively + boost::system::error:code ec; + http::write(sock, req, ec); + if(ec) + std::cerr << "error writing http message: " << ec.message(); + } +``` + +An asynchronous interface is available: +``` + void handle_write(boost::system::error_code); + ... + http::request req; + ... + http::async_write(sock, req, std::bind(&handle_write, std::placeholders::_1)); +``` + +When the implementation reads messages from a socket, it can read bytes lying +after the end of the message if they are present (the alternative is to read +a single byte at a time which is unsuitable for performance reasons). To +store and re-use these extra bytes on subsequent messages, the read interface +requires an additional paramter: a [link beast.types.Streambuf [*`Streambuf`]] +object. This example reads a message from the socket, with the extra bytes +stored in the streambuf parameter for use in a subsequent call to read: +``` + boost::asio::streambuf sb; + ... + http::response resp; + http::read(sock, sb, resp); // Throws exception on error + ... + // Alternatively + boost::system::error:code ec; + http::read(sock, sb, resp, ec); + if(ec) + std::cerr << "error reading http message: " << ec.message(); +``` + +As with the write function, an asynchronous interface is available. The +stream buffer parameter must remain valid until the completion handler is +called: +``` + void handle_read(boost::system::error_code); + ... + boost::asio::streambuf sb; + http::response resp; + ... + http::async_read(sock, resp, std::bind(&handle_read, std::placeholders::_1)); +``` + +An alternative to using a `boost::asio::streambuf` is to use a +[link beast.ref.streambuf `beast::streambuf`], which meets the requirements of +[*`Streambuf`] and is optimized for performance: +``` + void handle_read(boost::system::error_code); + ... + beast::streambuf sb; + http::response resp; + http::read(sock, sb, resp); +``` + +The `read` implementation can use any object meeting the requirements of +[link beast.types.Streambuf [*`Streambuf`]], allowing callers to define custom +memory management strategies used by the implementation. + +[endsect] + + + +[section:advanced Advanced] + +The spectrum of hardware and software platforms which perform these typical +HTTP operations is vast, ranging from powerful servers in large datacenters +to tiny resource-limited embedded devices. No single concrete implementation +of a class intended to model messages can efficiently serve all needs. +For example, an object that minimizes resources during parsing may not be +able to edit and change headers dynamically. A message that represents the +message body as a disk file may support sending but not parsing. Many efficient +and correct models of messages exist, supporting some or all of the +operations listed above. + +[heading Message model] + +The message class template and provided Body types are suitable for casual +library users. This section explains the message model for advanced users +who wish to take control over aspects of the implementation. We introduce +customization points for the header and body via class template arguments. +This illustration shows more detail about the +[link beast.ref.http__message [*`message`]] class template (boilerplate +present in the actual declaration has been removed for clarity): + +[$images/message.png [width 580px] [height 225px]] + +The default constructor, move special members, and copy special members are +all defaulted. A message is movable, copyable, or default constructible based +on the capabilities of its template arguments. + +Messages modeled in this fashion are ['complete], containing all of the +information required to perform the supported set of operations. They are +['first-class types], returnable from functions and composable. HTTP +requests and responses are distinct types, allowing functions to be +overloaded on the type of message. + +[endsect] + + + +[section:headers Headers Type] + +The `Headers` type represents the field/value pairs present in every HTTP +message. These types implement the +[link beast.types.FieldSequence [*`FieldSequence`]] +concept. The value type of a field sequence is an object meeting the +requirements of [link beast.types.Field [*`Field`]]. The implementation can +serialize any instance of `Headers` that meets the field sequence requirements. +This example shows a function which returns `true` if the specified field +sequence has a connect field: +``` +template +bool +has_connect(FieldSequence const& fs) +{ + return std::find_if(fs.begin(), fs.end(), + [&](auto const& field) + { + return ci_equal(field.name(), "Connect"); + }); +} +``` + +[endsect] + + + +[section:body Body Type] + +The `Body` template argument in the `message` class must meet the +[link beast.types.Body [*`Body`] requirements]. It provides customization +of the data member in the message, the algorithm for parsing, and the +algorithm for serialization: + +[$images/body.png [width 510px] [height 210px]] + +Instances of the optional nested types `writer` and `reader` perform +serialization and deserialization of the message body. If either or +both of these types are present, the message becomes serializable, parsable, +or both. They model [link beast.types.Reader [*`Reader`]] and +[link beast.types.Writer [*`Writer`]] respectively. + +For specialized applications, users may implement their own types which +meet the requirements. The examples included with this library provide a +Body implementation used to serve files in a HTTP server. + +[endsect] + + +[endsect] + diff --git a/doc/images/beast.png b/doc/images/beast.png new file mode 100644 index 00000000..3444d581 Binary files /dev/null and b/doc/images/beast.png differ diff --git a/doc/images/body.png b/doc/images/body.png new file mode 100644 index 00000000..54d05550 Binary files /dev/null and b/doc/images/body.png differ diff --git a/doc/images/body.psd b/doc/images/body.psd new file mode 100644 index 00000000..3d8dca09 Binary files /dev/null and b/doc/images/body.psd differ diff --git a/doc/images/message.png b/doc/images/message.png new file mode 100644 index 00000000..cb07efc8 Binary files /dev/null and b/doc/images/message.png differ diff --git a/doc/index.xml b/doc/index.xml new file mode 100644 index 00000000..0bbf7b76 --- /dev/null +++ b/doc/index.xml @@ -0,0 +1,13 @@ + + + + + +
+ +
diff --git a/doc/makeqbk.sh b/doc/makeqbk.sh new file mode 100644 index 00000000..584cb8a9 --- /dev/null +++ b/doc/makeqbk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +mkdir -p ../bin/doc/xml +doxygen beast.dox +cd ../bin/doc/xml +xsltproc combine.xslt index.xml > all.xml +cd ../../../doc +xsltproc reference.xsl ../bin/doc/xml/all.xml > reference.qbk diff --git a/doc/quickref.xml b/doc/quickref.xml new file mode 100644 index 00000000..460e6d3c --- /dev/null +++ b/doc/quickref.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + HTTP + + + WebSocket + + + + + + + Classes + + basic_headers + basic_parser + basic_streambuf_body + empty_body + error_code + message + resume_context + streambuf_body + string_body + + Type Traits + + is_Body + + + + Functions + + async_read + async_write + chunk_encode + chunk_encode_final + read + write + + Concepts + + Body + Field + FieldSequence + Reader + Writer + + + + Classes + + close_reason + static_string + stream + + Options + + auto_fragment_size + decorate + keep_alive + read_buffer_size + read_message_max + write_buffer_size + + + + Functions + + async_teardown + teardown + + Constants + + close_code + opcode + + + + + + + + + + + + + + Core + + + + + + + Classes + + async_completion + basic_streambuf + buffers_adapter + consuming_buffers + handler_alloc + prepared_buffers + static_streambuf + static_streambuf_n + streambuf + streambuf_readstream + + + + Functions + + bind_handler + buffer_cat + prepare_buffer + prepare_buffers + write + + + + Type Traits + + is_AsyncReadStream + is_AsyncWriteStream + is_BufferSequence + is_ConstBufferSequence + is_Handler + is_MutableBufferSequence + is_Stream + is_Streambuf + is_SyncReadStream + is_SyncWriteStream + + + + Concepts + + BufferSequence + Stream + Streambuf + + + + + + diff --git a/doc/reference.xsl b/doc/reference.xsl new file mode 100644 index 00000000..51d64ee2 --- /dev/null +++ b/doc/reference.xsl @@ -0,0 +1,1639 @@ + + + + + + + + + + + + + + + + + +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:ref Reference] + + + + + + + + + + + + + + + + + + + [endsect] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``[link beast.ref.ConstBufferSequence ['ConstBufferSequence]]`` + + + ``['implementation-defined]`` + + + ``[link beast.ref.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]`` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [heading + + ] + + + + + [@ + + + + ] + + + + + + + + + + + + + `` + + + + + + `` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + + ` + + + + ` + + ` + + + + * + + + + + + + +[*] + +['] + + + + + [heading Parameters] + + + [heading Exceptions] + + + [variablelist + + ] + + + + [[ + + ][ + + ]] + + + + + + + + [heading Return Value] + + + + + + [heading Remarks] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \_ + + + + + + + + + + + + + + + + + \[ + + + + + + + \] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + [heading Requirements] + ['Header: ][^ + + ] + + + ['Convenience header: ][^beast/http.hpp] + + + ['Convenience header: ][^beast/websocket.hpp] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + + ``` + + + + + + : + + + + + + public + + + + + + , + + + + ``` + + + + + + + + + + + + + +[endsect] + + + + + + + [heading Types] + [table [[Name][Description]] + + + [ + + + [[link beast.ref. + + . + + [* + + ]]] [ + + + + ] + + + + + + + + + + + + + + + + + + + [[link beast.ref. + + [* + + ]]] [ + + ] + + + ] + + ] + + + [heading Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Protected Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Private Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Protected Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Private Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Friends] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Related Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref.. + [*]]] + [ + + + + + + + + + ] + ] + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + :: + + ] + [indexterm2 + + .. + + ] + + ``` + + + + + ``` + + ``` + + + + explicit + friend + static + virtual + + + + + + + + + ``[link beast.ref. + + . + + .overload + + + + ]``( + + ) + + const + + ; + + ``` + + + + + [section: + + + + + overload + + + + + :: + + + ( + + of + + overloads) + + ] + + + + ['Inherited from + + + + .] + + + + [indexterm2 + + .. + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + + + + + ``` using + + = + + + + + + + + + ; ``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + static + + + + + + + + + ; ``` + + + + + + + ``` + enum + + ``` + + + + [indexterm2 + + .. + + ] + + + [heading Values] + [variablelist + + [[ + + ] [ + + ]] + + ] + + + + + + + + + + + + + + + + + + + + + + + static + virtual + + + + + + ( + + ) + const + ; + + + + + template< + + > + + + + + + + class ``[link beast.types.Body [*Body]]`` + + + class ``[link beast.types.Streambuf [*Streambuf]]`` + + + + + + + + + + + + + + + + + + + + + + = + + + + , + + + + + + + + + (& + + ) + + + + + + + && + + + + & + + + + * + + + + + + + + + + = + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + [indexterm1 + + ] + + + + + + + + + + + ``` + + + + + + + + + + + + ``[link beast.ref. + + .overload + + + + ]``( + + ); + + ``` + + + + + + + + + [section: + + + + [section: + overload + + + + + + ( + + of + + overloads) + + ] + + [indexterm1 + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + diff --git a/doc/types.qbk b/doc/types.qbk new file mode 100644 index 00000000..535a4b49 --- /dev/null +++ b/doc/types.qbk @@ -0,0 +1,446 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:types Type Requirements] + + + +[section:Body Body] + +In this table: + +* `X` is a type meeting the requirements of [*`Body`]. + +[table Body requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + The type of the `message::body` member. + If this is not movable or not copyable, the containing message + will be not movable or not copyable. + ] +] +[ + [`X:value_type{}`] + [] + [`DefaultConstructible`] +] +[ + [`Body::reader`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Reader [*`Reader`]]. + Provides an implementation to parse the body. + ] +] +[ + [`Body::writer`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Writer [*`Writer`]]. + Provides an implementation to serialize the body. + ] +] +] + +[endsect] + + + +[section:BufferSequence BufferSequence] + +A `BufferSequence` meets [*one of] the following requirements: + +* `ConstBufferSequence` +* `MutableBufferSequence` + +[endsect] + + + +[section:Field Field] + +A [*`Field`] represents a single HTTP header field/value pair. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Field`]. +* `a` denotes a value of type `X`. + +[table Field requirements + +[[operation][type][semantics, pre/post-conditions]] +[ + [`a.name()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the case-insensitve field + name, without leading or trailing white space. + ] +] +[ + [`a.value()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the value for the field. The + value is considered canonical if there is no leading or + trailing whitespace. + ] +] +] + +[endsect] + + + +[section:FieldSequence FieldSequence] + +A [*`FieldSequence`] is an iterable container whose value type meets +the requirements of [link beast.types.Field [*`Field`]]. + +In this table: + +* `X` denotes a type that meets the requirements of [*`FieldSequence`]. + +* `a` is a value of type `X`. + +[table FieldSequence requirements +[[operation][type][semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + A type that meets the requirements of `Field`. + ] +] +[ + [`X::const_iterator`] + [] + [ + A type that meets the requirements of `ForwardIterator`. + ] +] +[ + [`a.begin()`] + [`X::const_iterator`] + [ + Returns an iterator to the beginning of the field sequence. + ] +] +[ + [`a.end()`] + [`X::const_iterator`] + [ + Returns an iterator to the end of the field sequence. + ] +] +] + +[endsect] + + + +[section:Reader Reader] + +Parser implementations will construct the corresponding `reader` object +during the parse. This customization point allows the Body to determine +the strategy for storing incoming message body data. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Reader`]. + +* `a` denotes a value of type `X`. + +* `p` is any pointer. + +* `n` is a value convertible to `std::size_t`. + +* `ec` is a value of type `error_code&`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true` + + +[table Reader requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X a(m);`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is + guaranteed to end no earlier than after `a` is destroyed. + ] +] +[ + [`a.write(p, n, ec)`] + [`void`] + [ + Deserializes the input sequence into the body. + If `ec` is set, the deserialization is aborted and the error + is returned to the caller. + ] +] +] + +[note Definitions for required `Reader` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +[endsect] + + + +[section:Writer Writer] + +A `Writer` serializes the message body. The implementation creates an instance +of this type when serializing a message, and calls into it zero or more times +to provide buffers containing the data. The interface of `Writer` is intended +to allow serialization in these scenarios: + +* A body that does not entirely fit in memory. +* A body produced incrementally from coroutine output. +* A body represented by zero or more buffers already in memory. +* A body as a series of buffers when the content size is not known ahead of time. +* Body data generated on demand from other threads. +* Body data computed algorithmically. + +In this table: + +* `X` denotes a type meeting the requirements of `Writer`. + +* `a` denotes a value of type `X`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true`. + +* `rc` is an object of type [link beast.reference.http__resume_context resume_context]. + +* `ec` is a value of type `error_code&`. + +* `wf` is a [*write function]: a function object of unspecified type provided + by the implementation which accepts any value meeting the requirements + of `ConstBufferSequence` as its single parameter. + +[table Writer requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X a(m);`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is + guaranteed to end no earlier than after `a` is destroyed. + ] +] +[ + [`a.init(ec)`] + [`void`] + [ + Called immediately after construction. + If `ec` is set, the serialization is aborted and the error + is propagated to the caller. + ] +] +[ + [`a.content_length()`] + [`std::size_t`] + [ + If this member is present, it is called after initialization + and before calls to provide buffers. The serialized message will + have the Content-Length field set to the value returned from + this function. If this member is absent, the serialized message + body will be chunk-encoded for HTTP versions 1.1 and later, else + the serialized message body will be sent unmodified, with the + error `boost::asio::error::eof` returned to the caller, to notify + they should close the connection to indicate the end of the message. + ] +] +[ + [`a(rc, ec, wf)`] + [`boost::tribool`] + [ + Called repeatedly after `init` succeeds. + `wf` is a function object which takes as its single parameter, + any value meeting the requirements of `ConstBufferSequence`. + Buffers provided by the `writer` to this [*write function] must + remain valid until the next member function of `writer` is + invoked (which may be the destructor). This function returns `true` + to indicate all message body data has been written, or `false` + if there is more body data. If the return value is + `boost::indeterminate`, the implementation will suspend the operation + until the writer invokes `rc`. It is the writers responsibility when + returning `boost::indeterminate`, to acquire ownership of the + `resume_context` via move construction and eventually call it or else + undefined behavior results. + ] +] +] + +[note Definitions for required `Writer` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +Exemplar: +``` +struct writer +{ +public: + /** Construct the writer. + + The msg object is guaranteed to exist for the lifetime of the writer. + + Exceptions: + No-throw guarantee. + + @param msg The message whose body is to be written. + */ + template + explicit + writer(message const& msg); + + /** Initialize the writer. + + Called once immediately after construction. + The writer can perform initialization which may fail. + + @param ec Contains the error code if any errors occur. + */ + void + init(error_code& ec); + + /** Returns the content length. + + If this member is present, the implementation will set the + Content-Length field accordingly. If absent, the implementation will + use chunk-encoding or terminate the connection to indicate the end + of the message. + */ + std::size_t + content_length() const; + + /** Write zero or one buffer representing the message body. + + Postconditions: + + If return value is `true`: + * Callee does not take ownership of resume. + * Callee made zero or one calls to `write`. + * There is no more data remaining to write. + + If return value is `false`: + * Callee does not take ownership of resume. + * Callee made one call to `write`. + + If return value is boost::indeterminate: + * Callee takes ownership of `resume`. + * Caller suspends the write operation + until `resume` is invoked. + + When the caller takes ownership of resume, the + asynchronous operation will not complete until the + caller destroys the object. + + @param resume A functor to call to resume the write operation + after the writer has returned boost::indeterminate. + + @param ec Set to indicate an error. This will cause an + asynchronous write operation to complete with the error. + + @param write A functor the writer will call to provide the next + set of buffers. Ownership of the buffers is not transferred, + the writer must guarantee that the buffers remain valid until the + next member function is invoked, which may be the destructor. + + @return `true` if there is data, `false` when done, + boost::indeterminate to suspend. + + @note Undefined behavior if the callee takes ownership + of resume but does not return boost::indeterminate. + */ + template + boost::tribool + operator()(resume_context&&, error_code&, WriteFunction&& write); +}; +``` + +[endsect] + + + +[section:Stream Stream] + +A `Stream` meets the following requirements: + +* `SyncReadStream` +* `SyncWriteStream` +* `AsyncReadStream` +* `AsyncWriteStream` + +[endsect] + + + +[section:Streambuf Streambuf] + +In the table below, `X` denotes a class, `a` denotes a value +of type `X`, `n` denotes a value convertible to `std::size_t`, +and `U` and `T` denote unspecified types. + +[table Streambuf requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [`T`] + [`T` meets the requirements for `ConstBufferSequence`.] +] +[ + [`X::mutable_buffers_type`] + [`U`] + [`U` meets the requirements for `MutableBufferSequence`.] +] +[ + [`a.commit(n)`] + [`void`] + [Moves bytes from the output sequence to the input sequence.] +] +[ + [`a.consume(n)`] + [`void`] + [Removes bytes from the input sequence.] +] +[ + [`a.data()`] + [`T`] + [Returns a list of buffers that represents the input sequence.] +] +[ + [`a.prepare(n)`] + [`U`] + [Returns a list of buffers that represents the output sequence, with + the given size.] +] +[ + [`a.size()`] + [`std::size_t`] + [Returns the size of the input sequence.] +] +[ + [`a.max_size()`] + [`std::size_t`] + [Returns the maximum size of the `Streambuf`.] +] +] + +[endsect] + + + +[endsect] diff --git a/doc/websocket.qbk b/doc/websocket.qbk new file mode 100644 index 00000000..80eb0b58 --- /dev/null +++ b/doc/websocket.qbk @@ -0,0 +1,395 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:websocket WebSocket] + +The WebSocket Protocol enables two-way communication between a client +running untrusted code in a controlled environment to a remote host that has +opted-in to communications from that code. The protocol consists of an opening +handshake followed by basic message framing, layered over TCP. The goal of +this technology is to provide a mechanism for browser-based applications that +need two-way communication with servers that does not rely on opening multiple +HTTP connections. + +Beast.WebSocket provides developers with a robust WebSocket implementation +built on Boost.Asio with a consistent asynchronous model using a modern +C++ approach. + +The WebSocket protocol is described fully in +[@https://tools.ietf.org/html/rfc6455 rfc6455] + + + +[section:motivation Motivation] + +Today's web applications increasingly rely on alternatives to standard HTTP +to achieve performance and/or responsiveness. While WebSocket implementations +are widely available in common web development languages such as Javascript, +good implementations in C++ are scarce. A survey of existing C++ WebSocket +solutions reveals interfaces which lack symmetry, impose performance penalties, +and needlessly restrict implementation strategies. + +Beast.WebSocket is built on Boost.Asio, a robust cross platform networking +framework that is part of Boost and also offered as a standalone library. +A proposal to add networking functionality to the C++ standard library, +based on Boost.Asio, is under consideration by the standards committee. +Since the final approved networking interface for the C++ standard library +will likely closely resemble the current interface of Boost.Asio, it is +logical for Beast.WebSocket to use Boost.Asio as its network transport. + +Beast.WebSocket takes advantage of Boost.Asio's extensible asynchronous +model, handler allocation, and handler invocation hooks. Calls to +Beast.WebSocket asynchronous initiation functions allow callers the choice +of using a completion handler, stackful or stackless coroutines, futures, +or user defined customizations (for example, Boost.Fiber). The +implementation uses handler invocation hooks (`asio_handler_invoke`), +providing execution guarantees on composed operations in a manner +identical to Boost.Asio. The implementation also uses handler allocation +hooks (`asio_handler_allocate`) when allocating memory internally for +composed operations. + +There is no need for inheritance or virtual members in `websocket::stream`. +All operations are templated and transparent to the compiler, allowing for +maximum inlining and optimization. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the WebSocket protocol specification described in +[@https://tools.ietf.org/html/rfc6455 rfc6455] ] + +[endsect] + + + +[section:creating Creating the socket] + +The interface to Beast's WebSocket implementation is a single template +class [link beast.ref.websocket__stream `websocket::stream`] which wraps a +"next layer" object. The next layer object must meet the requirements of +`SyncReadStream` and `SyncWriteStream` if synchronous operations are performed, +`AsyncReadStream` and `AsyncWriteStream` is asynchronous operations are +performed, or both. Arguments supplied during construction are passed to +next layer's constructor. Here we declare two websockets which have ownership +of the next layer: +``` +io_service ios; +websocket::stream ws(ios); + +ssl::context ctx(ssl::context::sslv23); +websocket::stream> wss(ios, ctx); +``` + +For servers that can handshake in multiple protocols, it may be desired +to wrap an object that already exists. This socket can be moved in: +``` + tcp::socket&& sock; + ... + websocket::stream ws(std::move(sock)); +``` + +Or, the wrapper can be constructed with a non-owning reference. In +this case, the caller is responsible for managing the lifetime of the +underlying socket being wrapped: +``` + tcp::socket sock; + ... + websocket::stream ws(sock); +``` + +The layer being wrapped can be accessed through the websocket's "next layer", +permitting callers to interact directly with its interface. +``` + ssl::context ctx(ssl::context::sslv23); + websocket::stream> ws(ios, ctx); + ... + ws.next_layer().shutdown(); // ssl::stream shutdown +``` + +[important Initiating read and write operations on the next layer while +websocket operations are being performed can break invariants, and +result in undefined behavior. ] + +[endsect] + + + +[section:connecting Making connections] + +Connections are established by using the interfaces which already exist +for the next layer. For example, making an outgoing connection: +``` + std::string const host = "mywebapp.com"; + io_service ios; + tcp::resolver r(ios); + websocket::stream ws(ios); + connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"})); +``` + +Accepting an incoming connection: +``` +void do_accept(tcp::acceptor& acceptor) +{ + websocket::stream ws(acceptor.get_io_service()); + acceptor.accept(ws.next_layer()); +} +``` + +[note Examples use synchronous interfaces for clarity of exposition. ] + +[endsect] + + + +[section:handshaking Handshaking] + +A WebSocket session begins when one side sends the HTTP Upgrade request +for websocket, and the other side sends an appropriate HTTP response +indicating that the request was accepted and that the connection has +been upgraded. The HTTP Upgrade request must include the Host HTTP field, +and the URI of the resource to request. `hanshake` is used to send the +request with the required host and resource strings. +``` + websocket::stream ws(ios); + ... + ws.set_option(websocket::keep_alive(true)); + ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices"); +``` + +The `websocket::stream` automatically handles receiving and processing +the HTTP response to the handshake request. The call to handshake is +successful if a HTTP response is received with the 101 "Switching Protocols" +status code. On failure, an error is returned or an exception is thrown. +Depending on the keep alive setting, the socket may remain open for a +subsequent handshake attempt + +Performing a handshake for an incoming websocket upgrade request operates +similarly. If the handshake fails, an error is returned or exception thrown: +``` + websocket::stream ws(ios); + ... + ws.accept(); +``` + +Servers that can handshake in multiple protocols may have already read data +on the connection, or might have already received an entire HTTP request +containing the upgrade request. Overloads of `accept` allow callers to +pass in this additional buffered handshake data. +``` +void do_accept(tcp::socket& sock) +{ + boost::asio::streambuf sb; + read_until(sock, sb, "\r\n\r\n"); + ... + websocket::stream ws(sock); + ws.accept(sb.data()); + ... +} +``` + +Alternatively, the caller can pass an entire HTTP request if it was +obtained elsewhere: +``` +void do_accept(tcp::socket& sock) +{ + boost::asio::streambuf sb; + http::request request; + http::read(sock, request); + if(http::is_upgrade(request)) + { + websocket::stream ws(sock); + ws.accept(request); + ... + } +} +``` + +[note Identifiers in the `http` namespace are part of Beast.HTTP. ] + +[endsect] + + + +[section:messages Messages] + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface. This interface requires that +all of the buffers representing the message are known ahead of time: +``` +void echo(websocket::stream& ws) +{ + streambuf sb; + websocket::opcode::value op; + ws.read(sb); + + ws.set_option(websocket::message_type(op)); + websocket::write(ws, sb.data()); + sb.consume(sb.size()); +} +``` + +[important Calls to `set_option` must be made from the same implicit +or explicit strand as that used to perform other operations. ] + +[endsect] + + + +[section:frames Frames] + +Some use-cases make it impractical or impossible to buffer the entire +message ahead of time: + +* Streaming multimedia to an endpoint. +* Sending a message that does not fit in memory at once. +* Providing incremental results as they become available. + +For these cases, the frame oriented interface may be used. This +example reads and echoes a complete message using this interface: +``` +void echo(websocket::stream& ws) +{ + streambuf sb; + websocket::frame_info fi; + for(;;) + { + ws.read_frame(fi, sb); + if(fi.fin) + break; + } + ws.set_option(websocket::message_type(fi.op)); + consuming_buffers cb(sb.data()); + for(;;) + { + using boost::asio::buffer_size; + std::size_t size = std::min(buffer_size(cb)); + if(size > 512) + { + ws.write_frame(false, beast::prepare_buffers(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +} +``` + +[endsect] + + + +[section:controlframes Control frames] + +During read operations, the implementation automatically reads and processes +WebSocket control frames such as ping, pong, and close. Pings are replied +to as soon as possible, pongs are noted. The receipt of a close frame +initiates the WebSocket close procedure, eventually resulting in the +error code `websocket::error::closed` being delivered to the caller in +a subsequent read operation, assuming no other error takes place. + +To ensure timely delivery of control frames, large messages are broken up +into smaller sized frames. The implementation chooses the size and number +of the frames making up the message. The automatic fragment size option +gives callers control over the size of these frames: +``` + ... + ws.set_option(websocket::auto_fragment_size(8192)); +``` + +The WebSocket protocol defines a procedure and control message for initiating +a close of the session. Handling of close initiated by the remote end of the +connection is performed automatically. To manually initiate a close, use +`websocket::stream::close`: +``` + ws.close(); +``` + +[note To receive the `websocket::error::closed` error, a read operation +is required. ] + +[endsect] + + + +[section:buffers Buffers] + +Because calls to read data may return a variable amount of bytes, the +interface to calls that read data require an object that meets the requirements +of `Streambuf`. This concept is modeled on `boost::asio::basic_streambuf`. + +The implementation does not perform queueing or buffering of messages. If +desired, these features should be provided by callers. The impact of this +design is that library users are in full control of the allocation strategy +used to store data and the back-pressure applied on the read and write side +of the underlying TCP/IP connection. + +[endsect] + + +[section:async Asynchronous interface] + +Asynchronous versions are available for all functions: +``` +websocket::opcode op; +ws.async_read(op, sb, + [](boost::system::error_code const& ec) + { + ... + }); +``` + +Calls to asynchronous initiation functions support the extensible asynchronous +model developed by the Boost.Asio author, allowing for traditional completion +handlers, stackful or stackless coroutines, and even futures: +``` +void echo(websocket::stream& ws, + boost::asio::yield_context yield) +{ + ws.async_read(sb, yield); + std::future fut = + ws.async_write, sb.data(), boost::use_future); + ... +} +``` + +[endsect] + + + +[section:io_service io_service] + +The creation and operation of the `boost::asio::io_service` associated with +the underlying stream is left to the callers, permitting any implementation +strategy including one that does not require threads for environments where +threads are unavailable. Beast.WSProto itself does not use or require threads. + +[endsect] + + + +[section:safety Thread Safety] + +Like a regular asio socket, a `websocket::stream` is not thread safe. Callers +are responsible for synchronizing operations on the socket using an implicit +or explicit strand, as per the Asio documentation. The asynchronous interface +supports one active read and one active write simultaneously. Undefined +behavior results if two or more reads or two or more writes are attempted +concurrently. Caller initiated WebSocket ping, pong, and close operations +each count as an active write. + +The implementation uses composed asynchronous operations internally; a high +level read can cause both reads and writes to take place on the underlying +stream. This behavior is transparent to callers. + +[endsect] + + + +[endsect] + +[include quickref.xml] diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..5b193b44 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,30 @@ +# Part of Beast + +GroupSources(include/beast) +GroupSources(examples) + +add_executable (http-crawl + ${BEAST_INCLUDES} + http_crawl.cpp + urls_large_data.cpp +) + +add_executable (http-server + ${BEAST_INCLUDES} + http_server.cpp +) + +add_executable (http-example + ${BEAST_INCLUDES} + http_example.cpp +) + +add_executable (websocket-echo + ${BEAST_INCLUDES} + websocket_echo.cpp +) + +add_executable (websocket-example + ${BEAST_INCLUDES} + websocket_example.cpp +) diff --git a/examples/Jamfile b/examples/Jamfile new file mode 100644 index 00000000..fade6b0b --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,30 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +exe http_crawl : + http_crawl.cpp + urls_large_data.cpp + ; + +exe http_server : + http_server.cpp + ; + +exe http_example : + http_example.cpp + ; + +exe websocket_echo : + websocket_echo.cpp + ; + +exe websocket_example : + websocket_example.cpp + ; + diff --git a/examples/file_body.h b/examples/file_body.h new file mode 100644 index 00000000..d6719d5a --- /dev/null +++ b/examples/file_body.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_EXAMPLE_FILE_BODY_H_INCLUDED +#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +struct file_body +{ + using value_type = std::string; + + class writer + { + std::size_t size_; + std::size_t offset_ = 0; + std::string const& path_; + FILE* file_ = nullptr; + char buf_[4096]; + std::size_t buf_len_; + + public: + static bool constexpr is_single_pass = false; + + template + writer(message const& m) noexcept + : path_(m.body) + { + } + + ~writer() + { + if(file_) + fclose(file_); + } + + void + init(error_code& ec) noexcept + { + file_ = fopen(path_.c_str(), "rb"); + if(! file_) + ec = boost::system::errc::make_error_code( + static_cast(errno)); + else + size_ = boost::filesystem::file_size(path_); + } + + std::size_t + content_length() const + { + return size_; + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + buf_len_ = std::min(size_ - offset_, sizeof(buf_)); + auto const nread = fread(buf_, 1, sizeof(buf_), file_); + (void)nread; + offset_ += buf_len_; + write(boost::asio::buffer(buf_, buf_len_)); + return offset_ >= size_; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/examples/http_async_server.h b/examples/http_async_server.h new file mode 100644 index 00000000..5438ab6e --- /dev/null +++ b/examples/http_async_server.h @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED +#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED + +#include "file_body.h" +#include "http_stream.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class http_async_server +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + using req_type = request; + using resp_type = response; + + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::string root_; + std::vector thread_; + +public: + http_async_server(endpoint_type const& ep, + int threads, std::string const& root) + : sock_(ios_) + , acceptor_(ios_) + , root_(root) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + acceptor_.async_accept(sock_, + std::bind(&http_async_server::on_accept, this, + beast::asio::placeholders::error)); + thread_.reserve(threads); + for(int i = 0; i < threads; ++i) + thread_.emplace_back( + [&] { ios_.run(); }); + } + + ~http_async_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class peer : public std::enable_shared_from_this + { + int id_; + stream stream_; + boost::asio::io_service::strand strand_; + std::string root_; + req_type req_; + + public: + peer(peer&&) = default; + peer(peer const&) = default; + peer& operator=(peer&&) = delete; + peer& operator=(peer const&) = delete; + + explicit + peer(socket_type&& sock, std::string const& root) + : stream_(std::move(sock)) + , strand_(stream_.get_io_service()) + , root_(root) + { + static int n = 0; + id_ = ++n; + } + + void run() + { + do_read(); + } + + void do_read() + { + stream_.async_read(req_, strand_.wrap( + std::bind(&peer::on_read, shared_from_this(), + asio::placeholders::error))); + } + + void on_read(error_code ec) + { + if(ec) + return fail(ec, "read"); + do_read(); + auto path = req_.url; + if(path == "/") + path = "/index.html"; + path = root_ + path; + if(! boost::filesystem::exists(path)) + { + response resp( + {404, "Not Found", req_.version}); + resp.headers.replace("Server", "http_async_server"); + resp.body = "The file '" + path + "' was not found"; + stream_.async_write(std::move(resp), + std::bind(&peer::on_write, shared_from_this(), + asio::placeholders::error)); + return; + } + response resp( + {200, "OK", req_.version}); + resp.headers.replace("Server", "http_async_server"); + resp.headers.replace("Content-Type", "text/html"); + resp.body = path; + stream_.async_write(std::move(resp), + std::bind(&peer::on_write, shared_from_this(), + asio::placeholders::error)); + } + + void on_write(error_code ec) + { + if(ec) + fail(ec, "write"); + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != boost::asio::error::operation_aborted) + { + std::cerr << + "#" << std::to_string(id_) << " " << + what << ": " << ec.message() << std::endl; + } + } + }; + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + acceptor_.async_accept(sock_, + std::bind(&http_async_server::on_accept, this, + asio::placeholders::error)); + std::make_shared(std::move(sock), root_)->run(); + } +}; + +} // http +} // beast + +#endif diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp new file mode 100644 index 00000000..dd4779a1 --- /dev/null +++ b/examples/http_crawl.cpp @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "http_stream.h" +#include "urls_large_data.h" + +#include +#include + +using namespace beast::http; +using namespace boost::asio; + +template +void +err(error_code const& ec, String const& what) +{ + std::cerr << what << ": " << ec.message() << std::endl; +} + +int main(int, char const*[]) +{ + io_service ios; + for(auto const& host : urls_large_data()) + { + try + { + ip::tcp::resolver r(ios); + auto it = r.resolve( + ip::tcp::resolver::query{host, "http"}); + stream hs(ios); + connect(hs.lowest_layer(), it); + auto ep = hs.lowest_layer().remote_endpoint(); + request req({method_t::http_get, "/", 11}); + req.headers.insert("Host", host + + std::string(":") + std::to_string(ep.port())); + req.headers.insert("User-Agent", "beast/http"); + hs.write(req); + response resp; + hs.read(resp); + std::cout << resp; + } + catch(boost::system::system_error const& ec) + { + std::cerr << host << ": " << ec.what(); + } + catch(...) + { + std::cerr << host << ": unknown exception" << std::endl; + } + } +} diff --git a/examples/http_example.cpp b/examples/http_example.cpp new file mode 100644 index 00000000..b7fd3929 --- /dev/null +++ b/examples/http_example.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include + +int main() +{ + // Normal boost::asio setup + std::string const host = "boost.org"; + boost::asio::io_service ios; + boost::asio::ip::tcp::resolver r(ios); + boost::asio::ip::tcp::socket sock(ios); + boost::asio::connect(sock, + r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); + + using namespace beast::http; + + // Send HTTP request using beast + request req({method_t::http_get, "/", 11}); + req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); + req.headers.replace("User-Agent", "Beast"); + write(sock, req); + + // Receive and print HTTP response using beast + beast::streambuf sb; + response resp; + read(sock, sb, resp); + std::cout << resp; +} diff --git a/examples/http_server.cpp b/examples/http_server.cpp new file mode 100644 index 00000000..8e310239 --- /dev/null +++ b/examples/http_server.cpp @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "http_async_server.h" +#include "http_sync_server.h" +#include "sig_wait.h" + +#include + +#include + +int main(int ac, char const* av[]) +{ + using namespace beast::http; + namespace po = boost::program_options; + po::options_description desc("Options"); + + desc.add_options() + ("root,r", po::value()->implicit_value("."), + "Set the root directory for serving files") + ("port,p", po::value()->implicit_value(8080), + "Set the port number for the server") + ("ip", po::value()->implicit_value("0.0.0.0"), + "Set the IP address to bind to, \"0.0.0.0\" for all") + ("threads,n", po::value()->implicit_value(4), + "Set the number of threads to use") + ("sync,s", "Launch a synchronous server") + ; + po::variables_map vm; + po::store(po::parse_command_line(ac, av, desc), vm); + + std::string root = "."; + if(vm.count("root")) + root = vm["root"].as(); + + std::uint16_t port = 8080; + if(vm.count("port")) + port = vm["port"].as(); + + std::string ip = "0.0.0.0"; + if(vm.count("ip")) + ip = vm["ip"].as(); + + std::size_t threads = 4; + if(vm.count("threads")) + threads = vm["threads"].as(); + + bool sync = vm.count("sync") > 0; + + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + endpoint_type ep{address_type::from_string(ip), port}; + + if(sync) + { + http_sync_server server(ep, root); + sig_wait(); + } + else + { + http_async_server server(ep, threads, root); + sig_wait(); + } +} diff --git a/examples/http_stream.h b/examples/http_stream.h new file mode 100644 index 00000000..cb3bdb6b --- /dev/null +++ b/examples/http_stream.h @@ -0,0 +1,492 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HTTP_STREAM_H_INCLUDED +#define BEAST_HTTP_STREAM_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +class stream_base +{ +protected: + struct op + : boost::intrusive::list_base_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + { + virtual ~op() = default; + virtual void operator()() = 0; + virtual void cancel() = 0; + }; + + using op_list = typename boost::intrusive::make_list< + op, boost::intrusive::constant_time_size>::type; + + op_list wr_q_; + bool wr_active_ = false; +}; + +} // detail + +/** Provides message-oriented functionality using HTTP. + + The stream class template provides asynchronous and blocking + message-oriented functionality necessary for clients and servers + to utilize the HTTP protocol. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. The application must ensure that + all asynchronous operations are performed within the same + implicit or explicit strand. + + @par Example + + To use the class template with an `ip::tcp::socket`, you would write: + + @code + http::stream hs(io_service); + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock(io_service); + http::stream hs(sock); + @endcode + + @note A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + + @par Concepts + AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. + */ +template> +class stream : public detail::stream_base +{ + NextLayer next_layer_; + basic_streambuf rd_buf_; + +public: + /// The type of the next layer. + using next_layer_type = + typename std::remove_reference::type; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// The type of endpoint of the lowest layer. + using endpoint_type = + typename lowest_layer_type::endpoint_type; + + /// The protocol of the next layer. + using protocol_type = + typename lowest_layer_type::protocol_type; + + /// The type of resolver of the next layer. + using resolver_type = + typename protocol_type::resolver; + + /** Destructor. + + @note A stream object must not be destroyed while there + are pending asynchronous operations associated with it. + */ + ~stream(); + + /** Move constructor. + + Undefined behavior if operations are active or pending. + */ + stream(stream&&) = default; + + /** Move assignment. + + Undefined behavior if operations are active or pending. + */ + stream& operator=(stream&&) = default; + + /** Construct a HTTP stream. + + This constructor creates a HTTP stream and initialises + the next layer. + + @throws Any exceptions thrown by the Stream constructor. + + @param args The arguments to be passed to initialise the + next layer. The arguments are forwarded to the next layer's + constructor. + */ + template + explicit + stream(Args&&... args); + + /** Get the io_service associated with the stream. + + This function may be used to obtain the io_service object + that the stream uses to dispatch handlers for asynchronous + operations. + + @return A reference to the io_service object that the stream + will use to dispatch handlers. Ownership is not transferred + to the caller. + */ + boost::asio::io_service& + get_io_service() + { + return next_layer_.lowest_layer().get_io_service(); + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type& + next_layer() + { + return next_layer_; + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type const& + next_layer() const + { + return next_layer_; + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /** Cancel pending operations. + + This will cancel all of the asynchronous operations pending, + including pipelined writes that have not been started. Handlers for + canceled writes will be called with + `boost::asio::error::operation_aborted`. + + @throws boost::system::system_error Thrown on failure. + */ + void + cancel() + { + error_code ec; + cancel(ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Cancel pending operations. + + This will cancel all of the asynchronous operations pending, + including pipelined writes that have not been started. Handlers for + canceled writes will be called with + `boost::asio::error::operation_aborted`. + + @param ec Set to indicate what error occurred, if any. + */ + void + cancel(error_code& ec); + + /** Read a HTTP message from the stream. + + This function is used to read a single HTTP message from the stream. + The call will block until one of the followign conditions is true: + + @li A message has been read. + + @li An error occurred. + + The operation is implemented in terms of zero or more calls to the + next layer's `read_some` function. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read(message& msg) + { + error_code ec; + read(msg, ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Read a HTTP message from the stream. + + This function is used to read a single HTTP message from the stream. + The call will block until one of the followign conditions is true: + + @li A message has been read. + + @li An error occurred. + + The operation is implemented in terms of zero or more calls to the + next layer's `read_some` function. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read(message& msg, + error_code& ec); + + /** Start reading a HTTP message from the stream asynchronously. + + This function is used to asynchronously read a single HTTP message + from the stream. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The message has been written. + + @li An error occurred. + + This operation is implemented in terms of zero or more calls to the + next layer's async_read_some function, and is known as a composed + operation. The program must ensure that the stream performs no other + read operations or any other composed operations that perform reads + until this operation completes. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. Ownership of the message + is not transferred; the caller must guarantee that the object remains + valid until the handler is called. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + ReadHandler, void(error_code)>::result_type + #endif + async_read(message& msg, + ReadHandler&& handler); + + /** Write a HTTP message to the stream. + + This function is used to write a single HTTP message to the + stream. The call will block until one of the following conditions + is true: + + @li The entire message is sent. + + @li An error occurred. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, + `boost::asio::error::eof` is thrown after the message is sent. + successfuly. The caller is responsible for actually closing the + connection. For regular TCP/IP streams this means shutting down the + send side, while SSL streams may call the SSL shutdown function. + + @param msg The message to send. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + write(message const& msg) + { + error_code ec; + write(msg, ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Write a HTTP message to the stream. + + This function is used to write a single HTTP message to the + stream. The call will block until one of the following conditions + is true: + + @li The entire message is sent. + + @li An error occurred. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, + `boost::asio::error::eof` is returned after the message is sent. + successfuly. The caller is responsible for actually closing the + connection. For regular TCP/IP streams this means shutting down the + send side, while SSL streams may call the SSL shutdown function. + + @param msg The message to send. + + @param ec Set to the error, if any occurred. + */ + template + void + write(message const& msg, + error_code& ec); + + /** Start pipelining a HTTP message to the stream asynchronously. + + This function is used to queue a message to be sent on the stream. + Unlike the free function, this version will place the message on an + outgoing message queue if there is already a write pending. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, the handler + is called with the error `boost::asio::error::eof` after the message + has been sent successfully. The caller is responsible for actually + closing the connection. For regular TCP/IP streams this means + shutting down the send side, while SSL streams may call the SSL + `async_shutdown` function. + + @param msg The message to send. A copy of the message will be made. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + WriteHandler, void(error_code)>::result_type + #endif + async_write(message const& msg, + WriteHandler&& handler); + + /** Start pipelining a HTTP message to the stream asynchronously. + + This function is used to queue a message to be sent on the stream. + Unlike the free function, this version will place the message on an + outgoing message queue if there is already a write pending. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, the handler + is called with the error boost::asio::error::eof. The caller is + responsible for actually closing the connection. For regular + TCP/IP streams this means shutting down the send side, while SSL + streams may call the SSL async_shutdown function. + + @param msg The message to send. Ownership of the message, which + must be movable, is transferred to the implementation. The message + will not be destroyed until the asynchronous operation completes. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + WriteHandler, void(error_code)>::result_type + #endif + async_write(message&& msg, + WriteHandler&& handler); + +private: + template class read_op; + template class write_op; + + void + cancel_all(); +}; + +} // http +} // beast + +#include "http_stream.ipp" + +#endif diff --git a/examples/http_stream.ipp b/examples/http_stream.ipp new file mode 100644 index 00000000..6a952b41 --- /dev/null +++ b/examples/http_stream.ipp @@ -0,0 +1,423 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HTTP_STREAM_IPP_INCLUDED +#define BEAST_HTTP_STREAM_IPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +template +class stream::read_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& s; + message& m; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& s_, + message& m_) + : s(s_) + , m(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, + stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()(error_code const& ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +read_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + beast::http::async_read(d.s.next_layer_, + d.s.rd_buf_, d.m, std::move(*this)); + return; + } + } + d.h(ec); +} + +//------------------------------------------------------------------------------ + +template +template +class stream::write_op : public op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& s; + message m; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& s_, + message const& m_, + bool cont_) + : s(s_) + , m(m_) + , h(std::forward(h_)) + , cont(cont_) + { + } + + template + data(DeducedHandler&& h_, stream& s_, + message&& m_, + bool cont_) + : s(s_) + , m(std::move(m_)) + , h(std::forward(h_)) + , cont(cont_) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, + stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + } + + void + operator()() override + { + (*this)(error_code{}, false); + } + + void cancel() override; + + void operator()(error_code const& ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +write_op:: +cancel() +{ + auto& d = *d_; + d.s.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); +} + +template +template +void +stream:: +write_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + beast::http::async_write(d.s.next_layer_, + d.m, std::move(*this)); + return; + } + } + d.h(ec); + if(! d.s.wr_q_.empty()) + { + auto& op = d.s.wr_q_.front(); + op(); + // VFALCO Use allocator + delete &op; + d.s.wr_q_.pop_front(); + } + else + { + d.s.wr_active_ = false; + } +} + +//------------------------------------------------------------------------------ + +template +stream:: +~stream() +{ + // Can't destroy with pending operations! + assert(wr_q_.empty()); +} + +template +template +stream:: +stream(Args&&... args) + : next_layer_(std::forward(args)...) +{ +} + +template +void +stream:: +cancel(error_code& ec) +{ + cancel_all(); + lowest_layer().cancel(ec); +} + +template +template +void +stream:: +read(message& msg, + error_code& ec) +{ + beast::http::read(next_layer_, rd_buf_, msg, ec); +} + +template +template +auto +stream:: +async_read(message& msg, + ReadHandler&& handler) -> + typename async_completion< + ReadHandler, void(error_code)>::result_type +{ + async_completion< + ReadHandler, void(error_code) + > completion(handler); + read_op{ + completion.handler, *this, msg}; + return completion.result.get(); +} + +template +template +void +stream:: +write(message const& msg, + error_code& ec) +{ + beast::http::write(next_layer_, msg, ec); +} + +template +template +auto +stream:: +async_write(message const& msg, + WriteHandler&& handler) -> + typename async_completion< + WriteHandler, void(error_code)>::result_type +{ + async_completion< + WriteHandler, void(error_code)> completion(handler); + auto const cont = wr_active_ || + boost_asio_handler_cont_helpers::is_continuation(handler); + if(! wr_active_) + { + wr_active_ = true; + write_op{ + completion.handler, *this, msg, cont }(); + } + else + { + // VFALCO Use allocator + wr_q_.push_back(*new write_op( + completion.handler, *this, msg, cont)); + } + return completion.result.get(); +} + +template +template +auto +stream:: +async_write(message&& msg, + WriteHandler&& handler) -> + typename async_completion< + WriteHandler, void(error_code)>::result_type +{ + async_completion< + WriteHandler, void(error_code)> completion(handler); + auto const cont = wr_active_ || + boost_asio_handler_cont_helpers::is_continuation(handler); + if(! wr_active_) + { + wr_active_ = true; + write_op{completion.handler, + *this, std::move(msg), cont}(); + } + else + { + // VFALCO Use allocator + wr_q_.push_back(*new write_op(completion.handler, + *this, std::move(msg), cont)); + } + return completion.result.get(); +} + +template +void +stream:: +cancel_all() +{ + for(auto it = wr_q_.begin(); it != wr_q_.end();) + { + auto& op = *it++; + op.cancel(); + // VFALCO Use allocator + delete &op; + } + wr_q_.clear(); +} + +} // http +} // beast + +#endif diff --git a/examples/http_sync_server.h b/examples/http_sync_server.h new file mode 100644 index 00000000..df6e4239 --- /dev/null +++ b/examples/http_sync_server.h @@ -0,0 +1,182 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED +#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED + +#include "file_body.h" +#include "http_stream.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace beast { +namespace http { + +class http_sync_server +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + using req_type = request; + using resp_type = response; + + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::string root_; + std::thread thread_; + +public: + http_sync_server(endpoint_type const& ep, + std::string const& root) + : sock_(ios_) + , acceptor_(ios_) + , root_(root) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + acceptor_.async_accept(sock_, + std::bind(&http_sync_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + + ~http_sync_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + struct lambda + { + int id; + http_sync_server& self; + socket_type sock; + boost::asio::io_service::work work; + + lambda(int id_, http_sync_server& self_, + socket_type&& sock_) + : id(id_) + , self(self_) + , sock(std::move(sock_)) + , work(sock.get_io_service()) + { + } + + void operator()() + { + self.do_peer(id, std::move(sock)); + } + }; + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + static int id_ = 0; + std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); + acceptor_.async_accept(sock_, + std::bind(&http_sync_server::on_accept, this, + asio::placeholders::error)); + } + + void + fail(int id, error_code const& ec) + { + if(ec != boost::asio::error::operation_aborted && + ec != boost::asio::error::eof) + std::cerr << + "#" << std::to_string(id) << " " << std::endl; + } + + void + do_peer(int id, socket_type&& sock) + { + http::stream hs(std::move(sock)); + error_code ec; + for(;;) + { + req_type req; + hs.read(req, ec); + if(ec) + break; + auto path = req.url; + if(path == "/") + path = "/index.html"; + path = root_ + path; + if(! boost::filesystem::exists(path)) + { + response resp( + {404, "Not Found", req.version}); + resp.headers.replace("Server", "http_sync_server"); + resp.body = "The file '" + path + "' was not found"; + hs.write(resp, ec); + if(ec) + break; + } + response resp( + {200, "OK", req.version}); + resp.headers.replace("Server", "http_sync_server"); + resp.headers.replace("Content-Type", "text/html"); + resp.body = path; + hs.write(resp, ec); + if(ec) + break; + } + fail(id, ec); + } +}; + +} // http +} // beast + +#endif diff --git a/examples/sig_wait.h b/examples/sig_wait.h new file mode 100644 index 00000000..34ff1efd --- /dev/null +++ b/examples/sig_wait.h @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_EXAMPLE_SIG_WAIT_H_INCLUDED +#define BEAST_EXAMPLE_SIG_WAIT_H_INCLUDED + +#include +#include +#include + +// Block until SIGINT or SIGTERM +inline +void +sig_wait() +{ + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + signals.async_wait( + [&](boost::system::error_code const&, int) + { + }); + ios.run(); +} + +#endif diff --git a/examples/urls_large_data.cpp b/examples/urls_large_data.cpp new file mode 100644 index 00000000..782d0ca2 --- /dev/null +++ b/examples/urls_large_data.cpp @@ -0,0 +1,10031 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "urls_large_data.h" + +// Data from Alexa top 1 million sites +// http://s3.amazonaws.com/alexa-static/top-1m.csv.zip +// +std::vector const& +urls_large_data() +{ + static std::vector const urls ({ + "google.com", + "facebook.com", + "youtube.com", + "yahoo.com", + "baidu.com", + "wikipedia.org", + "qq.com", + "linkedin.com", + "taobao.com", + "twitter.com", + "live.com", + "amazon.com", + "sina.com.cn", + "google.co.in", + "hao123.com", + "blogspot.com", + "weibo.com", + "wordpress.com", + "yandex.ru", + "yahoo.co.jp", + "tmall.com", + "bing.com", + "vk.com", + "ebay.com", + "360.cn", + "google.de", + "sohu.com", + "pinterest.com", + "google.co.uk", + "ask.com", + "google.fr", + "msn.com", + "163.com", + "soso.com", + "tumblr.com", + "google.co.jp", + "instagram.com", + "mail.ru", + "google.com.br", + "microsoft.com", + "paypal.com", + "google.ru", + "xvideos.com", + "google.es", + "imdb.com", + "apple.com", + "google.it", + "adcash.com", + "craigslist.org", + "stackoverflow.com", + "amazon.co.jp", + "neobux.com", + "google.com.hk", + "imgur.com", + "ifeng.com", + "cnn.com", + "google.com.mx", + "xhamster.com", + "xinhuanet.com", + "gmw.cn", + "reddit.com", + "bbc.co.uk", + "blogger.com", + "google.ca", + "fc2.com", + "vube.com", + "go.com", + "akamaihd.net", + "alipay.com", + "about.com", + "people.com.cn", + "t.co", + "googleusercontent.com", + "wordpress.org", + "odnoklassniki.ru", + "alibaba.com", + "google.com.tr", + "aliexpress.com", + "youku.com", + "conduit.com", + "huffingtonpost.com", + "godaddy.com", + "flickr.com", + "pornhub.com", + "google.com.au", + "amazon.de", + "blogspot.in", + "kickass.to", + "ebay.de", + "netflix.com", + "google.pl", + "thepiratebay.se", + "bp.blogspot.com", + "adobe.com", + "dailymotion.com", + "china.com", + "vimeo.com", + "dailymail.co.uk", + "cnet.com", + "rakuten.co.jp", + "espn.go.com", + "ku6.com", + "ebay.co.uk", + "themeforest.net", + "xnxx.com", + "livejasmin.com", + "indiatimes.com", + "aol.com", + "redtube.com", + "dropbox.com", + "amazonaws.com", + "uol.com.br", + "weather.com", + "m2newmedia.com", + "amazon.co.uk", + "google.com.ar", + "google.com.sa", + "pixnet.net", + "nytimes.com", + "slideshare.net", + "youporn.com", + "google.com.eg", + "buzzfeed.com", + "wikimedia.org", + "booking.com", + "livejournal.com", + "globo.com", + "fiverr.com", + "adf.ly", + "secureserver.net", + "mozilla.org", + "google.com.pk", + "google.com.tw", + "google.nl", + "yelp.com", + "sogou.com", + "ameblo.jp", + "directrev.com", + "clkmon.com", + "hootsuite.com", + "deviantart.com", + "blogfa.com", + "wikia.com", + "outbrain.com", + "flipkart.com", + "wikihow.com", + "etsy.com", + "google.co.th", + "soundcloud.com", + "google.co.za", + "avg.com", + "w3schools.com", + "theguardian.com", + "stumbleupon.com", + "espncricinfo.com", + "livedoor.com", + "forbes.com", + "archive.org", + "4shared.com", + "foxnews.com", + "files.wordpress.com", + "answers.com", + "bankofamerica.com", + "chase.com", + "google.co.ve", + "mediafire.com", + "torrentz.eu", + "badoo.com", + "salesforce.com", + "aweber.com", + "sourceforge.net", + "bbc.com", + "addthis.com", + "liveinternet.ru", + "china.com.cn", + "indeed.com", + "reference.com", + "github.com", + "skype.com", + "hostgator.com", + "google.gr", + "bet365.com", + "spiegel.de", + "ask.fm", + "so.com", + "statcounter.com", + "gameforge.com", + "onet.pl", + "naver.com", + "google.com.co", + "developunit.info", + "google.com.vn", + "google.co.id", + "nicovideo.jp", + "shutterstock.com", + "walmart.com", + "google.be", + "mailchimp.com", + "softonic.com", + "stackexchange.com", + "google.com.ng", + "google.com.ua", + "popads.net", + "allegro.pl", + "gamer.com.tw", + "wordreference.com", + "wellsfargo.com", + "loading-delivery1.com", + "zillow.com", + "tripadvisor.com", + "quikr.com", + "pandora.com", + "wsj.com", + "goo.ne.jp", + "bild.de", + "tube8.com", + "wix.com", + "google.se", + "coccoc.com", + "google.ro", + "suning.com", + "photobucket.com", + "weebly.com", + "tianya.cn", + "warriorforum.com", + "telegraph.co.uk", + "google.dz", + "naver.jp", + "php.net", + "ups.com", + "rambler.ru", + "zedo.com", + "reuters.com", + "google.at", + "media.tumblr.com", + "taringa.net", + "google.com.ph", + "chinaz.com", + "mashable.com", + "blogspot.com.es", + "hurriyet.com.tr", + "google.com.pe", + "bleacherreport.com", + "gmx.net", + "wp.pl", + "goodreads.com", + "leboncoin.fr", + "rutracker.org", + "lenta.ru", + "babylon.com", + "domaintools.com", + "sharelive.net", + "rediff.com", + "google.ch", + "comcast.net", + "twitch.tv", + "avito.ru", + "kaskus.co.id", + "nbcnews.com", + "onclickads.net", + "businessinsider.com", + "ikea.com", + "codecanyon.net", + "ndtv.com", + "usps.com", + "google.cl", + "doublepimp.com", + "moz.com", + "google.com.sg", + "usatoday.com", + "dmm.co.jp", + "amazon.fr", + "google.pt", + "ucoz.ru", + "rt.com", + "milliyet.com.tr", + "xuite.net", + "samsung.com", + "fedex.com", + "uploaded.net", + "pcpop.com", + "google.com.bd", + "bitly.com", + "goodgamestudios.com", + "ettoday.net", + "baomihua.com", + "39.net", + "web.de", + "rbc.ru", + "9gag.com", + "disqus.com", + "snapdeal.com", + "xcar.com.cn", + "washingtonpost.com", + "bloomberg.com", + "scribd.com", + "hp.com", + "constantcontact.com", + "4dsply.com", + "gsmarena.com", + "intuit.com", + "meetup.com", + "nih.gov", + "americanexpress.com", + "ehow.com", + "infusionsoft.com", + "google.cz", + "mercadolivre.com.br", + "myntra.com", + "varzesh3.com", + "mobile01.com", + "hardsextube.com", + "goal.com", + "thefreecamsecret.com", + "thefreedictionary.com", + "douban.com", + "iqiyi.com", + "it168.com", + "mama.cn", + "tmz.com", + "time.com", + "olx.in", + "microsoftonline.com", + "hulu.com", + "enet.com.cn", + "speedtest.net", + "orange.fr", + "free.fr", + "detik.com", + "bluehost.com", + "libero.it", + "histats.com", + "webmd.com", + "eazel.com", + "hudong.com", + "extratorrent.cc", + "google.co.kr", + "chaturbate.com", + "huanqiu.com", + "cnzz.com", + "daum.net", + "xing.com", + "force.com", + "jrj.com.cn", + "pchome.net", + "cj.com", + "youjizz.com", + "delta-search.com", + "techcrunch.com", + "motherless.com", + "tinyurl.com", + "beeg.com", + "kooora.com", + "google.cn", + "clickbank.com", + "yandex.ua", + "ad6media.fr", + "google.no", + "amazon.cn", + "zippyshare.com", + "acesse.com", + "google.co.hu", + "google.ae", + "hdfcbank.com", + "nba.com", + "zendesk.com", + "blogspot.co.uk", + "bestbuy.com", + "accuweather.com", + "getresponse.com", + "repubblica.it", + "xywy.com", + "ci123.com", + "ebay.in", + "in.com", + "ign.com", + "groupon.com", + "yesky.com", + "elpais.com", + "marca.com", + "kwejk.pl", + "caijing.com.cn", + "google.ie", + "cloudfront.net", + "intoday.in", + "wideinfo.org", + "pof.com", + "kakaku.com", + "bitauto.com", + "life.com.tw", + "xe.com", + "goo.gl", + "google.az", + "feedly.com", + "tagged.com", + "amazon.in", + "list-manage.com", + "w3.org", + "quora.com", + "ixxx.com", + "mysearchdial.com", + "dell.com", + "seznam.cz", + "t-online.de", + "plugrush.com", + "nydailynews.com", + "okcupid.com", + "istockphoto.com", + "snapdo.com", + "abcnews.go.com", + "target.com", + "latimes.com", + "mywebsearch.com", + "joomla.org", + "odesk.com", + "surveymonkey.com", + "elmundo.es", + "siteadvisor.com", + "aili.com", + "uimserv.net", + "ebay.com.au", + "doubleclick.com", + "zeobit.com", + "google.co.il", + "fbcdn.net", + "ero-advertising.com", + "issuu.com", + "naukri.com", + "gazeta.pl", + "ck101.com", + "udn.com", + "google.dk", + "ce.cn", + "capitalone.com", + "att.com", + "drudgereport.com", + "blackhatworld.com", + "adrotator.se", + "retailmenot.com", + "justdial.com", + "icicibank.com", + "likes.com", + "elance.com", + "cntv.cn", + "ameba.jp", + "java.com", + "lifehacker.com", + "jabong.com", + "gizmodo.com", + "ebay.it", + "lenovo.com", + "hypergames.net", + "google.fi", + "doorblog.jp", + "blogspot.de", + "jimdo.com", + "probux.com", + "blogspot.it", + "habrahabr.ru", + "webmoney.ru", + "irctc.co.in", + "sahibinden.com", + "github.io", + "informer.com", + "mysearchresults.com", + "upworthy.com", + "pch.com", + "eyny.com", + "canadaalltax.com", + "b5m.com", + "freelancer.com", + "match.com", + "searchfun.in", + "adnxs.com", + "sberbank.ru", + "ig.com.br", + "trulia.com", + "subscene.com", + "corriere.it", + "livescore.com", + "irs.gov", + "twoo.com", + "clixsense.com", + "4399.com", + "xda-developers.com", + "iminent.com", + "google.com.my", + "onlinesbi.com", + "empowernetwork.com", + "zing.vn", + "youdao.com", + "systweak.com", + "flipora.com", + "ning.com", + "engadget.com", + "abril.com.br", + "exoclick.com", + "semrush.com", + "commentcamarche.net", + "shareasale.com", + "vnexpress.net", + "battle.net", + "youm7.com", + "digg.com", + "rapidgator.net", + "expedia.com", + "twimg.com", + "newegg.com", + "lady8844.com", + "xgo.com.cn", + "pcgames.com.cn", + "fotolia.com", + "rednet.cn", + "inspsearch.com", + "lemonde.fr", + "adultfriendfinder.com", + "typepad.com", + "mercadolibre.com.ar", + "homedepot.com", + "chip.de", + "lefigaro.fr", + "swagbucks.com", + "taleo.net", + "kinopoisk.ru", + "oracle.com", + "who.is", + "2ch.net", + "qtrax.com", + "eonline.com", + "sochi2014.com", + "myfreecams.com", + "pcbaby.com.cn", + "gome.com.cn", + "amazon.it", + "timeanddate.com", + "bestusefuldownloads.com", + "foursquare.com", + "telexfree.com", + "gc.ca", + "58.com", + "hubspot.com", + "backpage.com", + "examiner.com", + "ria.ru", + "google.sk", + "opensiteexplorer.org", + "awesomehp.com", + "sexlog.com", + "gutefrage.net", + "steampowered.com", + "slate.com", + "marketwatch.com", + "beva.com", + "shaadi.com", + "v1.cn", + "teensdigest.com", + "blogspot.ru", + "taboola.com", + "citrixonline.com", + "hatena.ne.jp", + "seesaa.net", + "houzz.com", + "bodybuilding.com", + "webs.com", + "chexun.com", + "ya.ru", + "citibank.com", + "viralnova.com", + "2345.com", + "ashleyrnadison.com", + "verizonwireless.com", + "55bbs.com", + "theblaze.com", + "123rf.com", + "gotomeeting.com", + "kijiji.ca", + "linkbucks.com", + "haber7.com", + "pravda.com.ua", + "altervista.org", + "mihanblog.com", + "terra.com.br", + "soku.com", + "urbandictionary.com", + "mercadolibre.com.mx", + "pixiv.net", + "oneindia.in", + "yellowpages.com", + "hotels.com", + "drtuber.com", + "scoop.it", + "blogspot.jp", + "cbslocal.com", + "movie4k.to", + "cpmterra.com", + "hubpages.com", + "mobile.de", + "yaolan.com", + "etao.com", + "hidemyass.com", + "ca.gov", + "dreamstime.com", + "firedrive.com", + "searchengines.ru", + "m-w.com", + "ebay.fr", + "templatemonster.com", + "evernote.com", + "fastdailyfind.com", + "amazon.es", + "gateable.com", + "hupu.com", + "y8.com", + "blogspot.com.tr", + "tokobagus.com", + "azlyrics.com", + "website-unavailable.com", + "r10.net", + "wiktionary.org", + "bongacams.com", + "focus.de", + "linksynergy.com", + "steamcommunity.com", + "vk.me", + "sakura.ne.jp", + "foxsports.com", + "optmd.com", + "tabelog.com", + "narod.ru", + "glassdoor.com", + "outlook.com", + "europa.eu", + "viadeo.com", + "leadpages.net", + "ouedkniss.com", + "facenama.com", + "lpcloudsvr302.com", + "agoda.com", + "qvo6.com", + "majesticseo.com", + "mirror.co.uk", + "google.com.kw", + "filehippo.com", + "moneycontrol.com", + "duckduckgo.com", + "npr.org", + "coupons.com", + "mynet.com", + "allrecipes.com", + "priceline.com", + "liveleak.com", + "jqw.com", + "slickdeals.net", + "webcrawler.com", + "babytree.com", + "amung.us", + "tomshardware.com", + "openadserving.com", + "independent.co.uk", + "kompas.com", + "turbobit.net", + "google.kz", + "leo.org", + "yandex.com.tr", + "mp3skull.com", + "nordstrom.com", + "news.com.au", + "traidnt.net", + "wunderground.com", + "cnbc.com", + "jquery.com", + "dict.cc", + "persianblog.ir", + "clarin.com", + "all-free-download.com", + "fhserve.com", + "sape.ru", + "asos.com", + "addmefast.com", + "lequipe.fr", + "lapatilla.com", + "ancestry.com", + "stockstar.com", + "monster.com", + "people.com", + "gawker.com", + "howstuffworks.com", + "tradedoubler.com", + "over-blog.com", + "cbc.ca", + "fishcod.com", + "yoka.com", + "macys.com", + "gazeta.ru", + "free-tv-video-online.me", + "google.bg", + "google.lk", + "southwest.com", + "realtor.com", + "custhelp.com", + "bhaskar.com", + "softpedia.com", + "farsnews.com", + "cy-pr.com", + "theverge.com", + "tudou.com", + "youboy.com", + "porn.com", + "wetransfer.com", + "virgilio.it", + "mega.co.nz", + "sfgate.com", + "delta-homes.com", + "squarespace.com", + "watchseries.lt", + "overstock.com", + "nifty.com", + "jvzoo.com", + "lanacion.com.ar", + "prntscr.com", + "vesti.ru", + "zimbio.com", + "adscale.de", + "google.co.nz", + "yac.mx", + "tickld.com", + "kayak.com", + "youth.cn", + "jd.com", + "shopclues.com", + "allocine.fr", + "cracked.com", + "eventbrite.com", + "behance.net", + "youtube-mp3.org", + "bankmellat.ir", + "echo.msk.ru", + "autohome.com.cn", + "yandex.kz", + "sex.com", + "smh.com.au", + "digikala.com", + "mackolik.com", + "rottentomatoes.com", + "renren.com", + "wired.com", + "imageshack.us", + "interia.pl", + "zanox.com", + "eastday.com", + "fool.com", + "chinabyte.com", + "haberturk.com", + "hespress.com", + "wow.com", + "jobrapido.com", + "idnes.cz", + "qinbei.com", + "pr-cy.ru", + "klikbca.com", + "17ok.com", + "gamefaqs.com", + "immobilienscout24.de", + "biblegateway.com", + "espnfc.com", + "ehowenespanol.com", + "tabnak.ir", + "avazutracking.net", + "sapo.pt", + "welt.de", + "google.com.ec", + "sears.com", + "pcmag.com", + "nownews.com", + "kickstarter.com", + "pixlr.com", + "hongkiat.com", + "chinatimes.com", + "zoho.com", + "reverso.net", + "indianrail.gov.in", + "privatehomeclips.com", + "cbssports.com", + "entrepreneur.com", + "sporx.com", + "bhphotovideo.com", + "fatakat.com", + "xunlei.com", + "nikkei.com", + "chron.com", + "24h.com.vn", + "basecamp.com", + "airtel.in", + "dealshark.com", + "shopify.com", + "babycenter.com", + "letitbit.net", + "nuvid.com", + "yts.re", + "aparat.com", + "r2games.com", + "prestashop.com", + "alarabiya.net", + "india.com", + "magentocommerce.com", + "heise.de", + "sulekha.com", + "mpnrs.com", + "ibm.com", + "cbsnews.com", + "searchengineland.com", + "mapquest.com", + "ccb.com", + "zappos.com", + "rightmove.co.uk", + "sahadan.com", + "milanuncios.com", + "wenku.baidu.com/user/reg", + "ovh.net", + "screencast.com", + "ahrefs.com", + "6.cn", + "businessweek.com", + "linternaute.com", + "dafont.com", + "bestblackhatforum.com", + "streamcloud.eu", + "as.com", + "gtmetrix.com", + "wmmail.ru", + "spankwire.com", + "google.com.do", + "jeuxvideo.com", + "digitalpoint.com", + "amazon.ca", + "mlb.com", + "weheartit.com", + "list.ru", + "leagueoflegends.com", + "56.com", + "bloglovin.com", + "payoneer.com", + "nokia.com", + "aizhan.com", + "novinky.cz", + "ustream.tv", + "getbootstrap.com", + "seekingalpha.com", + "codwide.com", + "vice.com", + "usmagazine.com", + "primewire.ag", + "cam4.com", + "thehindu.com", + "deezer.com", + "graphicriver.net", + "dmm.com", + "biglobe.ne.jp", + "google.rs", + "z5x.net", + "tutsplus.com", + "icicibank.co.in", + "indiamart.com", + "gap.com", + "ensonhaber.com", + "sitepoint.com", + "rutor.org", + "chinanews.com", + "brainyquote.com", + "keepvid.com", + "censor.net.ua", + "subito.it", + "tablica.pl", + "onlylady.com", + "zulily.com", + "squidoo.com", + "change.org", + "sh.st", + "webhostingtalk.com", + "9gag.tv", + "nairaland.com", + "gulfup.com", + "staples.com", + "gamespot.com", + "woorank.com", + "box.com", + "infobae.com", + "elegantthemes.com", + "104.com.tw", + "aftonbladet.se", + "google.hr", + "myegy.com", + "noticias24.com", + "eastmoney.com", + "lowes.com", + "xtube.com", + "worldstarhiphop.com", + "glispa.com", + "verizon.com", + "united.com", + "linkwithin.com", + "google.com.ly", + "whitepages.com", + "games.la", + "dmoz.org", + "imagebam.com", + "careerbuilder.com", + "sky.com", + "instructables.com", + "mercadolibre.com.ve", + "makemytrip.com", + "pinimg.com", + "pingdom.com", + "foodnetwork.com", + "orf.at", + "android.com", + "wiocha.pl", + "csdn.net", + "appledaily.com.tw", + "nike.com", + "eluniversal.com.mx", + "delta.com", + "ezinearticles.com", + "sueddeutsche.de", + "woothemes.com", + "comcast.com", + "gismeteo.ru", + "lotour.com", + "nfl.com", + "xbox.com", + "almanar.com.lb", + "disney.go.com", + "ctrip.com", + "forexfactory.com", + "clicksvenue.com", + "nhl.com", + "ted.com", + "gstatic.com", + "forobeta.com", + "ovh.com", + "mystart.com", + "maktoob.com", + "nouvelobs.com", + "hindustantimes.com", + "billdesk.com", + "1und1.de", + "zhaopin.com", + "weather.gov", + "kioskea.net", + "myspace.com", + "firstpost.com", + "picmonkey.com", + "grooveshark.com", + "exblog.jp", + "pantip.com", + "earthlink.net", + "styletv.com.cn", + "icloud.com", + "manta.com", + "google.by", + "mediaset.it", + "searchenginewatch.com", + "japanpost.jp", + "postimg.org", + "mbc.net", + "cheezburger.com", + "workercn.cn", + "directadvert.ru", + "nationzoom.com", + "caixa.gov.br", + "gumtree.com", + "51job.com", + "viooz.co", + "usbank.com", + "wmtransfer.com", + "fidelity.com", + "gogetlinks.net", + "office365.com", + "pchome.com.tw", + "feedburner.com", + "mp3truck.net", + "sfr.fr", + "junglee.com", + "keezmovies.com", + "binarysystem4u.com", + "way2sms.com", + "oyunskor.com", + "peyvandha.ir", + "ticketmaster.com", + "lacaixa.es", + "video-one.com", + "yomiuri.co.jp", + "popcash.net", + "nypost.com", + "dubizzle.com", + "wpmudev.org", + "mgid.com", + "google.lt", + "zazzle.com", + "nbcolympics.com", + "battlefield.com", + "avast.com", + "jsfiddle.net", + "namecheap.com", + "css-tricks.com", + "friv.com", + "6park.com", + "xhamstercams.com", + "mysql.com", + "dx.com", + "thechive.com", + "vsuch.com", + "souq.com", + "anyoption.com", + "dantri.com.vn", + "ebay.es", + "abc.es", + "whatsapp.com", + "bitshare.com", + "savefrom.net", + "beytoote.com", + "xiaomi.com", + "prweb.com", + "prothom-alo.com", + "pagesjaunes.fr", + "bbb.org", + "eenadu.net", + "tinypic.com", + "whois.com", + "sourtimes.org", + "adk2.com", + "criteo.com", + "tistory.com", + "ilyke.net", + "ruten.com.tw", + "skysports.com", + "makeuseof.com", + "p5w.net", + "hh.ru", + "nationalgeographic.com", + "pornmd.com", + "rakuten.ne.jp", + "idealo.de", + "telegraaf.nl", + "barnesandnoble.com", + "mynavi.jp", + "4chan.org", + "media-fire.org", + "crunchbase.com", + "bravotube.net", + "myfitnesspal.com", + "today.com", + "mit.edu", + "cnmo.com", + "nu.nl", + "tripadvisor.co.uk", + "spotify.com", + "homeway.com.cn", + "dianping.com", + "costco.com", + "pastebin.com", + "dhgate.com", + "windowsphone.com", + "stagram.com", + "investopedia.com", + "ninemsn.com.au", + "excite.co.jp", + "22find.com", + "google.iq", + "hi5.com", + "ad4game.com", + "kohls.com", + "online.sh.cn", + "geocities.jp", + "investing.com", + "haberler.com", + "ft.com", + "weblio.jp", + "google.tn", + "list-manage1.com", + "hsbc.co.uk", + "ppstream.com", + "cloob.com", + "korrespondent.net", + "ucoz.com", + "1and1.com", + "mail.com", + "fotostrana.ru", + "accountonline.com", + "forgeofempires.com", + "dribbble.com", + "city-data.com", + "drupal.org", + "tunein.com", + "51fanli.com", + "ibtimes.com", + "intel.com", + "polyvore.com", + "cbs.com", + "bab.la", + "imobile.com.cn", + "bestadbid.com", + "eztv.it", + "smashingmagazine.com", + "skyrock.com", + "copyscape.com", + "cookpad.com", + "smallseotools.com", + "asus.com", + "anysex.com", + "nbcsports.com", + "kinox.to", + "mbank.com.pl", + "500px.com", + "sofanti.com", + "marktplaats.nl", + "cpanel.net", + "hm.com", + "chicagotribune.com", + "ultimate-guitar.com", + "infolinks.com", + "merdeka.com", + "yandex.by", + "tribalfusion.com", + "livedoor.biz", + "prezi.com", + "last.fm", + "sznews.com", + "junbi-tracker.com", + "so-net.ne.jp", + "cocolog-nifty.com", + "ocn.ne.jp", + "perezhilton.com", + "yaplakal.com", + "championat.com", + "ebay.ca", + "mangareader.net", + "donanimhaber.com", + "miniclip.com", + "vcommission.com", + "noaa.gov", + "imagefap.com", + "gumtree.com.au", + "liveperson.net", + "gfycat.com", + "hollywoodreporter.com", + "dropboxusercontent.com", + "akhbarak.net", + "sou300.com", + "kicker.de", + "livestream.com", + "tribunnews.com", + "kp.ru", + "techradar.com", + "315che.com", + "dw.de", + "codeproject.com", + "newsru.com", + "pconline.com.cn", + "aa.com", + "usaa.com", + "asahi.com", + "themetapicture.com", + "quicksprout.com", + "piriform.com", + "888poker.com", + "gittigidiyor.com", + "nhk.or.jp", + "statigr.am", + "ipage.com", + "gazzetta.it", + "letv.com", + "yourlust.com", + "freepik.com", + "trello.com", + "mixi.jp", + "livememe.com", + "ew.com", + "seosprint.net", + "doisongphapluat.com", + "autotrader.com", + "docstoc.com", + "viva.co.id", + "livestrong.com", + "inc.com", + "hunantv.com", + "bahn.de", + "depositphotos.com", + "rackspace.com", + "flippa.com", + "norton.com", + "lynda.com", + "teamviewer.com", + "vip.com", + "ksl.com", + "solarmovie.so", + "klout.com", + "pikabu.ru", + "vporn.com", + "apache.org", + "sportbox.ru", + "correios.com.br", + "toptenreviews.com", + "ea.com", + "thenextweb.com", + "superuser.com", + "delicious.com", + "zoosk.com", + "inbox.com", + "kongregate.com", + "starbaby.cn", + "888casino.com", + "brazzers.com", + "sockshare.com", + "megashare.info", + "vistaprint.com", + "porntube.com", + "seriesyonkis.com", + "20minutos.es", + "jagran.com", + "programme-tv.net", + "elcomercio.pe", + "blogspot.kr", + "t411.me", + "googleapis.com", + "blog.com", + "qingdaonews.com", + "sedo.com", + "itmedia.co.jp", + "mtv.com", + "ynet.co.il", + "playstation.com", + "pornhublive.com", + "thedailybeast.com", + "r7.com", + "sabah.com.tr", + "126.com", + "topix.com", + "liga.net", + "marriott.com", + "tvn24.pl", + "sitesell.com", + "netdna-cdn.com", + "startimes.com", + "hstpnetwork.com", + "blogspot.com.au", + "17u.cn", + "subscribe.ru", + "woot.com", + "rozblog.com", + "wimp.com", + "vg.no", + "t-mobile.com", + "bmi.ir", + "kankan.com", + "tsn.ua", + "gumtree.co.za", + "googleadservices.com", + "pnc.com", + "cars.com", + "soccerway.com", + "o2.pl", + "zeit.de", + "zopim.com", + "cloudflare.com", + "givemesport.com", + "statscrop.com", + "gravatar.com", + "flightradar24.com", + "ubuntu.com", + "salon.com", + "ytimg.com", + "trovigo.com", + "sunporno.com", + "libertagia.com", + "mihanwebads.com", + "shopathome.com", + "cdiscount.com", + "commbank.com.au", + "elwatannews.com", + "discovercard.com", + "infowars.com", + "blogimg.jp", + "portaldosites.com", + "fixya.com", + "drom.ru", + "fastpic.ru", + "pbs.org", + "kandao.com", + "okwave.jp", + "faz.net", + "sbnation.com", + "hilton.com", + "duba.com", + "homeshop18.com", + "ultimasnoticias.com.ve", + "bankrate.com", + "megasesso.com", + "studiopress.com", + "sports.ru", + "asriran.com", + "etoro.com", + "cafemom.com", + "dhl.de", + "indiegogo.com", + "tdcanadatrust.com", + "1tv.ru", + "gogvo.com", + "bt.com", + "blogspot.tw", + "zergnet.com", + "garanti.com.tr", + "privatbank.ua", + "sanook.com", + "travelocity.com", + "networksolutions.com", + "thqafawe3lom.com", + "trklnks.com", + "torchbrowser.com", + "logmein.com", + "williamhill.com", + "zhihu.com", + "auto.ru", + "craigslist.ca", + "olx.com.pk", + "smi2.ru", + "ukr.net", + "dpreview.com", + "boston.com", + "folha.uol.com.br", + "name.com", + "ashemaletube.com", + "slimspots.com", + "envato.com", + "6pm.com", + "fandango.com", + "sozcu.com.tr", + "nikkeibp.co.jp", + "aufeminin.com", + "baiducontent.com", + "buscape.com.br", + "funnyordie.com", + "337.com", + "sakshi.com", + "airasia.com", + "samanyoluhaber.com", + "picofile.com", + "blackberry.com", + "google.com.gt", + "io9.com", + "easyhits4u.com", + "qianyan001.com", + "flashx.tv", + "zap2it.com", + "dnsrsearch.com", + "patch.com", + "staticflickr.com", + "clicksor.com", + "mcafee.com", + "zerohedge.com", + "zara.com", + "freelotto.com", + "tutorialspoint.com", + "qone8.com", + "olx.com.br", + "axisbank.com", + "marketgid.com", + "your-server.de", + "game321.com", + "harvard.edu", + "indianexpress.com", + "fatwallet.com", + "nudevista.com", + "paper.li", + "opera.com", + "serving-sys.com", + "nowvideo.sx", + "theatlantic.com", + "cisco.com", + "bookmyshow.com", + "webmasterworld.com", + "metro.co.uk", + "leparisien.fr", + "orbitz.com", + "h2porn.com", + "4cdn.org", + "itar-tass.com", + "nasa.gov", + "macrumors.com", + "google.si", + "usnews.com", + "premierleague.com", + "largeporntube.com", + "blogsky.com", + "bufferapp.com", + "zeroredirect1.com", + "sendspace.com", + "stanford.edu", + "state.gov", + "yjc.ir", + "abc.net.au", + "pornsharing.com", + "4pda.ru", + "52pk.net", + "meituan.com", + "mmgp.ru", + "sponichi.co.jp", + "masrawy.com", + "whatismyipaddress.com", + "onlinewebfind.com", + "news24.com", + "zdnet.com", + "imagevenue.com", + "gameaholic.com", + "mediaplex.com", + "clip.vn", + "2mdn.net", + "nmisr.com", + "musica.com", + "networkedblogs.com", + "metrolyrics.com", + "chacha.com", + "internetdownloadmanager.com", + "legacy.com", + "wwe.com", + "icbc.com.cn", + "ny.gov", + "chomikuj.pl", + "alexa.com", + "pcworld.com", + "overthumbs.com", + "adp.com", + "ad2games.com", + "adme.ru", + "bidvertiser.com", + "rtl.de", + "sweetim.com", + "gaana.com", + "7k7k.com", + "livingsocial.com", + "netteller.com", + "rakuten.com", + "chess.com", + "blackboard.com", + "thekitchn.com", + "ilfattoquotidiano.it", + "20minutes.fr", + "soufun.com", + "nerdbux.com", + "breitbart.com", + "webtretho.com", + "tvguide.com", + "miralinks.ru", + "weather.com.cn", + "zalando.de", + "dangdang.com", + "bubblews.com", + "thoughtcatalog.com", + "114la.com", + "msn.ca", + "blogmura.com", + "americanas.com.br", + "elitedaily.com", + "lightinthebox.com", + "bizjournals.com", + "yixun.com", + "itau.com.br", + "51.la", + "doodle.com", + "arabyonline.com", + "opencart.com", + "bomnegocio.com", + "lego.com", + "pclady.com.cn", + "metacafe.com", + "kimiss.com", + "journaldunet.com", + "gnavi.co.jp", + "51auto.com", + "clickey.com", + "chekb.com", + "searchnu.com", + "diply.com", + "walgreens.com", + "descargar.es", + "mirrorcreator.com", + "pornerbros.com", + "askmen.com", + "shop.com", + "inmotionhosting.com", + "filmweb.pl", + "kbb.com", + "appround.biz", + "aljazeera.com", + "lumosity.com", + "sweet-page.com", + "pole-emploi.fr", + "fishki.net", + "rollingstone.com", + "kapanlagi.com", + "yandex.com", + "cnblogs.com", + "plaintube.com", + "intentmedia.net", + "about.me", + "torrentz.in", + "01net.com", + "gyazo.com", + "nic.ru", + "juicyads.com", + "trend.az", + "askubuntu.com", + "unam.mx", + "tradus.com", + "tigerdirect.com", + "letras.mus.br", + "imagetwist.com", + "perfectmoney.is", + "wpbeginner.com", + "thepiratetrader.com", + "victoriassecret.com", + "images-amazon.com", + "tusfiles.net", + "chefkoch.de", + "ojooo.com", + "alphaporno.com", + "betfair.com", + "tympanus.net", + "td.com", + "4tube.com", + "phonearena.com", + "bankmandiri.co.id", + "cox.net", + "ozon.ru", + "perfectgirls.net", + "asana.com", + "sprint.com", + "berniaga.com", + "vente-privee.com", + "ing.nl", + "finn.no", + "atwiki.jp", + "toysrus.com", + "petflow.com", + "reverbnation.com", + "freeones.com", + "abc.go.com", + "bradesco.com.br", + "yenisafak.com.tr", + "profit-partner.ru", + "redfin.com", + "mangafox.me", + "appnexus.com", + "sxc.hu", + "speedanalysis.net", + "n-tv.de", + "qidian.com", + "teespring.com", + "break.com", + "nailedhard.com", + "jalan.net", + "authorize.net", + "adultadworld.com", + "sootoo.com", + "roboform.com", + "kotaku.com", + "multitran.ru", + "food.com", + "twitpic.com", + "pogo.com", + "sabq.org", + "priceminister.com", + "lexpress.fr", + "livetv.sx", + "nikkansports.com", + "securepaynet.net", + "vodoumedia.com", + "airbnb.com", + "trafficholder.com", + "rr.com", + "expireddomains.net", + "bandcamp.com", + "wayfair.com", + "tut.by", + "maybank2u.com.my", + "similarweb.com", + "zol.com.cn", + "247realmedia.com", + "asp.net", + "lonelyplanet.com", + "seopult.ru", + "whois.net", + "academia.edu", + "univision.com", + "interfax.ru", + "amarillasinternet.com", + "miercn.com", + "barclays.co.uk", + "societe.com", + "khabaronline.ir", + "unity3d.com", + "nyaa.se", + "euronews.com", + "verizon.net", + "rutube.ru", + "wetter.com", + "imlive.com", + "ggpht.com", + "docin.com", + "yify-torrents.com", + "yhd.com", + "citibank.co.in", + "xdating.com", + "roblox.com", + "voyages-sncf.com", + "189.cn", + "identi.li", + "bayt.com", + "medicinenet.com", + "wmaraci.com", + "stern.de", + "eluniversal.com", + "zoopla.co.uk", + "easyjet.com", + "dhl.com", + "freakshare.com", + "rojadirecta.me", + "sp.gov.br", + "friendfeed.com", + "beamtele.com", + "searchenginejournal.com", + "nocookie.net", + "reliancenetconnect.co.in", + "google.co.ma", + "fanpop.com", + "adk2.co", + "tagesschau.de", + "nate.com", + "ole.com.ar", + "index.hu", + "aruba.it", + "eltiempo.com", + "resellerclub.com", + "qip.ru", + "xossip.com", + "siteground.com", + "royalbank.com", + "advfn.com", + "tebyan.net", + "iciba.com", + "bigcartel.com", + "sciencedirect.com", + "hepsiburada.com", + "cpasbien.me", + "alfabank.ru", + "microsoftstore.com", + "discogs.com", + "onliner.by", + "socialmediaexaminer.com", + "seasonvar.ru", + "tripadvisor.in", + "360doc.com", + "chinabroadcast.cn", + "realestate.com.au", + "avira.com", + "herokuapp.com", + "downloadsetup.net", + "torcache.net", + "uefa.com", + "rg.ru", + "qianlong.com", + "hugedomains.com", + "arstechnica.com", + "fifa.com", + "ryanair.com", + "i.ua", + "axisbank.co.in", + "alimama.com", + "wanggou.com", + "radikal.com.tr", + "echoroukonline.com", + "unian.net", + "slashdot.org", + "dmv.org", + "mayoclinic.org", + "newsmax.com", + "google.com.et", + "icontact.com", + "vanguardngr.com", + "infojobs.net", + "say-move.org", + "ileehoo.com", + "mirtesen.ru", + "yam.com", + "e-hentai.org", + "wikimapia.org", + "shop-pro.jp", + "bukalapak.com", + "jcpenney.com", + "ilmeteo.it", + "iltasanomat.fi", + "telecomitalia.it", + "kariyer.net", + "shutterfly.com", + "santabanta.com", + "softlayer.com", + "complex.com", + "mamba.ru", + "netshoes.com.br", + "theweathernetwork.com", + "politico.com", + "advego.ru", + "eroprofile.com", + "mainichi.jp", + "nta.go.jp", + "sfimg.com", + "ap.org", + "webex.com", + "hinet.net", + "skyscrapercity.com", + "affili.net", + "adplxmd.com", + "iltalehti.fi", + "impress.co.jp", + "jezebel.com", + "extremetube.com", + "digitaltrends.com", + "one.com", + "ranker.com", + "qiwi.com", + "collegehumor.com", + "nbc.com", + "boursorama.com", + "watch32.com", + "meetcheap.com", + "forums.wordpress.com", + "cvs.com", + "kenh14.vn", + "yallakora.com", + "plurk.com", + "xtool.ru", + "airtel.com", + "wp.com", + "sftimes.co", + "economist.com", + "rapidshare.com", + "redbox.com", + "audible.com", + "kissmetrics.com", + "atpanel.com", + "wargaming.net", + "1337x.org", + "shahrekhabar.com", + "bdnews24.com", + "tvrain.ru", + "wykop.pl", + "mts.ru", + "nnm-club.me", + "schwab.com", + "elconfidencial.com", + "modelmayhem.com", + "zhidao.baidu.com/user/admin", + "aastocks.com", + "tiscali.it", + "qadabra.com", + "autoscout24.de", + "vatanim.com.tr", + "gotowebinar.com", + "edmunds.com", + "ebaumsworld.com", + "megafilmeshd.net", + "poste.it", + "mcssl.com", + "aljazeera.net", + "netvibes.com", + "sme.sk", + "tesco.com", + "525j.com.cn", + "tf1.fr", + "ccidnet.com", + "ldblog.jp", + "blockchain.info", + "played.to", + "nthwall.com", + "justanswer.com", + "clickbank.net", + "fnac.com", + "gmarket.co.kr", + "clipconverter.cc", + "nextmedia.com", + "bb.com.br", + "learntotradethemarket.com", + "pornoid.com", + "downloadha.com", + "hellporno.com", + "ooo-sex.com", + "blocket.se", + "tubeplus.me", + "seek.com.au", + "filestube.to", + "ceneo.pl", + "walmart.com.br", + "sonymobile.com", + "dyndns.org", + "ulmart.ru", + "vrbo.com", + "sport.pl", + "ashleymadison.com", + "el-wlid.com", + "boredpanda.com", + "udemy.com", + "mobogenie.com", + "tubegalore.com", + "theglobeandmail.com", + "bancobrasil.com.br", + "888.com", + "adreactor.com", + "zomato.com", + "depositfiles.com", + "moneysavingexpert.com", + "dofus.com", + "oanda.com", + "sport1.de", + "justhost.com", + "longhoo.net", + "otomoto.pl", + "techrepublic.com", + "westpac.com.au", + "dreamhost.com", + "ilsole24ore.com", + "clubic.com", + "gamme.com.tw", + "doctissimo.fr", + "isna.ir", + "s2d6.com", + "tdbank.com", + "zwaar.net", + "slando.ua", + "segodnya.ua", + "appannie.com", + "bartarinha.ir", + "egotastic.com", + "ed.gov", + "lg.com", + "120ask.com", + "hostmonster.com", + "super.cz", + "mercola.com", + "bayproxy.me", + "day.az", + "dummies.com", + "azet.sk", + "huffingtonpost.co.uk", + "okezone.com", + "medu.ir", + "lavanguardia.com", + "espreso.tv", + "nymag.com", + "autotrader.co.uk", + "socialmediatoday.com", + "wn.com", + "rtbpop.com", + "99designs.com", + "sanspo.com", + "proboards.com", + "virginmedia.com", + "99acres.com", + "blic.rs", + "pingomatic.com", + "prnewswire.com", + "sony.com", + "seobuilding.ru", + "www.gov.uk", + "jugem.jp", + "watchtower.com", + "seoprofiler.com", + "centrum.cz", + "techbrowsing.com", + "almasryalyoum.com", + "lolking.net", + "nipic.com", + "fanfiction.net", + "bwin.com", + "anyporn.com", + "sulit.com.ph", + "quickmeme.com", + "carview.co.jp", + "haizhangs.com", + "airtelforum.com", + "flirt4free.com", + "billboard.com", + "trademe.co.nz", + "rapgenius.com", + "pchouse.com.cn", + "beforeitsnews.com", + "peopleperhour.com", + "basecamphq.com", + "onlinedown.net", + "bet365.es", + "fucked-tube.com", + "santander.co.uk", + "speedbit.com", + "gi-akademie.com", + "google.com.pr", + "winupdatevideos.com", + "bizrate.com", + "xxxbunker.com", + "coursera.org", + "zoominfo.com", + "rarbg.com", + "ahram.org.eg", + "howtogeek.com", + "focus.cn", + "duden.de", + "mufg.jp", + "ex.ua", + "titan24.com", + "greatandhra.com", + "qqbaobao.com", + "rtve.es", + "yandex.net", + "yle.fi", + "panoramio.com", + "google.com.af", + "scol.com.cn", + "gamestop.com", + "xiami.com", + "webpagetest.org", + "creativecommons.org", + "archive.is", + "timesjobs.com", + "centurylink.com", + "wowhead.com", + "wordstream.com", + "lloydsbank.co.uk", + "incredibar.com", + "vagalume.com.br", + "installerapplicationusa.com", + "shinobi.jp", + "ruvr.ru", + "natwest.com", + "roulettebotplus.com", + "wildberries.ru", + "huaban.com", + "buenosearch.com", + "reg.ru", + "tempo.co", + "qvc.com", + "mangahere.com", + "tv.com", + "boc.cn", + "fnb.co.za", + "made-in-china.com", + "eleconomista.es", + "ubuntuforums.org", + "derstandard.at", + "xml-sitemaps.com", + "jang.com.pk", + "jiayuan.com", + "credit-agricole.fr", + "androidcentral.com", + "urbanspoon.com", + "pornoxo.com", + "regnum.ru", + "bedbathandbeyond.com", + "skladchik.com", + "daily.co.jp", + "n-mobile.net", + "suntrust.com", + "justjared.com", + "vid2c.com", + "gettyimages.com", + "tubecup.com", + "kinogo.net", + "similarsites.com", + "fling.com", + "vine.co", + "banglanews24.com", + "pudelek.pl", + "support.wordpress.com", + "cox.com", + "xkcd.com", + "adbooth.com", + "anz.com", + "adxcore.com", + "google.com.sv", + "indeed.co.in", + "scottrade.com", + "v9.com", + "isohunt.to", + "srclick.ru", + "gharreh.com", + "zaman.com.tr", + "monsterindia.com", + "delfi.lt", + "chewen.com", + "empowernetwork.com/QUbsgqDwpjjbkpOgwgOeaw==", + "state.tx.us", + "segundamano.es", + "utro.ru", + "sitescout.com", + "justclick.ru", + "wnd.com", + "cosmopolitan.com", + "local.com", + "anitube.se", + "sport.es", + "google.com.qa", + "ads-id.com", + "google.lv", + "capitalone360.com", + "hattrick.org", + "qualtrics.com", + "inagist.com", + "filgoal.com", + "incredibar-search.com", + "adslgate.com", + "directv.com", + "ilivid.com", + "warthunder.ru", + "jasmin.com", + "cyberciti.biz", + "msn.co.jp", + "canalblog.com", + "boerse.bz", + "tnaflix.com", + "apa.az", + "prchecker.info", + "reclameaqui.com.br", + "onlyworldnews.com", + "submarino.com.br", + "kuronekoyamato.co.jp", + "paipai.com", + "realitykings.com", + "deadspin.com", + "islamweb.net", + "no-ip.com", + "lostfilm.tv", + "wattpad.com", + "wav.tv", + "citi.com", + "buysellads.com", + "prezentacya.ru", + "forocoches.com", + "donga.com", + "drive2.ru", + "netsuite.com", + "privalia.com", + "bidorbuy.co.za", + "cmbchina.com", + "searchresultsguide.com", + "odatv.com", + "skrill.com", + "en.wordpress.com", + "dojki.com", + "unibet.com", + "hotpepper.jp", + "simplyhired.com", + "vesti.ua", + "ibtimes.co.uk", + "creativebloq.com", + "filmesonlinegratis.net", + "videohive.net", + "kmart.com", + "metacritic.com", + "rikunabi.com", + "zozo.jp", + "venturebeat.com", + "1and1.es", + "banesconline.com", + "internethaber.com", + "rae.es", + "planetsuzy.org", + "wpengine.com", + "kizi.com", + "armorgames.com", + "argos.co.uk", + "dtiblog.com", + "diigo.com", + "forever21.com", + "flashscore.com", + "vedomosti.ru", + "ccavenue.com", + "att.net", + "esporte.uol.com.br", + "empowernetwork.com/PXQ7Uz1Kyg+LR1APrG7pKQ==", + "to8to.com", + "hotwire.com", + "all.biz", + "info.com", + "xhamster.com/user/video", + "000webhost.com", + "worldoftanks.ru", + "dagbladet.no", + "computerbild.de", + "nasdaq.com", + "chitika.com", + "ipeen.com.tw", + "abola.pt", + "wiley.com", + "lpcloudsvr303.com", + "tinsao.net", + "mehrnews.com", + "hugesex.tv", + "christian-dogma.com", + "search.us.com", + "garmin.com", + "tgbus.com", + "iherb.com", + "torntv-tvv.org", + "chosun.com", + "kinozal.tv", + "berkeley.edu", + "techtarget.com", + "labanquepostale.fr", + "fitbit.com", + "folkd.com", + "theonion.com", + "idealista.com", + "oscar.go.com", + "jiji.com", + "anonym.to", + "allabout.co.jp", + "wufoo.com", + "rantlifestyle.com", + "google.com.bo", + "28.com", + "rocketnews24.com", + "hotspotshield.com", + "opentable.com", + "admngronline.com", + "naturalnews.com", + "zeroredirect2.com", + "ero-video.net", + "dinamalar.com", + "fl.ru", + "mtsindia.in", + "kapook.com", + "theage.com.au", + "allmyvideos.net", + "dinodirect.com", + "videodownloadconverter.com", + "tripleclicks.com", + "brassring.com", + "fapdu.com", + "cafepress.com", + "infospace.com", + "express.com.pk", + "fumu.com", + "infibeam.com", + "etrade.com", + "meneame.net", + "seroundtable.com", + "bigpoint.com", + "chinadaily.com.cn", + "myfonts.com", + "ezdownloadpro.info", + "codeplex.com", + "xl.pt", + "kaspersky.com", + "khabarfarsi.com", + "admitad.com", + "flirchi.com", + "dpstream.net", + "miniinthebox.com", + "samsclub.com", + "charter.net", + "techbang.com", + "tiu.ru", + "fanpage.it", + "spokeo.com", + "imageshack.com", + "ijreview.com", + "bc.vc", + "lazada.co.id", + "gofirstrow.eu", + "uptobox.com", + "newsnow.co.uk", + "symantec.com", + "editor.wix.com", + "avaxhome.cc", + "canalplus.fr", + "copyblogger.com", + "vetogate.com", + "yahoo-mbga.jp", + "manoramaonline.com", + "ah-me.com", + "elheddaf.com", + "bitcointalk.org", + "haqaik.com", + "bbandt.com", + "linguee.de", + "blogspot.nl", + "minus.com", + "amarujala.com", + "openclassrooms.com", + "bigmir.net", + "koramgame.com", + "commonfloor.com", + "gayromeo.com", + "cleartrip.com", + "kompasiana.com", + "tankionline.com", + "google.co.tz", + "springer.com", + "lifenews.ru", + "sify.com", + "tvoyauda4a.ru", + "dfiles.ru", + "technorati.com", + "myorderbox.com", + "fanatik.com.tr", + "activesearchresults.com", + "laposte.net", + "whirlpool.net.au", + "stockcharts.com", + "leadimpact.com", + "cardekho.com", + "ryushare.com", + "blog.jp", + "fastcompany.com", + "eurosport.fr", + "emirates.com", + "lb.ua", + "serverfault.com", + "forbes.ru", + "bfmtv.com", + "google.jo", + "17173.com", + "wikispaces.com", + "tokopedia.com", + "liberation.fr", + "a10.com", + "kdnet.net", + "variety.com", + "blog.me", + "homeaway.com", + "ameritrade.com", + "adult-empire.com", + "national.com.au", + "haraj.com.sa", + "timewarnercable.com", + "casino.com", + "sheknows.com", + "google.com.uy", + "abv.bg", + "liputan6.com", + "etxt.ru", + "bigstockphoto.com", + "strato.de", + "cashtasks.com", + "columbia.edu", + "zdf.de", + "pptv.com", + "cricbuzz.com", + "sharesuper.info", + "radaronline.com", + "pornolab.net", + "google.ba", + "opensubtitles.org", + "abclocal.go.com", + "ansa.it", + "pbskids.org", + "vz.ru", + "iconfinder.com", + "vitaminl.tv", + "ntv.ru", + "peliculasyonkis.com", + "foto-hd.com", + "paytm.com", + "rantsports.com", + "optimum.net", + "postbank.de", + "index.hr", + "smugmug.com", + "haivl.com", + "arbeitsagentur.de", + "dailycaller.com", + "freshbooks.com", + "genieo.com", + "alnaddy.com", + "britishairways.com", + "20min.ch", + "findthebest.com", + "autoblog.com", + "warez-bb.org", + "iza.ne.jp", + "filmaffinity.com", + "megogo.net", + "soccermanager.com", + "dawanda.com", + "parallels.com", + "clickjogos.uol.com.br", + "12306.cn", + "jstv.com", + "cliphunter.com", + "pixabay.com", + "milenio.com", + "keybinary.com", + "suumo.jp", + "mediatakeout.com", + "vodafone.in", + "cheapoair.com", + "seobook.com", + "videoyoum7.com", + "yr.no", + "gofundme.com", + "ixbt.com", + "starbucks.com", + "conferenceplus.com", + "media1first.com", + "sergey-mavrodi.com", + "rafflecopter.com", + "591.com.tw", + "mangapanda.com", + "huffingtonpost.fr", + "tiboo.cn", + "bancomer.com.mx", + "handelsblatt.com", + "hpylgr.com", + "psu.edu", + "bancomercantil.com", + "sport-express.ru", + "codepen.io", + "foundationapi.com", + "ow.ly", + "vatgia.com", + "kommersant.ru", + "srvsinf.com", + "telderi.ru", + "mercadolibre.com.co", + "hsbc.com.hk", + "t3n.de", + "magicbricks.com", + "shopstyle.com", + "mudah.my", + "punchng.com", + "therichest.com", + "fansshare.com", + "lepoint.fr", + "vipzona.info", + "globaltestmarket.com", + "jqueryui.com", + "bcsh.com", + "mundodeportivo.com", + "newyorker.com", + "funnyjunk.com", + "gongchang.com", + "a8.net", + "ecollege.com", + "newhdplugin.net", + "befuck.com", + "elespectador.com", + "drugs.com", + "abplive.in", + "feelcars.com", + "psychologytoday.com", + "instaforex.com", + "strtsrv.com", + "atpworldtour.com", + "incruit.com", + "starwoodhotels.com", + "zone-telechargement.com", + "songspk.name", + "hatenablog.com", + "enfemenino.com", + "msnbc.com", + "freebitco.in", + "akairan.com", + "discovery.com", + "angieslist.com", + "lesechos.fr", + "transfermarkt.de", + "sephora.com", + "come.in", + "dailykos.com", + "deutsche-bank.de", + "warriorplus.com", + "awwwards.com", + "lomadee.com", + "365jia.cn", + "lotterypost.com", + "egrana.com.br", + "rivals.com", + "literotica.com", + "myfreshnet.com", + "ihg.com", + "fazenda.gov.br", + "tomnod.com", + "filesharefanatic.com", + "hotukdeals.com", + "bitsnoop.com", + "tenpay.com", + "putlocker.bz", + "ziddu.com", + "ntvmsnbc.com", + "surveyzrewardcenter.com", + "aliyun.com", + "media.net", + "comdirect.de", + "yoox.com", + "hotelscombined.com", + "vgsgaming-ads.com", + "robokassa.ru", + "webdesignerdepot.com", + "rotoworld.com", + "jmw.com.cn", + "seloger.com", + "dsdomination.com", + "affiliatewindow.com", + "abs-cbnnews.com", + "bmo.com", + "iconarchive.com", + "funshion.com", + "admin5.com", + "playhe.com", + "bigcommerce.com", + "yodobashi.com", + "bigfishgames.com", + "onlinecreditcenter6.com", + "homes.co.jp", + "gameninja.com", + "ebates.com", + "banamex.com.mx", + "google.com.lb", + "2chblog.jp", + "entertainment-factory.com", + "asg.to", + "walla.co.il", + "mktmobi.com", + "opensooq.com", + "adam4adam.com", + "expressen.se", + "elephanttube.com", + "ingdirect.es", + "clicrbs.com.br", + "seriespepito.com", + "skyscanner.net", + "shine.com", + "appround.us", + "washingtontimes.com", + "nrk.no", + "1stwebdesigner.com", + "gucheng.com", + "ghanaweb.com", + "jzip.com", + "rozetka.com.ua", + "wiziwig.tv", + "jobsdb.com", + "kitco.com", + "rpp.com.pe", + "aljaras.com", + "telecinco.es", + "storify.com", + "netease.com", + "business-standard.com", + "babble.com", + "noticias.uol.com.br", + "alice.it", + "bizdec.ru", + "voanews.com", + "freejobalert.com", + "origo.hu", + "list-manage2.com", + "gavick.com", + "national-lottery.co.uk", + "gazzetta.gr", + "freevideodownloadforpc.com", + "xmarks.com", + "wikibooks.org", + "sexad.net", + "alc.co.jp", + "emol.com", + "careesma.in", + "cdc.gov", + "toshiba.com", + "imgchili.net", + "rbcroyalbank.com", + "indowebster.com", + "zynga.com", + "telekom.com", + "jiameng.com", + "adsmarket.com", + "utorrent.com", + "aarp.org", + "cityadspix.com", + "ekstrabladet.dk", + "doc88.com", + "fetlife.com", + "fh21.com.cn", + "cambridge.org", + "tune.pk", + "auction.co.kr", + "patheos.com", + "search-results.com", + "lds.org", + "webrankinfo.com", + "newsit.gr", + "techtudo.com.br", + "moviepilot.com", + "mercador.ro", + "nguoiduatin.vn", + "tripadvisor.it", + "lurkmore.to", + "europapress.es", + "uspto.gov", + "excelsior.com.mx", + "myvideo.de", + "jetblue.com", + "camfrog.com", + "blick.ch", + "batepapo.uol.com.br", + "manager.co.th", + "goibibo.com", + "cornell.edu", + "apetube.com", + "nation.com", + "allmusic.com", + "naughtyamerica.com", + "obozrevatel.com", + "telegrafi.com", + "neimanmarcus.com", + "yabancidiziizle1.com", + "globalewallet.com", + "beeline.ru", + "hinkhoj.com", + "sitemeter.com", + "almesryoon.com", + "localmoxie.com", + "grotal.com", + "knowyourmeme.com", + "qiwi.ru", + "yieldmanager.com", + "yimg.com", + "horoscopedays.com", + "gfy.com", + "mxtoolbox.com", + "searchgol.com", + "inquirer.net", + "hollywoodlife.com", + "ebrun.com", + "shopbop.com", + "mysmartprice.com", + "kijiji.it", + "visual.ly", + "mop.com", + "1fichier.com", + "prlog.org", + "xjtour.com", + "shorouknews.com", + "thegioinet.net", + "hln.be", + "enom.com", + "thaqafnafsak.com", + "wahuu.com", + "ganji.com", + "russianpost.ru", + "myplaycity.com", + "dzone.com", + "dns-shop.ru", + "aif.ru", + "ssisurveys.com", + "uploadboy.com", + "madpandatv.net", + "pcauto.com.cn", + "meinestadt.de", + "tasnimnews.com", + "rockettheme.com", + "imesh.com", + "home.pl", + "lindaikeji.blogspot.com", + "zamunda.net", + "with2.net", + "calameo.com", + "myonlinearcade.com", + "xcams.com", + "unicredit.it", + "clip2net.com", + "mediatemple.net", + "nosub.tv", + "click.in", + "alluc.to", + "thestar.com.my", + "niksalehi.com", + "lastampa.it", + "itv.com", + "hihi2.com", + "digitalocean.com", + "tripadvisor.fr", + "willhaben.at", + "laodong.com.vn", + "591hx.com", + "tineye.com", + "ptt.cc", + "beatport.com", + "infoseek.co.jp", + "avangate.com", + "indiaproperty.com", + "books.com.tw", + "nj.com", + "avaaz.org", + "wikitravel.org", + "direct.gov.uk", + "wangtu.com", + "discover.com", + "slon.ru", + "startpage.com", + "thestar.com", + "meishichina.com", + "finanzen.net", + "estadao.com.br", + "indeed.co.uk", + "expansion.com", + "irpopup.ir", + "arvixe.com", + "secureinternetbank.com", + "csmonitor.com", + "carwale.com", + "mtime.com", + "ceconline.com", + "abchina.com", + "cgbchina.com.cn", + "mediotiempo.com", + "vic.gov.au", + "cnbeta.com", + "alarab.net", + "vietnamnet.vn", + "gay.com", + "watchmygf.net", + "myanimelist.net", + "bannersbroker.com", + "asklaila.com", + "heureka.cz", + "grepolis.com", + "meteofrance.com", + "bol.com", + "hasoffers.com", + "bigrock.in", + "ibanking-services.com", + "nos.nl", + "google.com.gh", + "beppegrillo.it", + "nhaccuatui.com", + "hamariweb.com", + "wikiquote.org", + "evite.com", + "jobdiagnosis.com", + "17track.net", + "baixing.com", + "awempire.com", + "healthgrades.com", + "unifiedlayer.com", + "thisav.com", + "himado.in", + "zcool.com.cn", + "tsn.ca", + "atlassian.net", + "binary500.com", + "alotporn.com", + "dnaindia.com", + "sherdog.com", + "www.nhs.uk", + "gruposantander.es", + "quantcast.com", + "usearchmedia.com", + "merchantcircle.com", + "blog.hu", + "wallstcheatsheet.com", + "iptorrents.com", + "apsense.com", + "redirsvc.com", + "indiarailinfo.com", + "mobile.ir", + "xvideo-jp.com", + "boxofficemojo.com", + "scamadviser.com", + "rotapost.ru", + "someecards.com", + "mydealz.de", + "lvye.cn", + "videoweed.es", + "xlovecam.com", + "pepperjamnetwork.com", + "khabarpu.com", + "mathrubhumi.com", + "online-convert.com", + "b92.net", + "tiptopsoft.org", + "el-balad.com", + "qihoo.com", + "iheart.com", + "campaign-archive1.com", + "photodune.net", + "labnol.org", + "kotobank.jp", + "medium.com", + "google.lu", + "tm1111.com", + "gigazine.net", + "mypcbackup.com", + "instantcheckmate.com", + "seemorgh.com", + "topky.sk", + "puu.sh", + "aeriagames.com", + "whatismyip.com", + "rising.cn", + "scotiabank.com", + "wo.com.cn", + "yootheme.com", + "channel4.com", + "heavy-r.com", + "omgpm.com", + "milli.az", + "bnpparibas.net", + "vmware.com", + "diretta.it", + "skycn.com", + "sap.com", + "zyalt.livejournal.com", + "bookryanair.com", + "kkbox.com", + "subtitleseeker.com", + "abckj123.com", + "mashreghnews.ir", + "websitewelcome.com", + "2ch-c.net", + "health.com", + "uproxx.com", + "uast.ac.ir", + "bol.uol.com.br", + "feedsportal.com", + "ip-adress.com", + "sedty.com", + "ilsistemabinario.com", + "nabble.com", + "hightail.com", + "superjob.ru", + "tokyo.jp", + "hrblock.com", + "china.cn", + "tsetmc.com", + "unfranchise.com.tw", + "bhg.com", + "htmlbook.ru", + "stardoll.com", + "crictime.com", + "hsn.com", + "royalmail.com", + "globallshare.com", + "wikiwiki.jp", + "petardas.com", + "usembassy.gov", + "rakuten-bank.co.jp", + "golem.de", + "stepstone.de", + "aucfan.com", + "stubhub.com", + "rbcdaily.ru", + "ro2.biz", + "huffingtonpost.ca", + "timeout.com", + "digitalspy.co.uk", + "foreningssparbanken.se", + "autodesk.com", + "98ia.com", + "gtbank.com", + "runetki.com", + "freshdesk.com", + "ieee.org", + "getpocket.com", + "jutarnji.hr", + "caisse-epargne.fr", + "profitcentr.com", + "51.com", + "oi.com.br", + "sina.com.tw", + "fontspace.com", + "dynamicdrive.com", + "maduradas.com", + "ligtv.com.tr", + "sport24.gr", + "stargazete.com", + "bola.net", + "template-help.com", + "codecademy.com", + "officedepot.com", + "researchgate.net", + "17u.com", + "gfxtra.com", + "weiphone.com", + "shoutmeloud.com", + "hc360.com", + "lintas.me", + "mk.ru", + "wplocker.com", + "thumbtack.com", + "szn.cz", + "yatra.com", + "societegenerale.fr", + "wyborcza.pl", + "demotywatory.pl", + "thomann.de", + "imperiaonline.org", + "magento.com", + "skillpages.com", + "cam4ads.com", + "spiceworks.com", + "homestead.com", + "locanto.in", + "hotelurbano.com", + "sinaimg.cn", + "chetxia.com", + "menshealth.com", + "ideacellular.com", + "snopes.com", + "newgrounds.com", + "autosottocosto.com", + "admob.com", + "linguee.fr", + "net-a-porter.com", + "text.ru", + "sharebeast.com", + "televisionfanatic.com", + "netlog.com", + "lufthansa.com", + "ngoisao.net", + "3911.net", + "worldssl.net", + "rabobank.nl", + "lvmama.com", + "sharethis.com", + "sambaporno.com", + "mci.ir", + "inosmi.ru", + "joyreactor.cc", + "cartfill.in", + "cooltext.com", + "qz.com", + "onthe.io", + "citilink.ru", + "apartmenttherapy.com", + "lloydsbank.com", + "metroer.com", + "koyotesoft.com", + "subdivx.com", + "alriyadh.com", + "backlinkwatch.com", + "coindesk.com", + "porntube1.xxx", + "zovi.com", + "thestreet.com", + "rtbstream.com", + "etnet.com.hk", + "creditkarma.com", + "lifebuzz.com", + "opodo.co.uk", + "developpez.net", + "tinydeal.com", + "nature.com", + "oxforddictionaries.com", + "anchorfree.net", + "tn.com.ar", + "findicons.com", + "joomlart.com", + "hmrc.gov.uk", + "btc-e.com", + "extra.com.br", + "ad.nl", + "megafon.ru", + "qafqazinfo.az", + "shahvani.com", + "googlecode.com", + "milliyet.tv", + "deadline.com", + "grindtv.com", + "logitech.com", + "nzherald.co.nz", + "ninisite.com", + "webgains.com", + "oschina.net", + "webmasters.ru", + "filmix.net", + "seowhy.com", + "met-art.com", + "discart.ru", + "keywordblocks.com", + "vodly.to", + "redvak.com", + "tomshardware.co.uk", + "2shared.com", + "zoznam.sk", + "icims.com", + "virtapay.com", + "blomaga.jp", + "wasu.cn", + "carsensor.net", + "sportsdirect.com", + "uservoice.com", + "livescience.com", + "gamebase.com.tw", + "persiangig.com", + "livesmi.com", + "eharmony.com", + "banorte.com", + "radiojavan.com", + "startv.com.tr", + "coolmath-games.com", + "downloadquick.net", + "worldoftanks.eu", + "filelist.ro", + "fontsquirrel.com", + "superpages.com", + "yolasite.com", + "funnie.st", + "51cto.com", + "goo-net.com", + "builtwith.com", + "shaparak.ir", + "nuomi.com", + "omegle.com", + "poringa.net", + "lan.com", + "desi-tashan.com", + "shabdkosh.com", + "freedigitalphotos.net", + "betradar.com", + "womenshealthmag.com", + "realsimple.com", + "maxbounty.com", + "bbva.es", + "sweetpacks.com", + "pinoy-ako.info", + "jobstreet.com", + "cafe24.com", + "goldenbirds.biz", + "anandtech.com", + "mapion.co.jp", + "rawstory.com", + "streamate.com", + "celebuzz.com", + "freepornvs.com", + "divyabhaskar.co.in", + "lifehack.org", + "holidaycheck.de", + "quizlet.com", + "zhibo8.cc", + "askmefast.com", + "manageflitter.com", + "campaign-archive2.com", + "cityheaven.net", + "uniqlo.com", + "seoclerks.com", + "tecmundo.com.br", + "the-binary-options-guide.com", + "gigaom.com", + "mywot.com", + "playcast.ru", + "tatadocomo.com", + "csfd.cz", + "ganool.com", + "kaschpo.ru", + "webdunia.com", + "philly.com", + "doctoroz.com", + "jumei.com", + "poponclick.com", + "rueducommerce.fr", + "fararu.com", + "po.st", + "nextag.com", + "manhunt.net", + "sistrix.com", + "woman.ru", + "appbank.net", + "iobit.com", + "joins.com", + "vidtomp3.com", + "un.org", + "volusion.com", + "lipsum.com", + "afkarnews.ir", + "goldenline.pl", + "clickansave.net", + "softgozar.com", + "thefind.com", + "mozzi.com", + "wenxuecity.com", + "up2c.com", + "searchfunmoods.com", + "cabelas.com", + "coffetube.com", + "otto.de", + "webalta.ru", + "tataindicom.com", + "ctvnews.ca", + "inlinkz.com", + "kajabi.com", + "greatergood.com", + "whmcs.com", + "telekom.de", + "christianpost.com", + "fiducia.de", + "bancsabadell.com", + "pirateproxy.net", + "woobox.com", + "google.cm", + "noulinx.com", + "nick.com", + "moviehuts.com", + "mvideo.ru", + "couchtuner.eu", + "wyborcza.biz", + "tradeindia.com", + "girlsgogames.com", + "healthcare.gov", + "videolan.org", + "sky.it", + "tuniu.com", + "ime.nu", + "n4g.com", + "cyberforum.ru", + "justin.tv", + "createspace.com", + "joemonster.org", + "registro.br", + "elkhabar.com", + "open24news.tv", + "irr.ru", + "sanjesh.org", + "standardbank.co.za", + "neogaf.com", + "doyo.cn", + "am15.net", + "ana.co.jp", + "amd.com", + "gidonlinekino.com", + "sitetalk.com", + "nesn.com", + "arabseed.com", + "ingbank.pl", + "webpronews.com", + "saavn.com", + "yourdictionary.com", + "mobile9.com", + "jagranjosh.com", + "www.net.cn", + "computerbase.de", + "3djuegos.com", + "htc.com", + "the-binary-theorem.com", + "google.com.np", + "n11.com", + "vivastreet.com", + "mthai.com", + "seomastering.com", + "mercadolibre.cl", + "searchmetrics.com", + "mentalfloss.com", + "russia.tv", + "ubi.com", + "dwnews.com", + "ecplaza.net", + "trafficfactory.biz", + "loopnet.com", + "refinery29.com", + "minecraftforum.net", + "moviefone.com", + "bom.gov.au", + "google.com.bh", + "eltiempo.es", + "cuantarazon.com", + "2258.com", + "google.ee", + "nwolb.com", + "bezaat.com", + "eelly.com", + "icmwebserv.com", + "techsmith.com", + "timeweb.ru", + "johnlewis.com", + "independent.ie", + "bankia.es", + "dkb.de", + "gilt.com", + "duolingo.com", + "3file.info", + "cam4.de.com", + "pcanalysis.net", + "br.de", + "vodlocker.com", + "adfoc.us", + "ymlp.com", + "dailyfx.com", + "business2community.com", + "banki.ru", + "yell.com", + "smartresponder.ru", + "yaske.to", + "bokra.net", + "mercadolibre.com", + "viewster.com", + "recipesfinder.com", + "rei.com", + "dogpile.com", + "caf.fr", + "sbrf.ru", + "soha.vn", + "voila.fr", + "rp-online.de", + "members.webs.com", + "duvamis.com", + "nazwa.pl", + "google.com.ni", + "ilovemobi.com", + "retrogamer.com", + "blogtalkradio.com", + "utexas.edu", + "sonyentertainmentnetwork.com", + "dr.dk", + "jjwxc.net", + "service-public.fr", + "care2.com", + "musavat.com", + "h12-media.com", + "smartfren.com", + "immowelt.de", + "discuz.net", + "m1905.com", + "cas.sk", + "bitcoinwisdom.com", + "monografias.com", + "mindbodygreen.com", + "getfirebug.com", + "godlikeproductions.com", + "mydrivers.com", + "voc.com.cn", + "truecaller.com", + "livemook.com", + "i8ti.com", + "agame.com", + "kurir-info.rs", + "urbanoutfitters.com", + "shopping.uol.com.br", + "2ip.ru", + "91.com", + "banquepopulaire.fr", + "rappler.com", + "mint.com", + "jz123.cn", + "zurb.com", + "sport.cz", + "justunfollow.com", + "billionuploads.com", + "thaivisa.com", + "seitwert.de", + "ciudad.com.ar", + "archdaily.com", + "jalopnik.com", + "1news.az", + "dailyfinance.com", + "okitspace.com", + "dn.se", + "el-nacional.com", + "larousse.fr", + "mojo-themes.com", + "draugiem.lv", + "teamtreehouse.com", + "xiu.com", + "ennaharonline.com", + "oricon.co.jp", + "openoffice.org", + "tny.cz", + "forumfree.it", + "youthwant.com.tw", + "khamsat.com", + "vitacost.com", + "alrakoba.net", + "marmiton.org", + "yebhi.com", + "the-binary-trader.biz", + "dominos.com", + "dice.com", + "like4like.org", + "tvmuse.com", + "standardchartered.com", + "bitbucket.org", + "driveropti.net", + "google.hn", + "indiblogger.in", + "torrenthound.com", + "wolframalpha.com", + "experienceproject.com", + "nsw.gov.au", + "olx.pt", + "sciencedaily.com", + "viglink.com", + "plala.or.jp", + "sxsw.com", + "mileroticos.com", + "namejet.com", + "1hai.cn", + "skroutz.gr", + "bilibili.tv", + "travelzoo.com", + "saksfifthavenue.com", + "get-a-fuck-tonight.com", + "barclaycardus.com", + "withgoogle.com", + "whitehouse.gov", + "oprah.com", + "fishwrapper.com", + "softbank.jp", + "slutload.com", + "latercera.cl", + "100bestbuy.com", + "nur.kz", + "topit.me", + "computerhope.com", + "snob.ru", + "rackcdn.com", + "3158.cn", + "artlebedev.ru", + "yobt.com", + "rai.it", + "hs.fi", + "tinhte.vn", + "fotocasa.es", + "nyu.edu", + "dawn.com", + "series.ly", + "movshare.net", + "fineartamerica.com", + "westernunion.com", + "leaseweb.com", + "wayn.com", + "ivi.ru", + "bloomingdales.com", + "pons.com", + "rapidtrk.com", + "bama.ir", + "hackforums.net", + "dfiles.eu", + "intercambiosvirtuales.org", + "sdo.com", + "homes.com", + "colissimo.fr", + "discuss.com.hk", + "afreeca.com", + "rbc.ua", + "novamov.com", + "jungle.gr", + "iol.co.za", + "btcclicks.com", + "mapsofindia.com", + "propellerads.com", + "fab.com", + "princeton.edu", + "reliancebroadband.co.in", + "xdf.cn", + "mako.co.il", + "protothema.gr", + "booloo.com", + "livrariasaraiva.com.br", + "efukt.com", + "acrobat.com", + "globovision.com", + "xero.com", + "sevenforums.com", + "semalt.com", + "audiojungle.net", + "e1.ru", + "quoka.de", + "chinaiiss.com", + "gonzoxxxmovies.com", + "wallpaperswide.com", + "fapvid.com", + "trk4.com", + "forosdelweb.com", + "fark.com", + "my.tv.sohu.com/user/video", + "kaixin001.com", + "ntvspor.net", + "mobafire.com", + "zigwheels.com", + "filmifullizle.com", + "8tracks.com", + "readmanga.eu", + "lelong.com.my", + "planetromeo.com", + "192.com", + "republika.co.id", + "in.gr", + "televisao.uol.com.br", + "kalerkantho.com", + "usairways.com", + "malaysiakini.com", + "anonymouse.org", + "xxxhost.me", + "stuff.co.nz", + "dek-d.com", + "bbvanet.com.mx", + "watchseries-online.eu", + "malaysiaairlines.com", + "policymic.com", + "serviporno.com", + "aprod.hu", + "gelocal.it", + "tradetracker.com", + "cibc.com", + "umich.edu", + "smartshe.com", + "sony.jp", + "taobaocdn.com", + "bestgfx.biz", + "irib.ir", + "aliimg.com", + "esuteru.com", + "healthline.com", + "cnnamador.com", + "sat.gob.mx", + "childsafedownloadx.asia", + "playmillion.com", + "strawberrynet.com", + "alnilin.com", + "trustedreviews.com", + "21cn.com", + "persianv.com", + "baomoi.com", + "firestorage.jp", + "google.co.ke", + "admaimai.com", + "sbnlife.com", + "surveyrouter.com", + "megaindex.ru", + "intodns.com", + "yiqifa.com", + "mixcloud.com", + "softonic.fr", + "bakecaincontrii.com", + "payu.in", + "rakuten-card.co.jp", + "almaany.com", + "grantland.com", + "ricardo.ch", + "heavy.com", + "niusnews.com", + "canstockphoto.com", + "acunn.com", + "diythemes.com", + "canalrcnmsn.com", + "ngs.ru", + "rassd.com", + "groupon.co.in", + "depositfiles.org", + "mango.com", + "theregister.co.uk", + "worldfree4u.com", + "f-lite.ru", + "boingboing.net", + "bangbros.com", + "neteller.com", + "tonicmovies.com", + "p2up.ir", + "cartoonnetwork.com", + "lun.com", + "nk.pl", + "webry.info", + "burningcamel.com", + "mplife.com", + "nationalreview.com", + "williamhill.es", + "sssc.cn", + "col3negoriginal.lk", + "yoast.com", + "svyaznoy.ru", + "chengdu.cn", + "listverse.com", + "lastminute.com", + "guardianlv.com", + "39yst.com", + "bahseazad.ir", + "debian.org", + "tokyo-porn-tube.com", + "ilbe.com", + "upsocl.com", + "balatarin.com", + "charter97.org", + "wpexplorer.com", + "immi.gov.au", + "yokamen.cn", + "esmas.com", + "hbr.org", + "livechatinc.com", + "crazyegg.com", + "palcomp3.com", + "calottery.com", + "wistia.com", + "locaweb.com.br", + "techspot.com", + "bit.ly", + "yammer.com", + "uscis.gov", + "bramjnet.com", + "linio.com.mx", + "tharunaya.co.uk", + "desirulez.net", + "madmimi.com", + "everydayhealth.com", + "secondlife.com", + "ceskatelevize.cz", + "enter.ru", + "glopart.ru", + "woso.cn", + "pitlap.info", + "worldoftanks.com", + "videarn.com", + "dafiti.com.br", + "segundamano.mx", + "eamroomsnacks.com", + "vulture.com", + "humblebundle.com", + "rockstargames.com", + "wsodownloads.info", + "yazete.com", + "pcadvisor.co.uk", + "07073.com", + "modcloth.com", + "attracta.com", + "iol.pt", + "travideos.com", + "sparkpeople.com", + "rankingsandreviews.com", + "entekhab.ir", + "goarticles.com", + "wonderhowto.com", + "kankanews.com", + "rzb.ir", + "p30download.com", + "infojobs.it", + "socialspark.com", + "picstopin.com", + "celebritynetworth.com", + "tweakers.net", + "gi-backoffice.com", + "isbank.com.tr", + "qiyou.com", + "templatic.com", + "cyberpresse.ca", + "consumerreports.org", + "focalprice.com", + "linkpad.ru", + "svd.se", + "desitvforum.net", + "bulbagarden.net", + "showup.tv", + "webgozar.com", + "3dnews.ru", + "spyfu.com", + "bikhir.ma", + "trovaprezzi.it", + "autov.com.cn", + "podnapisi.net", + "tocmai.ro", + "realitatea.net", + "geico.com", + "joydownload.com", + "kddi.com", + "unbounce.com", + "meb.gov.tr", + "anchorfree.us", + "gumtree.pl", + "dbs.com", + "ebayimg.com", + "ptcsolution.com", + "clip.dj", + "samplicio.us", + "47news.jp", + "todo1.com", + "anspress.com", + "24sata.hr", + "cineblog01.tv", + "sport-fm.gr", + "unetenet.com", + "tenki.jp", + "jia.com", + "filefactory.com", + "onedio.com", + "gendai.net", + "nickjr.com", + "finviz.com", + "bytes.com", + "inkthemes.com", + "oriflame.com", + "aporrea.org", + "mazika2day.com", + "baimao.com", + "logme.in", + "downloadactivate.com", + "vista.ir", + "duowan.com", + "sinaapp.com", + "proceso.com.mx", + "xxxconnect.com", + "dvdvideosoft.com", + "nomorerack.com", + "almubasher.com.sa", + "afisha.ru", + "clicksure.com", + "11st.co.kr", + "turkishairlines.com", + "freekaamaal.com", + "findwide.com", + "sbisec.co.jp", + "mythemeshop.com", + "webnode.com", + "cumhuriyet.com.tr", + "morningstar.com", + "trafficshop.com", + "gopro.com", + "jeevansathi.com", + "dasoertliche.de", + "markets.com", + "absa.co.za", + "ikman.lk", + "fmworld.net", + "hyatt.com", + "virgin-atlantic.com", + "tfl.gov.uk", + "immonet.de", + "haodf.com", + "thewrap.com", + "appgame.com", + "hdwallpapers.in", + "canon.com", + "newsle.com", + "jorudan.co.jp", + "pcwelt.de", + "coinbase.com", + "yourgirlfriends.com", + "djmaza.info", + "findagrave.com", + "lainformacion.com", + "teacup.com", + "ezilon.com", + "redbubble.com", + "guru.com", + "brothersoft.com", + "coinmarketcap.com", + "mediabistro.com", + "elfagr.org", + "glavcom.ua", + "jino.ru", + "1e100.net", + "g9g.com", + "join.me", + "ynet.com", + "news247.gr", + "rcom.co.in", + "baby.ru", + "argentglobalnetwork.com", + "hsselite.com", + "foxbusiness.com", + "cookmates.com", + "lidl.de", + "timesofindia.com", + "fakt.pl", + "bgr.com", + "ghatreh.com", + "woxikon.de", + "pontofrio.com.br", + "gizmag.com", + "signup.wordpress.com", + "tre.it", + "gardenweb.com", + "alfalfalfa.com", + "prcm.jp", + "freemail.hu", + "iteye.com", + "katproxy.com", + "kickassunblock.info", + "wisegeek.com", + "zaobao.com", + "vpsdomain4.eu", + "naij.com", + "olx.co.za", + "mihanstore.org", + "gezinti.com", + "vistaprint.in", + "dastelefonbuch.de", + "blackhatteam.com", + "yummly.com", + "minecraft.net", + "justcloud.com", + "biblehub.com", + "argentinawarez.com", + "gandul.info", + "trialpay.com", + "paruvendu.fr", + "khanacademy.org", + "topsy.com", + "angelfire.com", + "nseindia.com", + "seositecheckup.com", + "hostelworld.com", + "zalukaj.tv", + "animeflv.net", + "laptopmag.com", + "tripadvisor.de", + "sec.gov", + "tripadvisor.es", + "oreilly.com", + "kanald.com.tr", + "halifax-online.co.uk", + "extremetracking.com", + "reimageplus.com", + "jal.co.jp", + "vuiviet.net", + "yeslibertin.com", + "pichunter.com", + "empowernetwork.com/aI3zkH7s3g6tZH8TDmh8LA==", + "reforma.com", + "singlessalad.com", + "upenn.edu", + "1ting.com", + "hsbc.com.mx", + "sulia.com", + "yepporn.com", + "scrubtheweb.com", + "e-autopay.com", + "rechargeitnow.com", + "ppomppu.co.kr", + "zemtv.com", + "gofeminin.de", + "peoplestylewatch.com", + "france24.com", + "comodo.com", + "openmace.net", + "maultalk.com", + "fancy.com", + "vodafone.it", + "pokerstrategy.com", + "crunchyroll.com", + "aipai.com", + "3dcartstores.com", + "leancy.com", + "myblogguest.com", + "papajohns.com", + "tuolar.com", + "junkmail.co.za", + "nyc.gov", + "rentalcars.com", + "sovsport.ru", + "marketo.com", + "smbc.co.jp", + "yale.edu", + "pitchfork.com", + "thetoptens.com", + "joyme.com", + "linuxquestions.org", + "htmlgoodies.com", + "laredoute.fr", + "0427d7.se", + "journaldesfemmes.com", + "xici.net", + "alwafd.org", + "daniweb.com", + "mtgox.com", + "jetstar.com", + "plotek.pl", + "jobvite.com", + "adage.com", + "aukro.cz", + "ford.com", + "problogger.net", + "518.com.tw", + "blogbigtime.com", + "nieuwsblad.be", + "register.com", + "24tv.ua", + "aftenposten.no", + "weightwatchers.com", + "smartpassiveincome.com", + "radiosvoboda.org", + "web.com", + "caixin.com", + "digitalmarketer.com", + "soup.io", + "soft98.ir", + "muzofon.com", + "gao7.com", + "freefilmshd.com", + "hsbc.com", + "xhamsterhq.com", + "my.tv.sohu.com/user/reg", + "trade65.com", + "chow.com", + "google.com.kh", + "inps.it", + "seesaa.jp", + "hoovers.com", + "peru21.pe", + "sdpnoticias.com", + "schema.org", + "linode.com", + "theknot.com", + "blesk.cz", + "google.ci", + "loltrk.com", + "vod.pl", + "geenstijl.nl", + "moneymakergroup.com", + "tupaki.com", + "huvrtech.com", + "metafilter.com", + "jumia.com.ng", + "newsvine.com", + "mylant.com", + "accenture.com", + "feedjit.com", + "twipple.jp", + "getgoodlinks.ru", + "radiofarda.com", + "informador.com.mx", + "wisc.edu", + "windows.net", + "zamzar.com", + "nationalpost.com", + "epnet.com", + "naaptol.com", + "x-art.com", + "ebookbrowsee.net", + "marketingland.com", + "thedailyshow.com", + "s-oman.net", + "theme-fusion.com", + "immobiliare.it", + "allvoices.com", + "adroll.com", + "francetvinfo.fr", + "callandput.com", + "entireweb.com", + "instructure.com", + "megacinema.fr", + "djelfa.info", + "flightaware.com", + "magazineluiza.com.br", + "ighome.com", + "mature-beauty.com", + "yuku.com", + "peerfly.com", + "hardware.fr", + "dcinside.com", + "telerik.com", + "pengpeng.com", + "gazetaexpress.com", + "publico.es", + "nwsource.com", + "oneandone.co.uk", + "qoo10.sg", + "aboutus.org", + "adsupplyads.com", + "wa.gov", + "google.co.cr", + "benchmarkemail.com", + "valuedealshopper.com", + "hola.com", + "mizuhobank.co.jp", + "zzstream.li", + "kriesi.at", + "exist.ru", + "nvidia.com", + "giallozafferano.it", + "jusbrasil.com.br", + "konga.com", + "avclub.com", + "reed.co.uk", + "advertig.com", + "ripoffreport.com", + "gooddrama.net", + "pornyaz.com", + "budsgunshop.com", + "78.cn", + "livesurf.ru", + "99114.com", + "pornorama.com", + "marksandspencer.com", + "vmall.com", + "fatcow.com", + "mylife.com", + "irishtimes.com", + "medicalnewstoday.com", + "uiuc.edu", + "groupon.it", + "washington.edu", + "addictinggames.com", + "anonymz.com", + "kindgirls.com", + "jne.co.id", + "guiamais.com.br", + "lyricsfreak.com", + "uloz.to", + "tagesanzeiger.ch", + "fashionandyou.com", + "bollywoodhungama.com", + "nationwide.co.uk", + "kboing.com.br", + "python.org", + "theladbible.com", + "eurosport.com", + "webinarjam.com", + "theme.wordpress.com", + "myfxbook.com", + "allwomenstalk.com", + "ucla.edu", + "opendns.com", + "pornbb.org", + "money.pl", + "domain.com.au", + "standaard.be", + "wanelo.com", + "express.co.uk", + "exam8.com", + "zum.com", + "amtrak.com", + "boots.com", + "800notes.com", + "fabfurnish.com", + "amoureux.com", + "ebizmba.com", + "ujian.cc", + "stargames.at", + "panasonic.com", + "ge.tt", + "scout.com", + "tiexue.net", + "nanapi.jp", + "eclipse.org", + "searchquotes.com", + "wetplace.com", + "lpcloudbox300.com", + "hollyscoop.com", + "bdjobs.com", + "larepublica.pe", + "hizliresim.com", + "yepme.com", + "bitcoincharts.com", + "bsnl.co.in", + "ucwan87.net", + "tianji.com", + "hgtv.com", + "cleanfiles.net", + "moonsy.com", + "tomsguide.com", + "datpiff.com", + "net.hr", + "orange.pl", + "bankrate.com.cn", + "mafiashare.net", + "giaoduc.net.vn", + "sucuri.net", + "ringcentral.com", + "gamingwonderland.com", + "freenet.de", + "cian.ru", + "gayboystube.com", + "gog.com", + "gruenderszene.de", + "econsultancy.com", + "aktuality.sk", + "webrankstats.com", + "j-cast.com", + "popsugar.com", + "mr-guangdong.com", + "on.cc", + "jb51.net", + "lamoda.ru", + "sprashivai.ru", + "animoto.com", + "winzip.com", + "vanityfair.com", + "svt.se", + "noyapps.com", + "mnn.com", + "lancenet.com.br", + "e-monsite.com", + "gazeta.ua", + "cari.com.my", + "coub.com", + "veoh.com", + "kienthuc.net.vn", + "eldorado.ru", + "overclock.net", + "meetic.fr", + "tam.com.br", + "alfemminile.com", + "telmex.com", + "newsbomb.gr", + "funda.nl", + "netcoc.com", + "bhinneka.com", + "layalina.com", + "clubpenguin.com", + "talktalk.co.uk", + "dumpert.nl", + "almastba.com", + "clker.com", + "adweek.com", + "gigya.com", + "disponivel.uol.com.br", + "tomoson.com", + "w.org", + "rfi.fr", + "imgbox.com", + "omniture.com", + "n24.de", + "webdesignrazzi.com", + "medscape.com", + "playboy.com", + "usda.gov", + "cuevana.tv", + "trenitalia.com", + "explosm.net", + "mail.uol.com.br", + "delfi.lv", + "elektroda.pl", + "hulkshare.com", + "kotak.com", + "gi-akademie.ning.com", + "yelp.de", + "lrytas.lt", + "couchsurfing.org", + "drugstore.com", + "ssa.gov", + "seriescoco.com", + "iran-tejarat.com", + "hafiz.gov.sa", + "intellicast.com", + "sub.jp", + "paginasamarillas.es", + "anime44.com", + "babyschool.com.cn", + "advertisernets.com", + "vpsdomain2.eu", + "arcor.de", + "video2mp3.net", + "marthastewart.com", + "hawahome.com", + "articlesbase.com", + "spanishdict.com", + "meilishuo.com", + "grammarly.com", + "sportlemon.tv", + "zend.com", + "bigideamastermind.com", + "hawaaworld.com", + "pagseguro.uol.com.br", + "keywordspy.com", + "7search.com", + "cnki.net", + "aamaadmiparty.org", + "javaplayer.info", + "sidereel.com", + "azcentral.com", + "passportindia.gov.in", + "memurlar.net", + "bouyguestelecom.fr", + "buenastareas.com", + "ernmoneynow.com", + "eurosport.ru", + "blog.163.com", + "pizzahut.com", + "bossip.com", + "webdeveloper.com", + "add-anime.net", + "pimptubed.com", + "movistar.es", + "megashare.sh", + "empireavenue.com", + "btemplates.com", + "amkspor.com", + "payza.com", + "farfesh.com", + "mcgraw-hill.com", + "motor-talk.de", + "purepeople.com", + "i-gamer.net", + "sotmarket.ru", + "anthropologie.com", + "google.org", + "rai.tv", + "wikidot.com", + "canoe.ca", + "orange.es", + "umn.edu", + "emag.ro", + "webutations.org", + "ykb.com", + "canadapost.ca", + "zhubajie.com", + "nextcar.cn", + "next.co.uk", + "freemovie-hd.com", + "telegraf.com.ua", + "ajc.com", + "xsrv.jp", + "vanguard.com", + "mybroadband.co.za", + "999120.net", + "panet.co.il", + "bellemaison.jp", + "fonearena.com", + "playvid.com", + "purdue.edu", + "thesuperficial.com", + "114so.cn", + "irna.ir", + "websitetonight.com", + "edublogs.org", + "provincial.com", + "jahaniha.com", + "heroturko.me", + "dynadot.com", + "txtsrving.info", + "adclickxpress.com", + "sammobile.com", + "ebay.at", + "yaplog.jp", + "imtranslator.net", + "fotor.com", + "myvidster.com", + "williamhill.it", + "argaam.com", + "zoomby.ru", + "tcsbank.ru", + "origin.com", + "vov.vn", + "wrapbootstrap.com", + "imvu.com", + "fbsbx.com", + "elcorteingles.es", + "cerdas.com", + "google.com.om", + "dramasonline.com", + "businesswire.com", + "serpbook.com", + "santanderrio.com.ar", + "meta.ua", + "kenhgioitre.com", + "watchcartoononline.com", + "athome.co.jp", + "2checkout.com", + "moheet.com", + "jeu.info", + "ankieta-online.pl", + "triberr.com", + "wordtracker.com", + "blogher.com", + "colourlovers.com", + "maalaimalar.com", + "bakeca.it", + "thepostgame.com", + "beget.ru", + "abebooks.com", + "certified-toolbar.com", + "publico.pt", + "footmercato.net", + "indiabix.com", + "zakzak.co.jp", + "hotfile.com", + "virustotal.com", + "usgs.gov", + "tukif.com", + "banglamail24.com", + "graphicstock.com", + "svpressa.ru", + "sanalpazar.com", + "logsoku.com", + "loteriasyapuestas.es", + "youjizzlive.com", + "all-inkl.com", + "arcot.com", + "blogspot.fi", + "inbox.lv", + "anno-online.com", + "tikona.in", + "newsru.ua", + "centerblog.net", + "jahannews.com", + "joxi.ru", + "proxybay.eu", + "2gis.ru", + "indeed.fr", + "novayagazeta.ru", + "intelius.com", + "hosteurope.de", + "autoscout24.it", + "postjoint.com", + "pulsk.com", + "biobiochile.cl", + "homevv.com", + "zapbux.com", + "gsp.ro", + "priyo.com", + "jamnews.ir", + "addtoany.com", + "lookbook.nu", + "e-travel.com", + "themelock.com", + "bestsocialfeed.com", + "eqla3.com", + "klm.com", + "realtor.ca", + "tv2.no", + "jqueryrain.com", + "seo-fast.ru", + "academic.ru", + "clicksia.com", + "lpcloudbox30.com", + "worldpay.com", + "gogoanime.com", + "photoshelter.com", + "mlive.com", + "wetteronline.de", + "jkforum.net", + "secure.ipage.com", + "antena3.com", + "elsevier.com", + "thaiseoboard.com", + "addic7ed.com", + "rookee.ru", + "jstor.org", + "rajanews.com", + "uline.com", + "o2online.de", + "fbdownloader.com", + "wetpaint.com", + "nnm.me", + "50onred.com", + "tvtropes.org", + "philips.com", + "watchseries.to", + "perfectinter.net", + "progressive.com", + "stcn.com", + "golsearch.com", + "sarayanews.com", + "screenrant.com", + "macworld.com", + "classifiedads.com", + "ip138.com", + "izlesene.com", + "ovguide.com", + "gametop.com", + "urlm.co", + "isitdownrightnow.com", + "redhat.com", + "stafaband.info", + "biglion.ru", + "twitterfeed.com", + "ttnet.com.tr", + "panasonic.jp", + "loc.gov", + "telstra.com.au", + "jiathis.com", + "perfil.com.ar", + "inquisitr.com", + "ratp.fr", + "libertaddigital.com", + "bni.co.id", + "ycombinator.com", + "paginegialle.it", + "momoshop.com.tw", + "theweek.com", + "tuoitre.vn", + "scriptmafia.org", + "megavod.fr", + "indiavisiontv.com", + "history.com", + "flyeralarm.com", + "salespider.com", + "ard.de", + "dslreports.com", + "tokyo-tube.com", + "brazzersnetwork.com", + "bonanza.com", + "storenvy.com", + "37signals.com", + "jxnews.com.cn", + "1sale.com", + "allanalpass.com", + "yiiframework.com", + "kimsufi.com", + "nastygal.com", + "immoral.jp", + "icq.com", + "loxblog.com", + "jcrew.com", + "diamond.jp", + "nissen.co.jp", + "fxstreet.com", + "15min.lt", + "lifo.gr", + "alef.ir", + "iis.net", + "giphy.com", + "mypopup.ir", + "bsnl.in", + "distractify.com", + "foro20.com", + "chachaba.com", + "fx-trend.com", + "imanhua.com", + "playxn.com", + "gocomics.com", + "lcl.fr", + "hsw.cn", + "ellislab.com", + "persiantools.com", + "coupondunia.in", + "fengniao.com", + "coinmill.com", + "freelancer.in", + "blogdetik.com", + "data.com", + "trafficjunky.net", + "neverblue.com", + "flvto.com", + "lijit.com", + "zaycev.net", + "salamnews.org", + "siliconrus.com", + "dv37.com", + "conrad.de", + "ascii.jp", + "yesfreeporn.com", + "globalpost.com", + "d4000.com", + "dobreprogramy.pl", + "hammihan.com", + "wjunction.com", + "liverpoolfc.tv", + "nordea.fi", + "mail2web.com", + "songkick.com", + "comicbookresources.com", + "jwplayer.com", + "moi.gov.sa", + "iranecar.com", + "kayako.com", + "listal.com", + "technet.com", + "infonews.com", + "fun698.com", + "frys.com", + "vectorstock.com", + "vbox7.com", + "leroymerlin.fr", + "gpotato.eu", + "gulfnews.com", + "hot-sex-tube.com", + "buzzle.com", + "uludagsozluk.com", + "onlinefastpaydayloan.com", + "forumcommunity.net", + "potins.net", + "bwin.es", + "lobstertube.com", + "katestube.com", + "endomondo.com", + "navalny.livejournal.com", + "wayport.net", + "dealspl.us", + "pixhost.org", + "webopedia.com", + "flagcounter.com", + "hotnewhiphop.com", + "matomy.com", + "privat24.ua", + "fuq.com", + "readserver.net", + "haiwainet.cn", + "light-dark.net", + "decolar.com", + "tlbb8.com", + "tomtom.com", + "fontanka.ru", + "tchibo.de", + "slutfinder.com", + "rbc.cn", + "pepperfry.com", + "freshersworld.com", + "mob.org", + "brobible.com", + "phim3s.net", + "parsiblog.com", + "link-assistant.com", + "smotri.com", + "flyertalk.com", + "iefimerida.gr", + "pornyeah.com", + "trustpilot.com", + "wowslider.com", + "sekindo.com", + "shape.com", + "pizap.com", + "bluedart.com", + "cmu.edu", + "shueisha.co.jp", + "royal-search.com", + "123greetings.com", + "albawabhnews.com", + "holiday-weather.com", + "4travel.jp", + "lpcloudsvr203.com", + "panorama.com.ve", + "htcmania.com", + "skorer.tv", + "mymovies.it", + "fileice.net", + "1and1.fr", + "legiaodosherois.com.br", + "tomsguide.fr", + "baymirror.com", + "thesun.co.uk", + "huffingtonpost.es", + "61baobao.com", + "juegos.com", + "joyclub.de", + "qatarairways.com", + "ayosdito.ph", + "translate.ru", + "yithemes.com", + "buyvip.com", + "pearltrees.com", + "thinkprogress.org", + "twitcasting.tv", + "lusongsong.com", + "irecommend.ru", + "aukro.ua", + "eldiario.es", + "pop-music.ir", + "libertytimes.com.tw", + "themalaysianinsider.com", + "paddypower.it", + "delfi.ee", + "appthemes.com", + "tdameritrade.com", + "flashback.org", + "tubelib.com", + "claro.com.br", + "jotform.com", + "bigresource.com", + "libsyn.com", + "sendgrid.com", + "silikonvadisi.tv", + "geo.tv", + "pricegrabber.com", + "futhead.com", + "testberichte.de", + "skybet.com", + "wpcentral.com", + "lesnumeriques.com", + "clientoo.com", + "mathsisfun.com", + "expert.ru", + "maxptp.com", + "mediaite.com", + "regions.com", + "fast-torrent.ru", + "enlacespepito.com", + "redtram.com", + "benesse.ne.jp", + "jappy.de", + "wizzair.com", + "adevarul.ro", + "mhlw.go.jp", + "dev-point.com", + "ibtimes.co.in", + "coinwarz.com", + "slidesharecdn.com", + "classmates.com", + "tsite.jp", + "niazerooz.com", + "jma.go.jp", + "motherjones.com", + "easports.com", + "elbotola.com", + "tripadvisor.com.au", + "rozee.pk", + "vivanuncios.com.mx", + "lashou.com", + "theaustralian.com.au", + "superstarmagazine.com", + "zedge.net", + "hardwarezone.com.sg", + "yyets.com", + "nowgamez.com", + "blogspot.ch", + "gq.com", + "adidas.com", + "uyan.cc", + "dailydot.com", + "xenforo.com", + "ilmessaggero.it", + "state.nj.us", + "16888.com", + "searchengines.guru", + "nascar.com", + "dnb.no", + "smosh.com", + "shinyinnovation.com", + "who.int", + "mybet.com", + "yoo7.com", + "xrea.com", + "fotolog.net", + "smartprix.com", + "esquire.com", + "google.com.cy", + "santander.com.br", + "realclearpolitics.com", + "financereports24.com", + "onlinesoccermanager.com", + "noticiaaldia.com", + "kelkoo.com", + "drakulastream.eu", + "vodafone.de", + "bt.dk", + "greenwichmeantime.com", + "ufc.com", + "webstatsdomain.org", + "taoche.com", + "thinkgeek.com", + "caranddriver.com", + "123-reg.co.uk", + "translit.ru", + "w3school.com.cn", + "zwinky.com", + "inboxdollars.com", + "gratka.pl", + "vecernji.hr", + "taikang.com", + "bolshoyvopros.ru", + "urbita.com", + "api.ning.com", + "alltop.com", + "readwrite.com", + "medhelp.org", + "totaljobs.com", + "accorhotels.com", + "prom.ua", + "cuny.edu", + "livemint.com", + "berlin.de", + "blogspot.hk", + "orkut.com.br", + "lzjl.com", + "metric-conversions.org", + "nationalrail.co.uk", + "oxu.az", + "mql5.com", + "chinaluxus.com", + "linguee.es", + "saudiairlines.com", + "standardmedia.co.ke", + "alkislarlayasiyorum.com", + "apk.tw", + "lesoir.be", + "whatculture.com", + "indiewire.com", + "pccomponentes.com", + "paytm.in", + "ebaypartnernetwork.com", + "haivl.tv", + "e-junkie.com", + "tvp.pl", + "ifilez.org", + "aristeguinoticias.com", + "bangkokpost.com", + "whatmobile.com.pk", + "prodavalnik.com", + "arsenal.com", + "buyma.com", + "torrentday.com", + "sammydress.com", + "blogcu.com", + "imore.com", + "shitaraba.com", + "pasadrexam2014.in", + "songspk.at", + "eforosh.com", + "nocoty.pl", + "koreus.com", + "verticalresponse.com", + "arte.tv", + "ocnk.net", + "cryptsy.com", + "haaretz.com", + "straitstimes.com", + "2domains.ru", + "usnetads.com", + "pracuj.pl", + "edreams.it", + "sba.gov", + "instabang.com", + "domainnamesales.com", + "teensnow.com", + "visualwebsiteoptimizer.com", + "blogos.com", + "santander.com.mx", + "solidfiles.com", + "authorstream.com", + "teslamotors.com", + "tema.livejournal.com", + "kolesa.kz", + "startribune.com", + "vevo.com", + "clien.net", + "3asq.com", + "tvnet.lv", + "mouthshut.com", + "binary-machine.com", + "adultwork.com", + "hypebeast.com", + "itrack.it", + "network-tools.com", + "brandsoftheworld.com", + "comedycentral.com", + "hotscripts.com", + "meristation.com", + "rlsbb.com", + "uzai.com", + "volkskrant.nl", + "terra.com.mx", + "howtoforge.com", + "178.com", + "wickedfire.com", + "dilandau.eu", + "seriales.us", + "xataka.com", + "hktdc.com", + "express.pk", + "surfcanyon.com", + "buildhr.com", + "ouest-france.fr", + "stltoday.com", + "alternet.org", + "site5.com", + "payserve.com", + "society6.com", + "douguo.com", + "prosieben.de", + "verywed.com", + "enikos.gr", + "tubewolf.com", + "openrice.com", + "blankrefer.com", + "gouv.qc.ca", + "torrentfreak.com", + "craveonline.com", + "unfollowers.com", + "deser.pl", + "quanjing.com", + "runetki.tv", + "yepi.com", + "socialbakers.com", + "webmotors.com.br", + "mmo-champion.com", + "globososo.com", + "whois.sc", + "sportskeeda.com", + "socialblade.com", + "jin115.com", + "avjavjav.com", + "thenewstribe.com", + "pinkrod.com", + "blox.pl", + "mediamarkt.de", + "vivo.com.br", + "facilisimo.com", + "sinarharian.com.my", + "leguide.com", + "voegol.com.br", + "j.gs", + "okgj.com", + "revolveclothing.com", + "lolinez.com", + "go2cloud.org", + "vmoptions.com", + "myip.ms", + "ay.gy", + "liquidweb.com", + "sbi.co.in", + "alamaula.com", + "reshareable.tv", + "stirileprotv.ro", + "siriusxm.com", + "banvenez.com", + "sergey-mavrodi-mmm.net", + "ocj.com.cn", + "elderscrollsonline.com", + "webaslan.com", + "dereferer.org", + "newsbeast.gr", + "778669.com", + "yeucahat.com", + "guitarcenter.com", + "mercurynews.com", + "channeladvisor.com", + "go2000.com", + "google.ge", + "pandora.tv", + "haqqin.az", + "google.tt", + "32d1d3b9c.se", + "sexdatenow.net", + "befunky.com", + "micromaxinfo.com", + "jquerymobile.com", + "radikal.ru", + "wigetmedia.com", + "girlsplay.com", + "pureleverage.com", + "mathxl.com", + "themefuse.com", + "giga.de", + "freevideo.cz", + "stylebistro.com", + "iwriter.com", + "winporn.com", + "tripadvisor.ca", + "atresplayer.com", + "igma.tv", + "ravelry.com", + "semana.com", + "verycd.com", + "sblo.jp", + "fansided.com", + "goalunited.org", + "petfinder.com", + "worthofweb.com", + "memuruz.net", + "njuskalo.hr", + "yellowpages.ca", + "91jm.com", + "webconfs.com", + "interpals.net", + "erepublik.com", + "akismet.com", + "gaadi.com", + "casasbahia.com.br", + "techsupportalert.com", + "bostonglobe.com", + "uploadable.ch", + "smartprofitsystem.com", + "cimbclicks.com.my", + "uwants.com", + "gamestar.de", + "neowin.net", + "inilah.com", + "toster.ru", + "gosuslugi.ru", + "windguru.cz", + "3366.com", + "beinsports.net", + "vector.co.jp", + "memecenter.com", + "hotnews.ro", + "webgame.web.id", + "llnw.com", + "jobstreet.co.id", + "congratulations-you-won.com", + "libertatea.ro", + "app111.com", + "scientificamerican.com", + "strava.com", + "iranjib.ir", + "coneco.net", + "fdrmx.com", + "nps.gov", + "businessinsider.in", + "uniblue.com", + "proporn.com", + "dm5.com", + "qld.gov.au", + "nexusmods.com", + "mandatory.com", + "moskva.fm", + "prosperityteam.com", + "lent.az", + "appbrain.com", + "24smi.org", + "avaxsearch.com", + "gorillavid.in", + "realgm.com", + "favicon.cc", + "astro.com", + "joomlaforum.ru", + "joystiq.com", + "computerworld.com", + "tvspielfilm.de", + "centos.org", + "bugun.com.tr", + "amctv.com", + "relianceada.com", + "papystreaming.com", + "clickindia.com", + "sayyac.com", + "q.gs", + "bravoerotica.com", + "bibsonomy.org", + "netload.in", + "shopatron.com", + "iautos.cn", + "banggood.com", + "commerzbanking.de", + "legacyclix.com", + "intesasanpaolo.com", + "awesomescreenshot.com", + "desidime.com", + "eporner.com", + "alwatanvoice.com", + "ok.ru", + "europe1.fr", + "karnaval.com", + "tornn-tv.com", + "frmtr.com", + "pulscen.ru", + "groupon.co.uk", + "tradera.com", + "comingsoon.net", + "speckyboy.com", + "cargurus.com", + "citysearch.com", + "brother.com", + "acfun.tv", + "jamejamonline.ir", + "enterprise.com", + "elotrolado.net", + "userscripts.org", + "confirmit.com", + "compete.com", + "auctiva.com", + "potterybarn.com", + "ivoox.com", + "multiupload.nl", + "glavnoe.ua", + "p30world.com", + "snapnames.com", + "filmon.com", + "flipkey.com", + "rhhbschool.com", + "worldbank.org", + "proz.com", + "docusign.net", + "akakce.com", + "empflix.com", + "androidforums.com", + "mos.ru", + "contenko.com", + "malwarebytes.org", + "voyeurhit.com", + "filmvz.com", + "windowsazure.com", + "rlslog.net", + "zulagames.com", + "onlinefinder.net", + "blekko.com", + "tvb.com", + "mojomarketplace.com", + "warnerbros.com", + "rogers.com", + "sparkasse.at", + "bigcinema.tv", + "mattcutts.com", + "vemale.com", + "pgatour.com", + "bakusai.com", + "lavozdegalicia.es", + "style.com", + "torrentino.com", + "activeden.net", + "sweetpacks-search.com", + "pcstore.com.tw", + "starmedia.com", + "lycos.com", + "uncomo.com", + "caribbeancom.com", + "googleping.com", + "sondakika.com", + "premiumwp.com", + "tribune.com.pk", + "sc.com", + "91mobiles.com", + "elle.com", + "motortrend.com", + "starsports.com", + "otlan.com", + "planetminecraft.com", + "oregonlive.com", + "publichd.se", + "financialexpress.com", + "huxiu.com", + "hotwords.com", + "asda.com", + "impiego24.it", + "tunisia-sat.com", + "xojane.com", + "shiftdelete.net", + "hamshahrionline.ir", + "mp3.es", + "minutebuzz.com", + "footlocker.com", + "rusnovosti.ru", + "defencenet.gr", + "ndr.de", + "blogosfera.uol.com.br", + "quibids.com", + "soft32.com", + "davidwalsh.name", + "blogun.ru", + "webbirga.net", + "dict.cn", + "groupon.jp", + "abovetopsecret.com", + "contra.gr", + "zn.ua", + "jetbrains.com", + "myus.com", + "redbus.in", + "creativemarket.com", + "uainfo.org", + "rtbf.be", + "forexpf.ru", + "mainlink.ru", + "fantasti.cc", + "usagc.org", + "narutoget.com", + "trendyol.com", + "carsales.com.au", + "fujitv.co.jp", + "dardarkom.com", + "corrieredellosport.it", + "mcanime.net", + "nolo.com", + "beareyes.com.cn", + "familysearch.org", + "eprice.com.tw", + "indonetwork.co.id", + "geizhals.at", + "watchfreemovies.ch", + "siweiw.com", + "elnuevoherald.com", + "codecupdaters.com", + "p1.com", + "imhonet.ru", + "spox.com", + "spreadshirt.com", + "bittorrent.com", + "airberlin.com", + "epicurious.com", + "adverstitial.com", + "researchnow.com", + "lolnexus.com", + "xbabe.com", + "designmodo.com", + "hamusoku.com", + "radioshack.com", + "teamliquid.net", + "helpster.de", + "globalsources.com", + "fasttech.com", + "dickssportinggoods.com", + "j-sen.jp", + "smashwords.com", + "adversal.com", + "musiciansfriend.com", + "whoishostingthis.com", + "ads8.com", + "trend4pay.com", + "tribune.com", + "xsrving.com", + "youngleafs.com", + "gamersky.com", + "podio.com", + "state.pa.us", + "iphoneogram.com", + "datehookup.com", + "hbs.edu", + "ally.com", + "mitbbs.com", + "siasat.pk", + "miratuserie.tv", + "active.com", + "ricardoeletro.com.br", + "html.it", + "minnano-av.com", + "meetly.in", + "denverpost.com", + "supersport.com", + "grupobancolombia.com", + "freemake.com", + "bankier.pl", + "instapaper.com", + "derwesten.de", + "blogspot.hu", + "tamindir.com", + "play.pl", + "warthunder.com", + "team-bhp.com", + "freepeople.com", + "laposte.fr", + "cnews.ru", + "fayerwayer.com", + "hitta.se", + "hao123.com.eg", + "it-ebooks.info", + "freep.com", + "newser.com", + "lexilogos.com", + "polldaddy.com", + "auspost.com.au", + "aib.ie", + "cs.com.cn", + "providesupport.com", + "unblocksit.es", + "twittercounter.com", + "luisaviaroma.com", + "imgbabes.com", + "tharunee.lk", + "nexon.com", + "airliners.net", + "srf.ch", + "angel.co", + "tokyo-sports.co.jp", + "mt5.com", + "down1oads.com", + "vodonet.net", + "axar.az", + "taknaz.ir", + "google.com.py", + "ss.lv", + "stuffgate.com", + "giveawayoftheday.com", + "climatempo.com.br", + "xnxxmovies.com", + "heartinternet.co.uk", + "digikey.com", + "zimbra.free.fr", + "line.me", + "talkingpointsmemo.com", + "dyn.com", + "exacttarget.com", + "yourtango.com", + "shopping.com", + "king.com", + "crackberry.com", + "sm3na.com", + "matchesfashion.com", + "rk.com", + "ems.com.cn", + "xiaomi.cn", + "hillnews.com", + "geforce.com", + "internetslang.com", + "theoldreader.com", + "digitaljournal.com", + "aol.co.uk", + "straightdope.com", + "farfetch.com", + "lupoporno.com", + "ilgiornale.it", + "binzhi.com", + "loveplanet.ru", + "sogi.com.tw", + "googlevideo.com", + "nintendo.com", + "jreast.co.jp", + "coinad.com", + "keek.com", + "chegg.com", + "sparknotes.com", + "mywapblog.com", + "idlebrain.com", + "boardgamegeek.com", + "daserste.de", + "nicozon.net", + "ampnetzwerk.de", + "agenziaentrate.gov.it", + "apec.fr", + "redbull.com", + "listentoyoutube.com", + "diepresse.com", + "gohappy.com.tw", + "morguefile.com", + "dallasnews.com", + "adxpansion.com", + "almos3a.com", + "updatetube.com", + "lowyat.net", + "lolipop.jp", + "terafile.co", + "eversave.com", + "sportingnews.com", + "517dv.com", + "milfmovs.com", + "myflorida.com", + "brooonzyah.net", + "cdn-cachefront.net", + "ig.com", + "indiapost.gov.in", + "hangame.co.jp", + "ithemes.com", + "monova.org", + "udmserve.net", + "impactradius.com", + "teasernet.com", + "gmx.at", + "mmotraffic.com", + "citehr.com", + "viator.com", + "rt.ru", + "japannetbank.co.jp", + "zonealarm.com", + "ebayclassifieds.com", + "safecart.com", + "proptp.net", + "51hejia.com", + "telkomsel.com", + "deutschepost.de", + "startpagina.nl", + "alexaboostup.com", + "istgah.com", + "aufreeads.com", + "thinkstockphotos.com", + "similarsitesearch.com", + "gossiplankanews.com", + "venere.com", + "javascriptkit.com", + "square-enix.com", + "tvtoday.de", + "netaffiliation.com", + "computrabajo.com.mx", + "arioo.com", + "fromdoctopdf.com", + "lookany.com", + "bleepingcomputer.com", + "perfectmoney.com", + "dorkly.com", + "macmillandictionary.com", + "fotka.pl", + "porntubevidz.com", + "rj.gov.br", + "1.1.1.1", + "ebay.be", + "divxplanet.com", + "cyworld.com", + "biography.com", + "p1.cn", + "bazos.sk", + "55.la", + "lockerdome.com", + "yola.com", + "ccbill.com", + "groupon.de", + "okazii.ro", + "izismile.com", + "velvet.hu", + "news-postseven.com", + "ardmediathek.de", + "sagawa-exp.co.jp", + "sarzamindownload.com", + "pdfonline.com", + "gaaks.com", + "azhibo.com", + "pensador.uol.com.br", + "surveygizmo.com", + "familydoctor.com.cn", + "lazada.vn", + "guokr.com", + "enjoydressup.com", + "sky.de", + "afp.com", + "imujer.com", + "livescore.net", + "xinnet.com", + "mypageresults.com", + "safaribooksonline.com", + "extremetech.com", + "ecnavi.jp", + "babyblog.ru", + "practicalecommerce.com", + "vidspot.net", + "studymode.com", + "nation.co.ke", + "cpmrocket.com", + "es.wix.com", + "google.com.cu", + "pravda.ru", + "cda.pl", + "appszoom.com", + "selfhtml.org", + "zmovie.tw", + "dba.dk", + "domain.com", + "brightcove.com", + "lol55.com", + "textsale.ru", + "casualclub.com", + "gudvin.tv", + "torrentreactor.net", + "downloadcamp.com", + "eset.com", + "softonic.it", + "internations.org", + "retre.org", + "3quan.com", + "companieshouse.gov.uk", + "hautelook.com", + "edx.org", + "ono.es", + "jango.com", + "7sur7.be", + "spinrewriter.com", + "hqxnxx.com", + "elperiodico.com", + "arabiaweather.com", + "liansuo.com", + "pcgamer.com", + "offervault.com", + "mytheresa.com", + "memegenerator.net", + "gsm.ir", + "britannica.com", + "mensfitness.com", + "flaticon.com", + "newrelic.com", + "greatmmos.com", + "ifixit.com", + "aiyellow.com", + "my.tv.sohu.com/user/card", + "ce4arab.com", + "news-us.jp", + "slando.kz", + "redflagdeals.com", + "gsmhosting.com", + "raiffeisen.at", + "tagesspiegel.de", + "epravda.com.ua", + "bell.ca", + "sleazyneasy.com", + "dhnet.be", + "webcars.com.cn", + "freepik.es", + "androidpolice.com", + "cbr.ru", + "thanhnien.com.vn", + "shoptime.com.br", + "dailytelegraph.com.au", + "fastcodesign.com", + "full-stream.net", + "google.al", + "creativecow.net", + "centrum24.pl", + "openstat.ru", + "unfollowed.me", + "brb.to", + "babal.net", + "ibotoolbox.com", + "torrentdownloads.me", + "miami.com", + "mubasher.info", + "patient.co.uk", + "ayudawordpress.com", + "makepolo.com", + "exactseek.com", + "echosign.com", + "epson.com", + "somo.vn", + "tripadvisor.jp", + "bikroy.com", + "postimees.ee", + "band.uol.com.br", + "adopteunmec.com", + "imgserve.net", + "toofab.com", + "neeu.com", + "mailjet.com", + "kinja.com", + "qwertypay.com", + "cosme.net", + "dreammovies.com", + "avvo.com", + "wenyard.com", + "dir.bg", + "digital-photography-school.com", + "peru.com", + "thomsonreuters.com", + "grazia.com.cn", + "vivastreet.co.in", + "officemax.com", + "creditmutuel.fr", + "themify.me", + "ait-themes.com", + "share-links.biz", + "graphixshare.com", + "theoutnet.com", + "revolvermaps.com", + "careers360.com", + "tyroodr.com", + "escapistmagazine.com", + "ofreegames.com", + "surveryewadcentrez.eu", + "notebookcheck.net", + "k2s.cc", + "barstoolsports.com", + "townhall.com", + "mk.co.kr", + "epochtimes.com", + "arduino.cc", + "blogcms.jp", + "udacity.com", + "habervaktim.com", + "funweek.it", + "techtunes.com.bd", + "shopzilla.com", + "sun-sentinel.com", + "tfile.me", + "novafile.com", + "eurogamer.net", + "thenewporn.com", + "tuttosport.com", + "sverve.com", + "simplemachines.org", + "nginx.org", + "kepu.com.cn", + "jxedt.com", + "southparkstudios.com", + "gusuwang.com", + "utrace.de", + "peperonity.de", + "hvg.hu", + "cairodar.com", + "mappy.com", + "kouclo.com", + "my.tv.sohu.com/user/lucky", + "lushstories.com", + "stocktwits.com", + "atlantico.fr", + "baihe.com", + "wowkeren.com", + "v7n.com", + "mozilla.com", + "centurylink.net", + "1001freefonts.com", + "pubdirecte.com", + "mercadolibre.com.pe", + "vandelaydesign.com", + "cpaelites.com", + "wanyh.com", + "propertyguru.com.sg", + "esy.es", + "torrentbutler.eu", + "final.ir", + "53.com", + "clips4sale.com", + "freerepublic.com", + "huffingtonpost.de", + "boards.ie", + "fluege.de", + "parents.com", + "gimp.org", + "adorocinema.com", + "yelp.ca", + "twicsy.com", + "kuaibo.com", + "gnu.org", + "parsine.com", + "jpc.com", + "cloudapp.net", + "expatriates.com", + "sproutsocial.com", + "natemat.pl", + "livingrichwithcoupons.com", + "x-ho.com", + "adv-adserver.com", + "downloadchop.com", + "sagepub.com", + "eleconomista.com.mx", + "formula1.com", + "quackit.com", + "dlsite.com", + "powned.tv", + "stream-tv.me", + "mindbodyonline.com", + "mindmeister.com", + "twitlonger.com", + "ebay.ie", + "deccanchronicle.com", + "domaintuno.com", + "thethirdmedia.com", + "laiguana.tv", + "flurry.com", + "eloqua.com", + "createsend.com", + "hrloo.com", + "interfax.com.ua", + "admin.ch", + "zgzcw.com", + "bancoestado.cl", + "fda.gov", + "chinayes.com", + "beslist.nl", + "crossfit.com", + "skai.gr", + "covers.com", + "antpoker.com", + "perfectworld.com", + "momdot.com", + "1c-bitrix.ru", + "feiren.com", + "navitime.co.jp", + "siliconindia.com", + "warning.or.kr", + "qatarliving.com", + "uber.com", + "ing-diba.de", + "boldsky.com", + "animeid.tv", + "tom.com", + "whatsmyserp.com", + "ftbpro.com", + "yobt.tv", + "toluna.com", + "burrp.com", + "viber.com", + "df.eu", + "vatera.hu", + "songmeanings.com", + "smbc-card.com", + "hip2save.com", + "wmzona.com", + "banamex.com", + "chinaacc.com", + "t-online.hu", + "indianpornvideos.com", + "runescape.com", + "wapka.mobi", + "115.com", + "blogspot.no", + "aksam.com.tr", + "bankcomm.com", + "giantbomb.com", + "maxcdn.com", + "topshop.com", + "livescores.com", + "rosbalt.ru", + "gandi.net", + "ioffer.com", + "mihandownload.com", + "eweb4.com", + "britishcouncil.org", + "cheetahmail.com", + "vbulletin.com", + "bradsdeals.com", + "empowernetwork.com/commissionloophole", + "vueling.com", + "raventools.com", + "yext.com", + "everychina.com", + "reduxmediia.com", + "openstreetmap.org", + "wed114.cn", + "dzwww.com", + "miibeian.gov.cn", + "ufl.edu", + "deseretnews.com", + "presstv.ir", + "copy.com", + "fourhourworkweek.com", + "buzztheme.net", + "bollywoodlife.com", + "redtubelive.com", + "pengyou.com", + "antaranews.com", + "selectornews.com", + "lavoz.com.ar", + "avm.de", + "flalottery.com", + "jmp9.com", + "baltimoresun.com", + "mozillazine.org", + "dnevnik.hr", + "aebn.net", + "mightydeals.com", + "livescore.eu", + "publipt.com", + "xbmc.org", + "scmp.com", + "sbobet.com", + "alpari.ru", + "harborfreight.com", + "linio.com.pe", + "2500sz.com", + "rdio.com", + "bearshare.com", + "thetrainline.com", + "888poker.es", + "filecrop.com", + "twiends.com", + "elnuevodia.com", + "rasekhoon.net", + "36.cn", + "axeso5.com", + "mzamin.com", + "webmastersitesi.com", + "thinkdigit.com", + "lifescript.com", + "experian.com", + "adorama.com", + "d1net.com", + "torrentz.pro", + "fullhdfilmizle.org", + "space.com", + "collider.com", + "zalando.it", + "slate.fr", + "carmax.com", + "recode.net", + "gfan.com", + "fonts.com", + "honda.com", + "ecwid.com", + "debate.com.mx", + "bmail.uol.com.br", + "santandernet.com.br", + "coderanch.com", + "lyricsmode.com", + "gametrailers.com", + "3600.com", + "designboom.com", + "131.com", + "finam.ru", + "df.gob.mx", + "85dcf732d593.se", + "coches.net", + "dhs.gov", + "neurs.com", + "poppen.de", + "public-api.wordpress.com", + "magnovideo.com", + "squareup.com", + "balagana.net", + "goodsearch.com", + "blog.ir", + "ne10.uol.com.br", + "di.se", + "sinembargo.mx", + "k618.cn", + "eventim.de", + "mysavings.com", + "futureshop.ca", + "spinding.com", + "landsend.com", + "oqenadserving.com", + "here.com", + "upi.com", + "picresize.com", + "abnamro.nl", + "filenuke.com", + "yxku.com", + "mypearson.com", + "dinakaran.com", + "correos.es", + "hotchatdirect.com", + "markafoni.com", + "gofuckbiz.com", + "webfindpage.com", + "cancan.ro", + "hostgator.in", + "latribune.fr", + "adxite.com", + "mofos.com", + "bluewin.ch", + "24ur.com", + "dion.ne.jp", + "vpsdomain3.eu", + "newsweek.com", + "oasgames.com", + "mtv3.fi", + "internetworld.de", + "yatedo.com", + "globaltimes.cn", + "paddypower.com", + "berlin1.de", + "norwegian.com", + "opteck.com", + "zenfolio.com", + "kakprosto.ru", + "ecosia.org", + "check24.de", + "idbibank.co.in", + "zoomit.ir", + "notebooksbilliger.de", + "torontosun.com", + "egopay.com", + "cnnexpansion.com", + "emoneyspace.com", + "rsport.ru", + "fok.nl", + "mql4.com", + "inhabitat.com", + "tqn.com", + "noticierodigital.com", + "wrike.com", + "bazos.cz", + "phonegap.com", + "microlancer.com", + "anipo.jp", + "fotocommunity.de", + "kuwo.cn", + "chinacaipu.com", + "terra.cl", + "500px.org", + "qantas.com.au", + "advertising.com", + "push2check.net", + "livefyre.com", + "philstar.com", + "erail.in", + "mywebgrocer.com", + "der-postillon.com", + "ru-board.com", + "saramin.co.kr", + "yjbys.com", + "aaa.com", + "flavorwire.com", + "fcbarcelona.com", + "lacentrale.fr", + "googlegroups.com", + "luck4.me", + "catho.com.br", + "centauro.com.br", + "sia.az", + "forumactif.com", + "paltalk.com", + "beyond.com", + "occ.com.mx", + "stamps.com", + "getoffmyinternets.net", + "mbok.jp", + "cleveland.com", + "ucdavis.edu", + "tielabs.com", + "hkgolden.com", + "vandal.net", + "lyst.com", + "mrskin.com", + "bunshun.jp", + "netcarshow.com", + "b1.org", + "hromadske.tv", + "sf-express.com", + "pirateproxy.ca", + "getcashforsurveys.com", + "clikz4freakz.com", + "adsupply.com", + "truste.org", + "boxuu.com", + "webartex.ru", + "usc.edu", + "moo.com", + "winkal.com", + "deloitte.com", + "xvideos-field5.com", + "suomi24.fi", + "flexmls.com", + "weeklystandard.com", + "debenhams.com", + "mejortorrent.com", + "vconnect.com", + "ligatus.com", + "skynewsarabia.com", + "herschina.com", + "92lux.cn", + "care.com", + "chip.com.tr", + "tangdou.com", + "vatanbilgisayar.com", + "uppit.com", + "metronews.fr", + "parenting.com.tw", + "meteo.gr", + "meetgee.com", + "ldlc.com", + "ubs.com", + "elcolombiano.com", + "mafengwo.cn", + "moodle.org", + "asiaone.com", + "huffingtonpost.it", + "ipas2free.com", + "casa.it", + "kaiserpermanente.org", + "listindiario.com", + "treehugger.com", + "payeer.com", + "mypoints.com", + "pjmedia.com", + "foreca.com", + "luxup.ru", + "halifax.co.uk", + "webinarjam.net", + "webmeup.com", + "webdesignledger.com", + "rte.ie", + "18andabused.com", + "vbulletin.org", + "apne.tv", + "immoweb.be", + "colonelcassad.livejournal.com", + "atlassian.com", + "investors.com", + "globalbux.net", + "wechat.com", + "kanoon.ir", + "mawaly.com", + "self.com", + "drive.ru", + "autozone.com", + "creditonebank.com", + "fdj.fr", + "hsbc.com.br", + "desk.com", + "dlvr.it", + "wpml.org", + "newmobilelife.com", + "crosswalk.com", + "thehun.com", + "hobbyking.com", + "tiantian.com", + "hichina.com", + "cyanogenmod.org", + "horoscope.com", + "bizsugar.com", + "popularmechanics.com", + "egaliteetreconciliation.fr", + "eol.cn", + "rzd.ru", + "escalatenetwork.net", + "yad2.co.il", + "cna.com.tw", + "zi-m.com", + "leonardo.it", + "ap.gov.in", + "rakuten.de", + "wallsave.com", + "netzwelt.de", + "bitrix24.ru", + "techinasia.com", + "bitstamp.net", + "hostelbookers.com", + "shoplocal.com", + "200please.com", + "tune-up.com", + "mapy.cz", + "dealnews.com", + "livescore.co.uk", + "seocentro.com", + "rstyle.me", + "neoseeker.com", + "kaban.tv", + "visualstudio.com", + "cinetux.org", + "groupon.com.br", + "lastpass.com", + "rincondelvago.com", + "popsci.com", + "2144.cn", + "clickfair.com", + "dsebd.org", + "banesco.com", + "spi0n.com", + "blogspot.cz", + "tnews.ir", + "nme.com", + "paperblog.com", + "firstcry.com", + "tophotels.ru", + "liepin.com", + "korabia.com", + "ifttt.com", + "aemet.es", + "alot.com", + "manutd.com", + "edreams.es", + "turkcell.com.tr", + "eva.vn", + "techpowerup.com", + "vkrugudruzei.ru", + "feed2all.eu", + "thehindubusinessline.com", + "express.de", + "ixquick.com", + "thrillist.com", + "iqilu.com", + "seriouseats.com", + "edgesuite.net", + "telexads.com", + "alza.cz", + "en-japan.com", + "al.com", + "dota2lounge.com", + "totalping.com", + "seocheki.net", + "iceporn.com", + "col3negoriginel.lk", + "porsyar.com", + "property24.com", + "madamenoire.com", + "markethealth.com", + "alphacoders.com", + "webempresa.com", + "empireonline.com", + "downforeveryoneorjustme.com", + "lankadeepa.lk", + "darty.com", + "census.gov", + "wapka.me", + "kdslife.com", + "jpost.com", + "cricfree.tv", + "7xxxtube.com", + "businesscatalyst.com", + "dilbert.com", + "gaymaletube.com", + "bellanaija.com", + "renfe.es", + "equifax.com", + "pornmaki.com", + "lolesports.com", + "yunfan.com", + "everybitcity.com", + "9yao.com", + "avantlink.com", + "paginebianche.it", + "xrosview.com", + "newsday.com", + "usportnews.com", + "petapixel.com", + "pistonheads.com", + "lidovky.cz", + "cvent.com", + "adlandpro.com", + "wikisource.org", + "ename.com", + "prosport.ro", + "sharebuilder.com", + "prisjakt.nu", + "cw.com.tw", + "datafilehost.com", + "aircanada.com", + "osu.edu", + "linuxmint.com", + "toyota.com", + "google.am", + "adobeconnect.com", + "h33t.to", + "companycheck.co.uk", + "juno.com", + "beitaichufang.com", + "alaskaair.com", + "tipsandtricks-hq.com", + "yourstory.com", + "wow-impulse.ru", + "wishpond.com", + "promosite.ru", + "weddingwire.com", + "icicidirect.com", + "mouser.com", + "shopstyle.co.uk", + "sinemalar.com", + "addurl.nu", + "seccionamarilla.com.mx", + "yllix.com", + "motorola.com", + "huawei.com", + "mp3juices.com", + "fitday.com", + "pakwheels.com", + "uploading.com", + "snapwidget.com", + "rsb.ru", + "mturk.com", + "libreoffice.org", + "acer.com", + "rd.com", + "aa.com.tr", + "glamour.com", + "docnhat.net", + "cnbb.com.cn", + "piwik.org", + "mycompanyadmin.com", + "ohozaa.com", + "fox.com", + "mastercard.com.au", + "microsofttranslator.com", + "myhabit.com", + "acesso.uol.com.br", + "cqnews.net", + "default-search.net", + "juntadeandalucia.es", + "carigold.com", + "tweepi.com", + "gamedog.cn", + "videomega.tv", + "softarchive.net", + "akamai.com", + "adorika.net", + "zorpia.com", + "bitdefender.com", + "formstack.com", + "al3abbarq.com", + "ruelala.com", + "andhrajyothy.com", + "siteheart.com", + "spankbang.com", + "buxp.org", + "subtlepatterns.com", + "cucirca.eu", + "apontador.com.br", + "sbs.co.kr", + "rong360.com", + "billmelater.com", + "nowdownload.ch", + "pingmyurl.com", + "main.jp", + "allabolag.se", + "fastspring.com", + "20d625b48e.se", + "wpzoom.com", + "dl-protect.com", + "onvista.de", + "iberia.com", + "learni.st", + "minijuegos.com", + "ae.com", + "india-forums.com", + "port.hu", + "truelocal.com.au", + "ziprecruiter.com", + "toyokeizai.net", + "24timezones.com", + "tzetze.it", + "the-village.ru", + "ptcapusa.com", + "indiaresults.com", + "desjardins.com", + "nchsoftware.com", + "yasdl.com", + "delo.ua", + "baskino.com", + "salary.com", + "freecontact.com", + "wumii.com", + "ssc.nic.in", + "synology.com", + "nttdocomo.co.jp", + "racing-games.com", + "freeonlinegames.com", + "aftabnews.ir", + "zumi.pl", + "efshop.com.tw", + "hawamer.com", + "eastbay.com", + "izvestia.ru", + "mosaiquefm.net", + "twitchy.com", + "slaati.com", + "bookingbuddy.com", + "networksolutionsemail.com", + "etsystatic.com", + "sina.com", + "asiandatingbeauties.com", + "blogs.com", + "france3.fr", + "healthcentral.com", + "downloadatoz.com", + "matthewwoodward.co.uk", + "legalzoom.com", + "phun.org", + "campaignmonitor.com", + "cifraclub.com.br", + "hir24.hu", + "twoplustwo.com", + "hotshame.com", + "arvixededicated.com", + "designcrowd.com", + "mrexcel.com", + "blu-ray.com", + "bundesliga.de", + "comments.ua", + "filmstarts.de", + "3sk.tv", + "donedeal.ie", + "natunbarta.com", + "forex-mmcis.com", + "lifehacker.ru", + "sitepronews.com", + "tudogostoso.com.br", + "titanfall.com", + "brainpickings.org", + "saharareporters.com", + "vidto.me", + "mlit.go.jp", + "terapeak.com", + "techweb.com.cn", + "premiere.fr", + "kanui.com.br", + "asianetnews.tv", + "cric.lk", + "exxxtrasmall.com", + "hiphopdx.com", + "5617.com", + "namepros.com", + "humanmetrics.com", + "allin1convert.com", + "ox.ac.uk", + "msecnd.net", + "cathaypacific.com", + "bet365affiliates.com", + "tcs.com", + "trafficestimate.com", + "lmgtfy.com", + "finance.ua", + "find404.com", + "pagesperso-orange.fr", + "curbed.com", + "tasteofhome.com", + "ar15.com", + "mydigitallife.info", + "promiflash.de", + "technobuffalo.com", + "gamestorrents.com", + "labirint.ru", + "pclab.pl", + "indiatvnews.com", + "finya.de", + "redmondpie.com", + "rabota.ru", + "share-online.biz", + "packtpub.com", + "techgig.com", + "shaw.ca", + "apkmania.co", + "bet-at-home.com", + "barclaycard.co.uk", + "onefloorserve.com", + "raiffeisen.ru", + "t-nation.com", + "mbalib.com", + "staseraintv.com", + "uswitch.com", + "smartadserver.com", + "bbcgoodfood.com", + "elitetorrent.net", + "backlinko.com", + "naszemiasto.pl", + "submitexpress.com", + "savings.com", + "gem.pl", + "ahlamontada.com", + "fotomac.com.tr", + "gougou.com", + "payamsara.com", + "klix.ba", + "doostiha.ir", + "bibliocommons.com", + "wealthyaffiliate.com", + "emgn.com", + "sifyitest.com", + "washingtonexaminer.com", + "rakuten-sec.co.jp", + "mysurvey.com", + "uuu9.com", + "uscourts.gov", + "showroomprive.com", + "cargocollective.com", + "hsoub.com", + "afterbuy.de", + "marunadanmalayali.com", + "jumponhottie.com", + "avon.com", + "lazada.com.my", + "51netu.com.cn", + "ria.com", + "sankakucomplex.com", + "tvline.com", + "devshed.com", + "superherohype.com", + "tv-series.me", + "stylecraze.com", + "wizards.com", + "linkcollider.com", + "xinjunshi.com", + "bestchange.ru", + "1111.com.tw", + "fakenamegenerator.com", + "newsmth.net", + "noupe.com", + "marketingprofs.com", + "bluestacks.com", + "jemtube.com", + "bestbuy.ca", + "backcountry.com", + "designshack.net", + "pond5.com", + "highsnobiety.com", + "rtl.fr", + "pearsoncmg.com", + "hellowork.go.jp", + "koimoi.com", + "soali.ir", + "incentria.com", + "vagas.com.br", + "huaxi100.com", + "cheaa.com", + "lohaco.jp", + "gov.on.ca", + "fabthemes.com", + "pipl.com", + "comscore.com", + "pcfinancial.ca", + "craigslist.co.in", + "michaels.com", + "jibjab.com", + "sankeibiz.jp", + "dominos.co.in", + "supermoney.eu", + "itusozluk.com", + "sdchina.com", + "allkpop.com", + "cam4.com.br", + "stayfriends.de", + "futbol24.com", + "searchina.net", + "sgcpanel.com", + "informe21.com", + "alsacreations.com", + "crateandbarrel.com", + "alliancewarfare.com", + "filerio.in", + "emailsrvr.com", + "reventmedia.com", + "vietcombank.com.vn", + "rxlist.com", + "acidcow.com", + "orange.co.uk", + "hellocoton.fr", + "boldchat.com", + "psychcentral.com", + "surfline.com", + "xxxkinky.com", + "es.wordpress.com", + "publika.az", + "shufoo.net", + "hertz.com", + "businessweekly.com.tw", + "virgula.uol.com.br", + "linkis.com", + "ixwebhosting.com", + "usa.gov", + "linguee.com", + "yengo.com", + "huffingtonpost.jp", + "1x3x.com", + "mediametrics.ru", + "ec21.com", + "80018.cn", + "get-tune.net", + "yupoo.com", + "moviepilot.de", + "ihned.cz", + "fio.cz", + "pubted.com", + "cinemablend.com", + "stooorage.com", + "thevault.bz", + "journaldugeek.com", + "sommer-sommer.com", + "travelchannel.com", + "softonic.de", + "petsmart.com", + "ufxmarkets.com", + "e24.no", + "spirit.com", + "dabi.ir", + "admin5.net", + "serialu.net", + "vancouversun.com", + "submanga.com", + "sbicard.com", + "affaritaliani.it", + "idownloadblog.com", + "castorama.fr", + "sudouest.fr", + "payscale.com", + "serebii.net", + "storypick.com", + "jobkorea.co.kr", + "gowildcasino.com", + "hmetro.com.my", + "parimatch.com", + "51pinwei.com", + "globalnews.ca", + "mercadopago.com", + "sixrevisions.com", + "saisoncard.co.jp", + "gov.kz", + "gossip-tv.gr", + "zbozi.cz", + "aliorbank.pl", + "huanqiuauto.com", + "okaz.com.sa", + "cplusplus.com", + "mochimedia.com", + "google.sn", + "jquery-plugins.net", + "peoplesmart.com", + "mmafighting.com", + "ponpare.jp", + "porsche.com", + "charter.com", + "scrabblefinder.com", + "unpcn.com", + "loveaholics.com", + "geocities.co.jp", + "ladbrokes.com", + "52che.com", + "swansonvitamins.com", + "sweetwater.com", + "ali213.net", + "savenkeep.com", + "hotair.com", + "orgasmatrix.com", + "runrun.es", + "filezilla-project.org", + "looti.net", + "pagesix.com", + "twentytwowords.com", + "gingersoftware.com", + "youxiduo.com", + "isimtescil.net", + "rui.jp", + "d1g.com", + "2dbook.com", + "zigzig.ir", + "123telugu.com", + "wdr.de", + "hankyung.com", + "pr.com", + "search.ch", + "iranproud.com", + "raaga.com", + "alt1040.com", + "okpay.com", + "recordchina.co.jp", + "dreamincode.net", + "bendibao.com", + "creativelive.com", + "socialoomph.com", + "cssdeck.com", + "taxactonline.com", + "pb.com", + "fnac.es", + "ucsd.edu", + "tmart.com", + "whorush.com", + "spicejet.com", + "socialadr.com", + "tutorialzine.com", + "christianmingle.com", + "torrentleech.org", + "statefarm.com", + "orkut.com", + "whoscored.com", + "truthaboutonlinesluts.com", + "32red.com", + "wp-themes.com", + "adpost.com", + "cnr.cn", + "180upload.com", + "snagajob.com", + "textbroker.com", + "freshome.com", + "talkgold.com", + "dudamobile.com", + "weblancer.net", + "newone.org", + "nacion.com", + "takepart.com", + "voici.fr", + "gizmodo.jp", + "viki.com", + "symbaloo.com", + "topface.com", + "businessdictionary.com", + "adtech.info", + "elandroidelibre.com", + "fux.com", + "21-sun.com", + "cpmfx.com", + "siemens.com", + "edmodo.com", + "notitarde.com", + "babypips.com", + "silkroad.com", + "onextrapixel.com", + "notdoppler.com", + "walmart.ca", + "marvel.com", + "groupon.fr", + "us1.com", + "bunte.de", + "findlaw.com", + "topito.com", + "carrefour.fr", + "aawsat.com", + "super.ae", + "kicktipp.de", + "simplyrecipes.com", + "i-mobile.co.jp", + "podrobnosti.ua", + "snn.ir", + "successfactors.com", + "paixie.net", + "wiwo.de", + "glowgaze.com", + "mapsofworld.com", + "limetorrents.com", + "plarium.com", + "tvi.ua", + "corel.com", + "adhitprofits.com", + "findthecompany.com", + "noticiasaldiayalahora.co", + "160by2.com", + "jobberman.com", + "rus.ec", + "fujitsu.com", + "ladepeche.fr", + "pravda.sk", + "wwtdd.com", + "b9dm.com", + "marathonbet.com", + "sexlunch.com", + "komputronik.pl", + "gutenberg.org", + "dizo.com.cn", + "comunio.de", + "naked.com", + "gamesradar.com", + "moneysupermarket.com", + "cheaptickets.com", + "assembla.com", + "timesonline.co.uk", + "mckinsey.com", + "ebook3000.com", + "videoblocks.com", + "ruseller.com", + "pideo.net", + "joomlacode.org", + "pciconcursos.com.br", + "channelnewsasia.com", + "cnnturk.com", + "consultant.ru", + "michigan.gov", + "manoto1.com", + "10010.com", + "wholefoodsmarket.com", + "globalgrind.com", + "pichak.net", + "morpakampus.com", + "crazydomains.com.au", + "uptodown.com", + "polygon.com", + "raspberrypi.org", + "atmarkit.co.jp", + "heftig.co", + "informationweek.com", + "trendhunter.com", + "state.ny.us", + "pornpros.com", + "tnt.com", + "sportal.bg", + "spritzinc.com", + "theladders.com", + "lulu.com", + "applesfera.com", + "qoo10.jp", + "notebookreview.com", + "startertv.fr", + "healthkart.com", + "imgsin.com", + "mp3olimp.net", + "readms.com", + "casadellibro.com", + "nalog.ru", + "4gamer.net", + "jquery4u.com", + "betfred.com", + "3dwwwgame.com", + "appleinsider.com", + "dailystar.co.uk", + "swalif.com", + "hushmail.com", + "worldtimebuddy.com", + "sportschau.de", + "mediatraffic.com", + "gismeteo.ua", + "record.com.mx", + "putnik1.livejournal.com", + "nextbigwhat.com", + "vinescope.com", + "jarchi.ir", + "yidio.com", + "dunyanews.tv", + "hangseng.com", + "interpark.com", + "scholastic.com", + "penny-arcade.com", + "geilundlive.com", + "budbi.com", + "stop55.com", + "jaidefinichon.com", + "smartsheet.com", + "vector.me", + "farecompare.com", + "vodafone.com.eg", + "craftsy.com", + "theresumator.com", + "downloadab.com", + "abendzeitung-muenchen.de", + "dicio.com.br", + "fobshanghai.com", + "2dehands.be", + "thethao247.vn", + "cams.com", + "gothamist.com", + "moneynews.com", + "zocdoc.com", + "rol.ro", + "nic.ar", + "avenues.info", + "cleverbridge.com", + "usajobs.gov", + "divxstage.eu", + "jsticket.net", + "himasoku.com", + "thrivethemes.com", + "orientaltrading.com", + "premiumpress.com", + "house365.com", + "drugoi.livejournal.com", + "monster.co.uk", + "quickanddirtytips.com", + "virtuemart.net", + "huffpost.com", + "real.com", + "teacherspayteachers.com", + "t24.com.tr", + "zakon.kz", + "shoppinglifestyle.com", + "trendmicro.com", + "surfingbird.ru", + "cam.ac.uk", + "navyfederal.org", + "rateyourmusic.com", + "bravenet.com", + "polo.com", + "cl.ly", + "nanacast.com", + "cnfol.com", + "24option.com", + "myscienceacademy.org", + "getsoftfree.com", + "cba.pl", + "templateism.com", + "actudesfinances.org", + "1.254.254.254", + "jobtalk.jp", + "barbie.com", + "rahnama.com", + "gigporno.com", + "buddypress.org", + "corporationwiki.com", + "enorth.com.cn", + "thepioneerwoman.com", + "cam4ultimate.com", + "phpclasses.org", + "uplus.metroer.com/~content", + "komando.com", + "iha.com.tr", + "televisa.com", + "tmtpost.com", + "classifiedsgiant.com", + "sportinglife.com", + "askul.co.jp", + "travelagency.travel", + "jqueryscript.net", + "rusfolder.com", + "netcq.net", + "irangrand.ir", + "interaztv.com", + "downvids.net", + "mongodb.org", + "nasgo.net", + "thesitewizard.com", + "songspk.cc", + "travelandleisure.com", + "d-h.st", + "wasanga.com", + "cgg.gov.in", + "silverclix.com", + "almogaz.com", + "sexmob.es", + "mistreci.com", + "nukistream.com", + "postaffiliatepro.com", + "dish.com", + "ulta.com", + "worldcat.org", + "crackle.com", + "cineblog01.net", + "parispornmovies.com", + "avito.ma", + "nethouse.ru", + "elfinanciero.com.mx", + "ab-in-den-urlaub.de", + "pixeden.com", + "thetimenow.com", + "divar.ir", + "coolrom.com", + "akbank.com", + "saturn.de", + "dstv.com", + "mttbsystem.com", + "hujiang.com", + "bash.im", + "sextgem.com", + "subaonet.com", + "cnsnews.com", + "informationng.com", + "utoronto.ca", + "hetzner.de", + "vend-o.com", + "cybozu.com", + "autotimes.com.cn", + "5giay.vn", + "theepochtimes.com", + "penesalud.com", + "livenation.com", + "qoinpro.com", + "myway.com", + "safe-swaps.com", + "elephantjournal.com", + "curse.com", + "17zwd.com", + "hitleap.com", + "mec.gov.br", + "freewebcams.com", + "westelm.com", + "imagegals.com", + "streeteasy.com", + "ekitan.com", + "git-scm.com", + "p30day.com", + "kapaza.be", + "svoboda.org", + "google.mg", + "timesofisrael.com", + "roodo.com", + "malwaretips.com", + "dumpaday.com", + "dha.com.tr", + "diary.ru", + "epa.gov", + "jonloomer.com", + "holidaylettings.co.uk", + "oem.com.mx", + "oxfordjournals.org", + "3bmeteo.com", + "iwebtool.com", + "yinyuetai.com", + "ixl.com", + "rezultati.com", + "51sole.com", + "moudamepo.com", + "macromill.com", + "nzz.ch", + "buzznet.com", + "roku.com", + "posta.com.tr", + "letsbonus.com", + "theoatmeal.com", + "artofmanliness.com", + "tubepornkiss.com", + "aviny.com", + "sabresonicweb.com", + "tech-wd.com", + "qbank.ru", + "getsatisfaction.com", + "prokerala.com", + "ebc.com.br", + "typiol.com", + "photo.net", + "rockpapershotgun.com", + "netgear.com", + "mg.co.za", + "impressrd.jp", + "jkbk.cn", + "bakufu.jp", + "lgmi.com", + "tnt-online.ru", + "frequency.com", + "zip-codes.com", + "sport.ro", + "wolfram.com", + "pgmediaserve.com", + "icann.org", + "twistedsifter.com", + "mopo.de", + "vogue.co.uk", + "chatrandom.com", + "checkpagerank.net", + "colorzilla.com", + "autobip.com", + "laban.vn", + "pricecheck.co.za", + "notimex.com.mx", + "pik.ba", + "monster.de", + "google.cd", + "economia.uol.com.br", + "vsnl.net.in", + "futura-sciences.com", + "appstorm.net", + "icefilms.info", + "dicelacancion.com", + "hypovereinsbank.de", + "piratestreaming.tv", + "101domain.com", + "google.mn", + "rutgers.edu", + "behindwoods.com", + "downloadmaster.ru", + "smartinsights.com", + "wiziq.com", + "ftc.gov", + "animenewsnetwork.com", + "tandfonline.com", + "seagate.com", + "famitsu.com", + "onekingslane.com", + "zalando.fr", + "convio.com", + "seochat.com", + "qq163.com", + "capital.gr", + "tamin.ir", + "phpfreaks.com", + "cricketcountry.com", + "ubersuggest.org", + "ntv.co.jp", + "olark.com", + "1001fonts.com", + "appsgeyser.com", + "elsiglodetorreon.com.mx", + "lyricsmint.com", + "tovima.gr", + "petco.com", + "campograndenews.com.br", + "amadeus.com", + "tinychat.com", + "astrology.com", + "idg.se", + "nordea.se", + "gelbeseiten.de", + "nrc.nl", + "houseoffraser.co.uk", + "hepsibahis3.com", + "empowernetwork.com/almostasecret", + "phpnuke.org", + "xfwed.com", + "gulte.com", + "msi.com", + "netfirms.com", + "ovcanada.com", + "smzdm.com", + "wwwhatsnew.com", + "mydomainadvisor.com", + "newindianexpress.com", + "luxtarget.com", + "evz.ro", + "otodom.pl", + "linkin.net", + "moneysavingmom.com", + "uploadbaz.com", + "egyup.com", + "three.co.uk", + "co-operativebank.co.uk", + "redad.ru", + "ipsosinteractive.com", + "all-union.com", + "trustlink.ru", + "softango.com", + "toucharcade.com", + "tv2.dk", + "thisissouthwales.co.uk", + "jra.go.jp", + "alfavita.gr", + "ibigdan.livejournal.com", + "gtarcade.com", + "jc001.cn", + "microcenter.com", + "optimizepress.com", + "beyebe.com", + "muyzorras.com", + "expedia.co.uk", + "volvocars.com", + "lyd.com.cn", + "korben.info", + "disput.az", + "aeroflot.ru", + "betanews.com", + "avangard.ru", + "wrzko.eu", + "cakephp.org", + "majorgeeks.com", + "dreamteammoney.com", + "foroactivo.com", + "ut.ac.ir", + "eonet.jp", + "vn-zoom.com", + "footytube.com", + "expedia.de", + "barneys.com", + "lieyunwang.com", + "gartner.com", + "lazada.com.ph", + "ourtime.com", + "ohnotheydidnt.livejournal.com", + "e-rewards.com", + "updatestar.com", + "cafef.vn", + "cookinglight.com", + "expressdownload.net", + "shockmansion.com", + "adserverpub.com", + "ipko.pl", + "gsa-online.de", + "car.gr", + "cqcounter.com", + "thenews.com.pk", + "gnc.com", + "teambeachbody.com", + "kobobooks.com", + "cwan.com", + "ca.com", + "list.ly", + "baijob.com", + "gunbroker.com", + "rtl.be", + "myadvertisingpays.com", + "mediapost.com", + "groupalia.com", + "anquan.org", + "mixshowblast.com", + "myscore.ru", + "bancochile.cl", + "a2hosting.com", + "hayneedle.com", + "myfc.ir", + "aktifhaber.com", + "eater.com", + "anymeeting.com", + "cylex.de", + "sayidaty.net", + "synonym.com", + "comprendrechoisir.com", + "prlog.ru", + "solidtrustpay.com", + "cinejosh.com", + "yelp.co.uk", + "olx.com.ar", + "dihitt.com", + "donkeymails.com", + "chainreactioncycles.com", + "wearehairy.com", + "ebuyer.com", + "bplans.com", + "ponparemall.com", + "readthedocs.org", + "tehparadox.com", + "source-wave.com", + "tbs.co.jp", + "redcoon.de", + "francetv.fr", + "freelogoservices.com", + "ogone.com", + "jeanmarcmorandini.com", + "lasexta.com", + "sponsoredreviews.com", + "govome.com", + "vecteezy.com", + "alhayat.com", + "cnhubei.com", + "mumayi.com", + "webfail.com", + "eluniverso.com", + "moddb.com", + "gaiaonline.com", + "gordonua.com", + "liveadexchanger.com", + "zhe800.com", + "softonic.com.br", + "cnyes.com", + "cumlouder.com", + "krakow.pl", + "nullrefer.com", + "zaluu.com", + "diretube.com", + "seattlepi.com", + "eanswers.com", + "gostorego.com", + "play.com", + "lenskart.com", + "unblog.fr", + "chatwork.com", + "bronto.com", + "jobisjob.co.in", + "mercadolibre.com.uy", + "overdrive.com", + "runkeeper.com", + "smartshopping.com", + "getrichradio.com", + "grader.com", + "101greatgoals.com", + "elbilad.net", + "kopp-verlag.de", + "ranksignals.com", + "trome.pe", + "jobstreet.com.my", + "farnell.com", + "fastmail.fm", + "faithit.com", + "blogdohotelurbano.com", + "sencha.com", + "daisycon.com", + "random.org", + "tportal.hr", + "rkanr.com", + "fc2-erodouga.com", + "youngpornvideos.com", + "bravoteens.com", + "mobile.free.fr", + "mitele.es", + "edis.at", + "questionablecontent.net", + "indiangilma.com", + "comunio.es", + "bitnami.com", + "sliptalk.com", + "sourceforge.jp", + "lyoness.net", + "nespresso.com", + "ultrafarma.com.br", + "lik.cl", + "jsoftj.com", + "metoffice.gov.uk", + "androidauthority.com", + "360safe.com", + "wikimart.ru", + "tutu.ru", + "truetwit.com", + "xinruijunshi.com", + "online-sweepstakes.com", + "singaporeair.com", + "cwtv.com", + "politiken.dk", + "alaan.tv", + "tuttomercatoweb.com", + "imagehost123.com", + "drakensang.com", + "frenchweb.fr", + "syosetu.com", + "zhifang.com", + "viptube.com", + "theme123.net", + "statista.com", + "51bi.com", + "clickz.com", + "kingworldnews.com", + "qunar.com", + "pimpandhost.com", + "pagina12.com.ar", + "xinmin.cn", + "kidshealth.org", + "pics.livejournal.com", + "kabum.com.br", + "anandabazar.com", + "pnu.ac.ir", + "scene7.com", + "paperpkads.com", + "mylikes.com", + "longtailvideo.com", + "firmy.cz", + "bitcoinity.org", + "inopressa.ru", + "expat-blog.com", + "governmentjobs.com", + "airdroid.com", + "mid-day.com", + "frandroid.com", + "webceo.com", + "cadenaser.com", + "minutouno.com", + "onamae.com", + "onlinekhabar.com", + "williams-sonoma.com", + "freewebsubmission.com", + "vozforums.com", + "bigpicture.ru", + "mymodernmet.com", + "presseportal.de", + "licindia.in", + "ismedia.jp", + "depor.pe", + "sing365.com", + "v2cigs.com", + "postimage.org", + "gencat.cat", + "west263.com", + "alohatube.com", + "eenadupratibha.net", + "terra.com", + "bri.co.id", + "silktide.com", + "anyang.gov.cn", + "speedanalysis.com", + "bitcoin.org", + "toodledo.com", + "topgear.com", + "rtvslo.si", + "hotpads.com", + "nedsecure.co.za", + "expedia.ca", + "thairath.co.th", + "gizbot.com", + "naukrigulf.com", + "guffins.com", + "twilio.com", + "avsforum.com", + "sharpnews.ru", + "inoreader.com", + "stgeorge.com.au", + "shoebuy.com", + "sbwire.com", + "bfm.ru", + "dongtaiwang.com", + "mandrillapp.com", + "176.com", + "olx.com", + "trustpilot.co.uk", + "tgju.org", + "pap.fr", + "iphonehacks.com", + "gry-online.pl", + "mp3xd.com", + "viabcp.com", + "bizcommunity.com", + "chillingeffects.org", + "thomasnet.com", + "performersoft.com", + "paymaster.ru", + "comdotgame.com", + "bseindia.com", + "foodgawker.com", + "clicktale.com", + "dot.tk", + "smbc-comics.com", + "vvmembers.co", + "c-sharpcorner.com", + "acdcads.com", + "goldporntube.com", + "abakus-internet-marketing.de", + "babbel.com", + "fzg360.com", + "99inf.com", + "cduniverse.com", + "autobild.de", + "tvrage.com", + "phoenix.edu", + "hurriyetemlak.com", + "va.gov", + "maxmind.com", + "lifehacker.jp", + "nola.com", + "niroensani.ir", + "ihotelier.com", + "mianbao.com", + "powerball.com", + "express.com", + "freshdesignweb.com", + "o2.co.uk", + "utusan.com.my", + "overclockers.ru", + "fsiblog.com", + "launchpad.net", + "prizee.com", + "alalam.ir", + "imtalk.org", + "vavel.com", + "nyc.ny.us", + "youmob.com", + "dailythanthi.com", + "ksu.edu.sa", + "nlayer.net", + "kurier.at", + "expert-offers.com", + "marry52.com", + "cityads.ru", + "darkorbit.com", + "cio.com", + "a9.com", + "symfony.com", + "crobo.com", + "shatel.ir", + "opposingviews.com", + "myvoffice.com", + "vkmag.com", + "starpulse.com", + "bls.gov", + "lifebettering.com", + "fanhuan.com", + "upload7.ir", + "infoplease.com", + "suruga-ya.jp", + "deredactie.be", + "shangdu.com", + "portail.free.fr", + "smartaddons.com", + "w3layouts.com", + "wallstreet-online.de", + "yapo.cl", + "standard.co.uk", + "atlas.sk", + "seowizard.ru", + "currys.co.uk", + "nova.cz", + "mygully.com", + "favstar.fm", + "themuse.com", + "yudu.com", + "urlopener.com", + "ellentv.com", + "imgsrc.ru", + "km.ru", + "guildwars2.com", + "google.mu", + "androidpit.de", + "mrporter.com", + "ttt4.com", + "cbox.ws", + "hf365.com", + "1mobile.com", + "punjabkesari.in", + "msu.edu", + "grasscity.com", + "abduzeedo.com", + "sublimetext.com", + "jtb.co.jp", + "mensxp.com", + "myheritage.com", + "onvasortir.com", + "safeway.com", + "comicvine.com", + "cpubenchmark.net", + "nn.ru", + "animefreak.tv", + "angularjs.org", + "managewp.com", + "ameli.fr", + "merrjep.com", + "christianbook.com", + "nubiles.net", + "klmty.net", + "247sports.com", + "aramex.com", + "tubent.com", + "cognitiveseo.com", + "bankpasargad.com", + "pingler.com", + "compareja.com.br", + "surveyzrewardcenter.eu", + "allyes.com", + "graaam.com", + "poriborton.com", + "seslisozluk.net", + "shopyourway.com", + "awd.ru", + "wa.gov.au", + "slrclub.com", + "apollo.lv", + "girls-ly.com", + "totalfilm.com", + "cam4.it", + "gogecapital.com", + "sketchup.com", + "muscleandfitness.com", + "m24.ru", + "blinklist.com", + "artisteer.com", + "nettavisen.no", + "enjin.com", + "pc6.com", + "citrix.com", + "indgovtjobs.in", + "phpbb.com", + "laughingsquid.com", + "fblife.com", + "stansberryresearch.com", + "hugefiles.net", + "mp3monkey.net", + "mcdonalds.com", + "vodafone.co.uk", + "t-mobile.de", + "tp-link.com.cn", + "iflscience.com", + "nst.com.my", + "zoom.com.br", + "dsw.com", + "tigerair.com", + "doityourself.com", + "bongdainfo.com", + "imacros.net", + "paperlesspost.com", + "sprinthost.ru", + "lovoo.net", + "kadinlarkulubu.com", + "championselect.net", + "jcb.co.jp", + "miui.com", + "impots.gouv.fr", + "linkshop.com.cn", + "pando.com", + "ckeditor.com", + "arabylife.com", + "ml.com", + "sheldonsfans.com", + "freecharge.in", + "magicmovies.com", + "laweekly.com", + "highrisehq.com", + "tr.gg", + "vidbull.com", + "diy.com", + "yieldtraffic.com", + "cima4u.com", + "gendama.jp", + "netpnb.com", + "kannaway.com", + "soft112.com", + "die-boersenformel.com", + "movie25.so", + "footyroom.com", + "dostor.org", + "aol.de", + "desimartini.com", + "themeko.org", + "danarimedia.com", + "toroporno.com", + "stylemepretty.com", + "urlm.de", + "demotivation.me", + "gentside.com", + "carfax.com", + "huzhou.gov.cn", + "hotline.ua", + "fullmoneysystem.com", + "pornomovies.com", + "fsymbols.com", + "blog.de", + "clickpoint.com", + "tv-asahi.co.jp", + "flightstats.com", + "sonico.com", + "afamily.vn", + "anadolu.edu.tr", + "rs-online.com", + "androidpit.com", + "a-telecharger.com", + "wikifeet.com", + "nix.ru", + "tvmao.com", + "groupon.es", + "hitfix.com", + "weathernews.jp", + "dig.do", + "googledrive.com", + "fodors.com", + "melon.com", + "testflightapp.com", + "markt.de", + "mahua.com", + "sedo.co.uk", + "tuenti.com", + "comm100.com", + "olx.co.id", + "lefrecce.it", + "gov.cl", + "wplift.com", + "unn.com.ua", + "arizona.edu", + "msn.com.cn", + "k7x.com", + "techz.vn", + "jobomas.com", + "extranetinvestment.com", + "tengrinews.kz", + "comicbookmovie.com", + "4porn.com", + "rubiconproject.com", + "gametracker.com", + "otcmarkets.com", + "mismarcadores.com", + "trojmiasto.pl", + "cleverreach.com", + "contactform7.com", + "weloveshopping.com", + "runnersworld.com", + "falabella.com", + "airtelbroadband.in", + "twistys.com", + "ebesucher.de", + "belastingdienst.nl", + "orlandosentinel.com", + "th3professional.com", + "duke.edu", + "opensubtitles.us", + "clickme.net", + "infor.pl", + "unesco.org", + "socialfabric.us", + "ename.net", + "alternativeto.net", + "rotahaber.com", + "johnchow.com", + "pervclips.com", + "linkfeed.ru", + "dtvideo.com", + "kinoman.tv", + "siteprice.org", + "lacuerda.net", + "amulyam.in", + "mandghomebusiness.com", + "infojobs.com.br", + "hirufm.lk", + "mybloggertricks.com", + "athemes.com", + "moikrug.ru", + "fybersearch.com", + "wine-searcher.com", + "davidicke.com", + "dotabuff.com", + "sainsburys.co.uk", + "ovaustralia.com", + "updatesoftnow.com", + "desarrolloweb.com", + "elaph.com", + "icc-cricket.com", + "gaytube.com", + "di.fm", + "99wed.com", + "c-and-a.com", + "mlxchange.com", + "realgfporn.com", + "synxis.com", + "optymalizacja.com", + "ibtesama.com", + "hankooki.com", + "shiksha.com", + "anoox.com", + "seamless.com", + "stripe.com", + "ets.org", + "fresherslive.com", + "webcoinpay.net", + "oscommerce.com", + "gratisprogramas.org", + "football365.com", + "mkyong.com", + "antichat.ru", + "spamarrest.com", + "ubc.ca", + "36kr.com", + "startv.in", + "shoutcast.com", + "heureka.sk", + "entheosweb.com", + "51wan.com", + "madadsmedia.com", + "parse.com", + "ngacn.cc", + "techbargains.com", + "tvmovie.de", + "cpmstar.com", + "torproject.org", + "residentadvisor.net", + "genbeta.com", + "hulu.jp", + "pluralsight.com", + "postini.com", + "zombie.jp", + "skimlinks.com", + "liverail.com", + "phys.org", + "slashfilm.com", + "livemaster.ru", + "idwebgame.com", + "pho.to", + "dealfish.co.th", + "s1979.com", + "designfloat.com", + "live365.com", + "pbase.com", + "eniro.se", + "irannaz.com", + "socialmonkee.com", + "uidai.gov.in", + "joomlashine.com", + "allhyipmon.ru", + "upbulk.com", + "diapers.com", + "flypgs.com", + "hotmart.com.br", + "globus-inter.com", + "airfrance.fr", + "t3.com", + "unetepubli.com", + "yahoo-help.jp", + "iphones.ru", + "95559.com.cn", + "glamsham.com", + "gophoto.it", + "statmyweb.com", + "aftermarket.pl", + "forumotion.com", + "iafd.com", + "hotlog.ru", + "drclix.com", + "wawa-mania.ec", + "nichepursuits.com", + "imp.free.fr", + "baofeng.com", + "binsearch.info", + "neswangy.net", + "parperfeito.com.br", + "theeroticreview.com", + "sabayacafe.com", + "maplestage.com", + "icook.tw", + "customerhub.net", + "uid.me", + "viperchill.com", + "askfrank.net", + "juegosjuegos.com", + "debonairblog.com", + "coingeneration.com", + "onetravel.com", + "slutroulette.com", + "jetpack.me", + "embedupload.com", + "bancodevenezuela.com", + "mysavingsmedia.net", + "smallbiztrends.com", + "emirates247.com", + "bu.edu", + "fineco.it", + "netbarg.com", + "gatech.edu", + "zik.ua", + "peliculascoco.com", + "hkjc.com", + "turkiyegazetesi.com.tr", + "cooperativa.cl", + "challenges.fr", + "usingenglish.com", + "vouchercodes.co.uk", + "bonprix.de", + "disneylatino.com", + "townwork.net", + "rentanything.com", + "krypt.com", + "wral.com", + "globes.co.il", + "designtaxi.com", + "mp-success.com", + "invisionpower.com", + "liontravel.com", + "classicrummy.com", + "mt.co.kr", + "inccel.com", + "santander.cl", + "afip.gov.ar", + "yellowpages.com.au", + "osclass.org", + "couriermail.com.au", + "stream.cz", + "wizaz.pl", + "linkresearchtools.com", + "cam4.co.uk", + "allafrica.com", + "gezginler.net", + "free-ebooks.net", + "hopesandfears.com", + "ticketmaster.com.mx", + "ohio.gov", + "liberoquotidiano.it", + "neurs.net", + "forumophilia.com", + "odnako.org", + "gamekult.com", + "vh1.com", + "zxart.cn", + "mass.gov", + "tv-tokyo.co.jp", + "mundopromocion.com", + "theme-junkie.com", + "asu.edu", + "lalibre.be", + "pornokopilka.info", + "fx678.com", + "netbeans.org", + "chinabidding.com.cn", + "lunapic.com", + "dramafever.com", + "hbogo.com", + "torrents.net", + "woopra.com", + "justfab.com", + "fitnessmagazine.com", + "tripadvisor.com.mx", + "183.com.cn", + "adscendmedia.com", + "geek.com", + "thepetitionsite.com", + "skyscanner.ru", + "morningpost.com.cn", + "concursolutions.com", + "camplace.com", + "smarturl.it", + "browsershots.org", + "piktochart.com", + "elcorreo.com", + "collegeboard.org", + "kasikornbank.com", + "adsoftheworld.com", + "fakku.net", + "jpn.org", + "sahafah.net", + "rubias19.com", + "fc2web.com", + "redsurf.ru", + "iproperty.com.my", + "browsersafeguard.com", + "mmajunkie.com", + "bangkokbank.com", + "ovi.com", + "fap.to", + "filmygyan.in", + "edaily.vn", + "affilorama.com", + "valuecommerce.ne.jp", + "softaculous.com", + "exchanger.ru", + "html-color-codes.info", + "hd-xvideos.com", + "realself.com", + "geocaching.com", + "mom.me", + "stc.com.sa", + "video-i365.com", + "umd.edu", + "jeffbullas.com", + "kora.com", + "cyberchimps.com", + "popupads.ir", + "chatango.com", + "gearslutz.com", + "fout.jp", + "contactmusic.com", + "linkcrypt.ws", + "sophos.com", + "forum-auto.com", + "svtplay.se", + "newsbusters.org", + "fileswap.com", + "phpfox.com", + "denic.de", + "riverisland.com", + "encuentra24.com", + "foxnewsinsider.com", + "dtdc.com", + "tayyar.org", + "thepushbuttonmillionaire.com", + "brokenlinkcheck.com", + "ju51.com", + "stihi.ru", + "psdgraphics.com", + "mommyfucktube.com", + "mangocity.com", + "routard.com", + "whicdn.com", + "socialtriggers.com", + "e-bookspdf.org", + "songspk3.in", + "paginasamarillas.com", + "webgame.in.th", + "xxxdessert.com", + "materiel.net", + "skillshare.com", + "tubepleasure.com", + "paypal.de", + "yeahmobi.com", + "torrentz-proxy.com", + "aircel.com", + "openask.com", + "trafficg.com", + "anthem.com", + "aleseriale.pl", + "f-page.ru", + "hrsmart.com", + "cbengine.com", + "startlap.com", + "spamhaus.org", + "trovit.com", + "sporcle.com", + "xs8.cn", + "romwe.com", + "powtoon.com", + "cybozulive.com", + "zbiornik.com", + "cebit.de", + "flipboard.com", + "news.am", + "wm-panel.com", + "topnews.ru", + "usp.br", + "realmadrid.com", + "fastdist.net", + "unian.ua", + "youtube.com/user/PewDiePie", + "unc.edu", + "tamu.edu", + "ip2location.com", + "americanapparel.net", + "virtualedge.com", + "vikatan.com", + "goodlayers.com", + "feedblitz.com", + "dateinasia.com", + "freepdfconvert.com", + "iweb.com", + "lachainemeteo.com", + "aetna.com", + "ti.com", + "gratisjuegos.org", + "webhostinghub.com", + "preev.com", + "holidayiq.com", + "elpais.com.uy", + "uchicago.edu", + "telcel.com", + "feebbo.com", + "webhallen.com", + "dmir.ru", + "emailmg.ipage.com", + "hrs.de", + "propakistani.pk", + "theme.co", + "bigpara.com", + "finishline.com", + "fardanews.com", + "nat.gov.tw", + "stoloto.ru", + "sermepa.es", + "radaris.com", + "softportal.com", + "turkiye.gov.tr", + "nnn.ru", + "e-familynet.com", + "wenkang.cn", + "teamworkpm.net", + "videohelp.com", + "pixmania.com", + "games.com", + "guns.ru", + "plus28.com", + "pf.pl", + "pbworks.com", + "sedo.de", + "freedownloadscenter.com", + "backpackers.com.tw", + "mycokerewards.com", + "chinaunix.net", + "invisionapp.com", + "wordhippo.com", + "unitezz.com", + "yan.vn", + "sonhoo.com", + "spielaffe.de", + "d.pr", + "technologyreview.com", + "democraticunderground.com", + "molotok.ru", + "watchever.de", + "wdc.com", + "torg.com", + "ivillage.com", + "portableapps.com", + "ovh.es", + "vse.kz", + "alrajhibank.com.sa", + "dhl-usa.com", + "pingan.com", + "excite.com", + "cincodias.com", + "marksdailyapple.com", + "bter.com", + "nationaljournal.com", + "fao.org", + "oddee.com", + "digitalriver.com", + "livescore.in", + "siilu.com", + "gofirstrowus.eu", + "alquds.co.uk", + "autoevolution.com", + "zerx.ru", + "trovit.it", + "hvylya.org", + "carters.com", + "shejis.com", + "radio-canada.ca", + "vodafone.es", + "sky.fm", + "maturetube.com", + "virtualtourist.com", + "line25.com", + "rzeczpospolita.pl", + "megacurioso.com.br", + "beliefnet.com", + "eklablog.com", + "hbo.com", + "bsi.ir", + "fastweb.it", + "webrazzi.com", + "telam.com.ar", + "downloadming.me", + "bluefly.com", + "thewire.com", + "erotube.org", + "vt.edu", + "jsbin.com", + "ibosocial.com", + "brides.com.cn", + "jn.pt", + "togetter.com", + "olhardigital.uol.com.br", + "ebookee.org", + "socialgamenet.com", + "romaniatv.net", + "epweike.com", + "pronto.com", + "blog.pl", + "top81.cn", + "flagfox.wordpress.com", + "unionbank.com", + "visitkorea.or.kr", + "blogspot.dk", + "fermasosedi.ru", + "inmobi.com", + "abc.com.py", + "iqoo.me", + "17k.com", + "onlineapplicationsdownloads.com", + "komli.com", + "tala.ir", + "videopremium.me", + "job.ru", + "sunbiz.org", + "tw116.com", + "browserstack.com", + "youtu.be", + "yardbarker.com", + "flvmplayer.com", + "kulichki.net", + "gravity.com", + "levelclix.com", + "kissanime.com", + "insightexpressai.com", + "colorhexa.com", + "citizensbankonline.com", + "hjenglish.com", + "voeazul.com.br", + "paraskhnio.gr", + "es.tl", + "busuu.com", + "appsumo.com", + "metacrawler.com", + "ciku5.com", + "android-hilfe.de", + "laterooms.com", + "google.com.bn", + "gamefront.com", + "rev2pub.com", + "bharatiyamobile.com", + "publicidees.com", + "jossandmain.com", + "babycentre.co.uk", + "zetabux.com", + "movieweb.com", + "timescity.com", + "dnevnik.ru", + "nlcafe.hu", + "magazine3k.com", + "irancell.ir", + "kalaydo.de", + "e5.ru", + "lecai.com", + "sbs.com.au", + "sierratradingpost.com", + "girlfriendvideos.com", + "ink361.com", + "corporate-ir.net", + "allgameshome.com", + "povarenok.ru", + "typo3.org", + "turbosquid.com", + "miniurls.co", + "thinkwithgoogle.com", + "haber3.com", + "newsmeback.com", + "sipse.com", + "osxdaily.com", + "9lessons.info", + "cyclopaedia.net", + "reallifecam.com", + "gameblog.fr", + "colourbox.com", + "mixedmartialarts.com", + "prostoporno.net", + "chanel.com", + "maxim.com", + "cam4.es", + "olx.com.mx", + "newscientist.com", + "netcombo.com.br", + "serienjunkies.org", + "shablol.com", + "bnpparibasfortis.be", + "canada.com", + "timeslive.co.za", + "cinematoday.jp", + "21cineplex.com", + "bodisparking.com", + "cruisecritic.com", + "vertoz.com", + "glosbe.com", + "nstarikov.ru", + "epinions.com", + "colbertnation.com", + "marketglory.com", + "emailmeform.com", + "angloinfo.com", + "filfan.com", + "yougetsignal.com", + "broker.to", + "quickbux.net", + "gottabemobile.com", + "beyondtherack.com", + "tripadvisor.ru", + "jschina.com.cn", + "registraduria.gov.co", + "thisoldhouse.com", + "appcelerator.com", + "noticias24carabobo.com", + "skelbiu.lt", + "ru.wix.com", + "iransong.com", + "i-sux.com", + "telerama.fr", + "mydala.com", + "pia.jp", + "fusebux.com", + "biquge.com", + "microworkers.com", + "onlywire.com", + "fanbox.com", + "ambitoweb.com", + "tripadvisor.com.br", + "postgresql.org", + "telenet.be", + "thegeekstuff.com", + "primelocation.com", + "ee.co.uk", + "phoenixads.co.in", + "brigitte.de", + "360kad.com", + "kathimerini.gr", + "batoto.net", + "babebuns.com", + "webimpresion.com", + "olx.com.ng", + "which.co.uk", + "f5haber.com", + "cic.fr", + "talkfusion.com", + "ctrlq.org", + "caracol.com.co", + "wer-weiss-was.de", + "invisionzone.com", + "head-fi.org", + "thingiverse.com", + "manager-magazin.de", + "bzwbk.pl", + "f1news.ru", + "tweetreach.com", + "sj998.com", + "streetdirectory.com", + "asos.fr", + "kalahari.com", + "classiccars.com", + "bitacoras.com", + "bravotv.com", + "beanfun.com", + "bonappetit.com", + "fashionara.com", + "techdirt.com", + "newlook.com", + "iyinet.com", + "mogujie.com", + "carros.com.br", + "starfall.com", + "rp5.ru", + "pocket-lint.com", + "homeadvisor.com", + "webhostbox.net", + "cameraboys.com", + "pricerunner.com", + "worldcarfans.com", + "meganovosti.net", + "taxheaven.gr", + "freewebsitetemplates.com", + "mr90.ir", + "cheapflights.co.uk", + "bankinter.com", + "euroresidentes.com", + "bettycrocker.com", + "filmey.com", + "sheinside.com", + "2ememain.be", + "tu.tv", + "kugou.com", + "destructoid.com", + "todoist.com", + "karmaloop.com", + "appadvice.com", + "html5rocks.com", + "osdir.com", + "webisgreat.info", + "dailyblogtips.com", + "mca.gov.in", + "e-estekhdam.com", + "hlntv.com", + "proprofs.com", + "his-j.com", + "im286.com", + "mmorpg.com", + "shooshtime.com", + "terra.es", + "lpcloudbox301.com", + "tubepornstars.com", + "tableausoftware.com", + "soccersuck.com", + "websurf.ru", + "indiafreestuff.in", + "pimei.com", + "colatour.com.tw", + "fssnet.co.in", + "maxpark.com", + "foodandwine.com", + "apachefriends.org", + "standardchartered.co.in", + "waveapps.com", + "gosong.net", + "spring.me", + "weatherbug.com", + "iw33.com", + "apserver.net", + "thenationonlineng.net", + "tjournal.ru", + "greatschools.net", + "3movs.com", + "61.com", + "directpaybiz.com", + "w3snoop.com", + "jsonline.com", + "grainger.com", + "prevention.com", + "blizzard.com", + "baseball-reference.com", + "cjn.cn", + "fasthosts.co.uk", + "kym-cdn.com", + "cuponation.in", + "cam4.fr", + "lalawan67.net", + "videovantage.co", + "elimparcial.com", + "edlen24.com", + "donya-e-eqtesad.com", + "englishforums.com", + "dom2.ru", + "cinepolis.com", + "healthtap.com", + "astroawani.com", + "autosport.com", + "finalfantasyxiv.com", + "valueclickmedia.com", + "vitaminshoppe.com", + "apartmentguide.com", + "notjustok.com", + "gratispeliculas.org", + "intporn.com", + "eroticbeauties.net", + "egloos.com", + "nujij.nl", + "godvine.com", + "punishtube.com", + "wisegeek.org", + "somuch.com", + "taz.de", + "zetaboards.com", + "gravityforms.com", + "telegraf.rs", + "odn.ne.jp", + "topeleven.com", + "peekyou.com", + "razerzone.com", + "furaffinity.net", + "mobikwik.com", + "eventful.com", + "sextube.fm", + "treccani.it", + "phatograph.com", + "feedthebot.com", + "netdoctor.co.uk", + "collarme.com", + "reformal.ru", + "hemnet.se", + "jacquieetmicheltv.net", + "9ku.com", + "kraloyun.com", + "directadmin.com", + "bongda.com.vn", + "ddizi.org", + "tedata.net", + "fb.org", + "pagemodo.com", + "satu.kz", + "nodejs.org", + "tiberiumalliances.com", + "wallst.com", + "dezeen.com", + "rense.com", + "hawkhost.com", + "vonage.com", + "zjol.com.cn", + "housefun.com.tw", + "ad1.ru", + "myredbook.com", + "rarlab.com", + "audiopoisk.com", + "pbebank.com", + "whocallsme.com", + "playground.ru", + "tnr.com", + "firstdirect.com", + "xinhua.jp", + "gmane.org", + "verseriesynovelas.com", + "broadwayworld.com", + "thefrisky.com", + "rapmls.com", + "skapiec.pl", + "wholesale-dress.net", + "alison.com", + "n4hr.com", + "prefiles.com", + "vtc.vn", + "coral.co.uk", + "azyya.com", + "smithsonianmag.com", + "ticksy.com", + "theync.com", + "pcinpact.com", + "eventbrite.co.uk", + "buenosaires.gob.ar", + "a5cee7.se", + "bazingamob.com", + "pp.cc", + "shortnews.de", + "responsinator.com", + "filepost.com", + "securefreedom.com", + "notepad-plus-plus.org", + "thekrazycouponlady.com", + "apktops.ir", + "nr2.ru", + "timinternet.it", + "man.lv", + "forexpeacearmy.com", + "plius.lt", + "vcp.ir", + "mysitecost.ru", + "yourfiledownloader.com", + "dealguardian.com", + "sql.ru", + "laravel.com", + "shape5.com", + "visa.com", + "vocus.com", + "stackthatmoney.com", + "pornicom.com", + "my.tv.sohu.com/user/cc128", + "urban-rivals.com", + "robotstxt.org", + "colorlabsproject.com", + "mg.gov.br", + "local.ch", + "adbooth.net", + "se.pl", + "al-akhbar.com", + "he.net", + "covoiturage.fr", + "sakshieducation.com", + "thisdaylive.com", + "thestudentroom.co.uk", + "pzy.be", + "designyoutrust.com", + "forbes.ua", + "fandongxi.com", + "expertreviews.co.uk", + "dream-demo.com", + "barchart.com", + "ht.ly", + "burbuja.info", + "edgecastcdn.net", + "amazinglytimedphotos.com", + "value-domain.com", + "shareaholic.com", + "promptfile.com", + "voetbalzone.nl", + "ziare.com", + "trafficbroker.com", + "coolsport.tv", + "vanguardia.com.mx", + "makeshop.jp", + "mobilism.org", + "rebelmouse.com", + "brisbanetimes.com.au", + "publimetro.com.mx", + "coolkora.com", + "realsparrow.com", + "honda.co.jp", + "dealcatcher.com", + "sinoptik.ua", + "lefeng.com", + "mamaclub.com", + "bezuzyteczna.pl", + "smn.gov.ar", + "manhub.com", + "pcgameshardware.de", + "shipstation.com", + "zive.cz", + "disneystore.com", + "despegar.com.mx", + "addictivetips.com", + "path.com", + "pathofexile.com", + "seitenreport.de", + "pardot.com", + "decathlon.fr", + "flvrunner.com", + "xat.com", + "matchendirect.fr", + "roem.ru", + "wat.tv", + "edline.net", + "znds.com", + "northwestern.edu", + "friendscout24.de", + "movoto.com", + "dengiprosto.info", + "dlisted.com", + "sports.fr", + "maxifoot.fr", + "hubspot.net", + "natalie.mu", + "tabloidpulsa.co.id", + "polki.pl", + "gamasutra.com", + "matomeantena.com", + "adwords-community.com", + "gallup.com", + "redtubenacional.com", + "mangastream.com", + "zulutrade.com", + "wunderlist.com", + "taste.com.au", + "myhosting.com", + "totalbeauty.com", + "8pic.ir", + "sltrib.com", + "88db.com", + "minikoyuncu.com", + "net4.in", + "nuevoloquo.com", + "cago365.com", + "afip.gob.ar", + "keep2share.cc", + "kw.com", + "terra.com.pe", + "profi-forex.org", + "filecloud.io", + "peliculas4.com", + "mysitemyway.com", + "ilmattino.it", + "geizhals.de", + "sheershanews.com", + "whatwpthemeisthat.com", + "teknosa.com", + "myshopping.com.au", + "teamskeet.com", + "gandalfporn.com", + "2670.com", + "peoplefinders.com", + "elo7.com.br", + "sofoot.com", + "uhaul.com", + "sagepay.com", + "unb.br", + "jhu.edu", + "barrons.com", + "hasznaltauto.hu", + "winfxstrategy.com", + "spamcop.net", + "az.pl", + "op.fi", + "istruzione.it", + "eveonline.com", + "imgnip.com", + "jjoobb.cn", + "itesm.mx", + "wptavern.com", + "kproxy.com", + "pulptastic.com", + "quidco.com", + "ekantipur.com", + "joann.com", + "whattoexpect.com", + "tomshw.it", + "internetbookshop.it", + "followerwonk.com", + "cisionpoint.com", + "cint.com", + "quondos.com", + "suntimes.com", + "saveinter.net", + "eiga.com", + "jdzj.com", + "goldprice.org", + "websitemagazine.com", + "051jk.com", + "inweb24.com", + "gamgos.ae", + "elog-ch.com", + "trovit.com.mx", + "webnode.cz", + "ethnos.gr", + "oodle.com", + "subtitulos.es", + "gameinformer.com", + "9to5mac.com", + "youwatch.org", + "ibps.in", + "biggerpockets.com", + "sport24.co.za", + "my.tv.sohu.com/user/cc133", + "capsulecrm.com", + "fuett.mx", + "poznan.pl", + "melateiran.com", + "divxtotal.com", + "aftabir.com", + "cyberlink.com", + "ulozto.cz", + "jetswap.com", + "premium.wix.com", + "eprize.com", + "huntington.com", + "careerlink.vn", + "allhyipmonitors.com", + "autorambler.ru", + "jamieoliver.com", + "minube.com", + "credomatic.com", + "oddschecker.com", + "listia.com", + "mrrebates.com", + "8684.cn", + "kabam.com", + "tripit.com", + "gry.pl", + "ecvv.com", + "utanbaby.com", + "lavoixdunord.fr", + "just-eat.co.uk", + "go-for-files.com", + "afpbb.com", + "mybb.com", + "madmoizelle.com", + "sensacine.com", + "jtl-software.de", + "worldtimeserver.com", + "sunmaker.com", + "marieforleo.com", + "123sdfsdfsdfsd.ru", + "duga.jp", + "avery.com", + "tvguide.co.uk", + "salliemae.com", + "zap.com.br", + "my.tv.sohu.com/user/cc112", + "bookmark4you.com", + "twodollarclick.com", + "encontreinarede.com", + "joomshaper.com", + "marinetraffic.com", + "feedermatrix.com", + "nowvideo.at", + "millenium.org", + "newalbumreleases.net", + "frankkern.com", + "onlineconversion.com", + "ahlalhdeeth.com", + "mediawiki.org", + "xseo.in", + "generation-nt.com", + "naftemporiki.gr", + "hardforum.com", + "hornbunny.com", + "my.tv.sohu.com/user/cc124", + "housing.com", + "vogue.es", + "safemls.net", + "cuisineaz.com", + "estekhtam.com", + "coqnu.com", + "despegar.com.ar", + "fedoraproject.org", + "france2.fr", + "autovit.ro", + "tradetang.com", + "levelupgames.uol.com.br", + "dnforum.com", + "homebank.kz", + "carnival.com", + "hzycsj.com", + "army.mil", + "bootsnipp.com", + "istockimg.com", + "po-kaki-to.com", + "qiushibaike.com", + "tsb.co.uk", + "gzmama.com", + "aitnews.com", + "tripwiremagazine.com", + "torrenty.org", + "djangoproject.com", + "chelseafc.com", + "gamesgames.com", + "herold.at", + "baur.de", + "kauppalehti.fi", + "nrelate.com", + "clickwebinar.com", + "entropay.com", + "gridserver.com", + "virginia.gov", + "tuan800.com", + "trueactivist.com", + "caras.uol.com.br", + "fontawesome.io", + "haodou.com", + "reshareworthy.com", + "royalcaribbean.com", + "crakrevenue.com", + "motika.com.mk", + "xovi.de", + "deutsche-startups.de", + "inven.co.kr", + "atrapalo.com", + "ictv.ua", + "muji.net", + "kingplayer.com", + "adworkmedia.com", + "xjtu.edu.cn", + "thenounproject.com", + "professionali.ru", + "xizhi.com", + "boo-box.com", + "customink.com", + "groupon.my", + "spartoo.com", + "bannersdontwork.com", + "ncore.cc", + "reklama5.mk", + "macrojuegos.com", + "zenhabits.net", + "te3p.com", + "utah.gov", + "telesurtv.net", + "101.ru", + "5yi.com", + "lide.cz", + "ancestry.co.uk", + "sana.sy", + "siteslike.com", + "vg247.com", + "tyzhden.ua", + "ncsu.edu", + "apherald.com", + "taaza.com", + "traffic-delivery.com", + "uuyoyo.com", + "hwupgrade.it", + "game-debate.com", + "jetpack.wordpress.com", + "anycodes.com", + "femina.hu", + "zattoo.com", + "acm.org", + "comenity.net", + "tarot.com", + "azadliq.org", + "advertiserdigital.com", + "osym.gov.tr", + "empornium.me", + "netzero.net", + "myfico.com", + "nolix.ru", + "cnwnews.com", + "springpad.com", + "ahangestan.in", + "localbitcoins.com", + "nikkei.co.jp", + "youlikehits.com", + "boyfriendtv.com", + "wampserver.com", + "elnorte.com", + "codeschool.com", + "si.edu", + "nosis.com", + "downloadhelper.net", + "global-free-classified-ads.com", + "flagmantube.com", + "elnuevodiario.com.ni", + "handelsbanken.se", + "sverigesradio.se", + "femjoy.com", + "444.hu", + "calcalist.co.il", + "asiatech.ir", + "tv.nu", + "installmac.com", + "programmableweb.com", + "fotki.com", + "vidivodo.com", + "websquash.com", + "selfgrowth.com", + "wehkamp.nl", + "amawebs.com", + "3dlat.com", + "comunidades.net", + "streamin.to", + "idbi.com", + "cshtracker.com", + "cdbaby.com", + "mycommerce.com", + "united-domains.de", + "tradingview.com", + "niniweblog.com", + "avn.info.ve", + "apa.tv", + "pinnaclesports.com", + "adafruit.com", + "ipower.com", + "lebuteur.com", + "utah.edu", + "mobeoffice.com", + "lingualeo.com", + "fuub.net", + "tvmongol.com", + "namasthetelangaana.com", + "secretsearchenginelabs.com", + "whowhatwear.com", + "hiapk.com", + "torrentz.sx", + "php.su", + "sodahead.com", + "sbb.ch", + "k12.ca.us", + "jecontacte.com", + "thehollywoodgossip.com", + "pekao24.pl", + "yourtest-india.com", + "screwfix.com", + "humoron.com", + "yiwugou.com", + "mulher.uol.com.br", + "thehackernews.com", + "shopmania.ro", + "dlink.com", + "despegar.com", + "archlinux.org", + "nielsen.com", + "rosnet.ru", + "appsapk.com", + "funfarsi.ir", + "4over.com", + "subscribe.wordpress.com", + "lewrockwell.com", + "portalnet.cl", + "antenam.info", + "letribunaldunet.fr", + "see-tube.com", + "tureng.com", + "topcashback.co.uk", + "contactcars.com", + "jeux.fr", + "khan.co.kr", + "ffffound.com", + "gmail.com", + "laprensa.com.ni", + "jizzhut.com", + "liilas.com", + "muare.vn", + "kting.cn", + "heteml.jp", + "genericsteps.com", + "searchcompletion.com", + "dwayir.com", + "dicionarioinformal.com.br", + "vcita.com", + "fitsugar.com", + "kleiderkreisel.de", + "collective-evolution.com", + "ethz.ch", + "sendreach.com", + "abidjan.net", + "islamicfinder.org", + "elong.com", + "yonhapnews.co.kr", + "seratnews.ir", + "stream2watch.me", + "content-watch.ru", + "seotechnocrat.com", + "todaoferta.uol.com.br", + "toyota.jp", + "purevid.com", + "incredimail.com", + "kia.com", + "intensedebate.com", + "kaufmich.com", + "servis24.cz", + "brusheezy.com", + "rtbtraf.com", + "tejiawang.com", + "expedia.co.in", + "food52.com", + "top-channel.tv", + "jpnn.com", + "desitorrents.com", + "faxingw.cn", + "crutchfield.com", + "seneweb.com", + "usenet.nl", + "aviasales.ru", + "nesine.com", + "appshopper.com", + "dressupgamesite.com", + "lowendtalk.com", + "muslima.com", + "v-webs.com", + "linkomanija.net", + "geniuzz.com", + "caringbridge.org", + "gooya.com", + "dospy.com", + "siol.net", + "games.co.id", + "archiveofourown.org", + "rocketlawyer.com", + "free-downloadz.net", + "pastemagazine.com", + "invest-system.net", + "el-ahly.com", + "todayhumor.co.kr", + "racingpost.com", + "stereogum.com", + "soompi.com", + "pof.com.br", + "brainabundance.com", + "fontpalace.com", + "canon.jp", + "eldeforma.com", + "sacbee.com", + "jobscore.com", + "fashionista.com", + "mywork.vn", + "thisis50.com", + "cubadebate.cu", + "healthguru.com", + "instatheme.com", + "apotheken-umschau.de", + "blizko.ru", + "tinymce.com", + "virtualbox.org", + "tehran.ir", + "pr.gov.br", + "mlmleadsystempro.com", + "resbux.com", + "komonews.com", + "childrensplace.com", + "yoyopress.com", + "cloudify.cc", + "24livenewspaper.com", + "w4.com", + "islamway.net", + "fantagazzetta.com", + "imgspice.com", + "bovada.lv", + "payback.de", + "peb.pl", + "hotnigerianjobs.com", + "dev7studios.com", + "tejaratbank.net", + "takuhai.jp", + "travelmath.com", + "cultofmac.com", + "wakeupnow.com", + "insidefacebook.com", + "mediamarkt.es", + "500wan.com", + "rumah123.com", + "contentmarketinginstitute.com", + "ed.ac.uk", + "phimvang.com", + "womansday.com", + "lookatme.ru", + "citizensbank.com", + "esam.ir", + "phandroid.com", + "canadiantire.ca", + "serienjunkies.de", + "bbspink.com", + "yaranehkala.ir", + "vesti.bg", + "el-carabobeno.com", + "smarty.net", + "6tie.com", + "antpedia.com", + "p-world.co.jp", + "camelcamelcamel.com", + "sudu.cn", + "priberam.pt", + "infoworld.com", + "myrecipes.com", + "metrodeal.com", + "widgetbox.com", + "krone.at", + "outfox.tv", + "mymp4.in", + "685wo.com", + "sadanduseless.com", + "cgd.pt", + "mrmlsmatrix.com", + "parenting.com", + "tradepub.com", + "linkvehicle.com", + "mobiledia.com", + "rghost.ru", + "xxsy.net", + "goanimate.com", + "pikacn.com", + "metropcs.com", + "byu.edu", + "bitelia.com", + "designscrazed.com", + "wpde.org", + "paheal.net", + "nulled.cc", + "hmv.co.jp", + "openx.com", + "hyundai.com", + "vvvdj.com", + "reason.com", + "livesports.pl", + "polskieradio.pl", + "oszone.net", + "chinhphu.vn", + "performancehorizon.com", + "internetretailer.com", + "skoob.com.br", + "mediaworld.it", + "imgrind.com", + "bancopopular.es", + "seogadget.ru", + "m6.fr", + "rongbay.com", + "niazpardaz.com", + "rewity.com", + "ssense.com", + "sky-fire.com", + "flibusta.net", + "eat24hours.com", + "lotto.pl", + "cnw.com.cn", + "takvim.com.tr", + "englishtown.com", + "letudiant.fr", + "peliculasmas.com", + "askme.com", + "linksmanagement.com", + "webseoanalytics.com", + "presse-citron.net", + "eldiariodeamerica.com", + "instantshift.com", + "ampxchange.com", + "xe.gr", + "experts-exchange.com", + "sportcategory.com", + "sharecash.org", + "linksys.com", + "yninfo.com", + "onlinelic.in", + "aflamneek.com", + "22.cn", + "ubuntu-fr.org", + "cjs.com.cn", + "mp3clan.com", + "grani.ru", + "zen-cart.com", + "shippingchina.com", + "jsfor.net", + "wanadoo.es", + "zurker.com", + "chotot.vn", + "masaladesi.com", + "insight.ly", + "trivago.de", + "neue-sexpartner.com", + "boe.es", + "military.com", + "moonbasa.com", + "langlaoda.com", + "tnooz.com", + "zacks.com", + "liverpool.com.mx", + "internic.net", + "ecuavisa.com", + "autonews.ru", + "mcafeesecure.com", + "peopleclick.com", + "madthumbs.com", + "hot-live-chat.com", + "sugarcrm.com", + "notonthehighstreet.com", + "linio.com.co", + "smartbrief.com", + "auto-motor-und-sport.de", + "kyivpost.com", + "instantteleseminar.com", + "dillards.com", + "5i5j.com", + "runtastic.com", + "danawa.com", + "ladunliadi.blogspot.com", + "getnews.jp", + "romedic.ro", + "rodfile.com", + "instyle.com", + "swoodoo.com", + "bannersnack.com", + "evolife.cn", + "ktonanovenkogo.ru", + "office.com", + "zamalekfans.com", + "magiran.com", + "ithome.com", + "ktrmr.com", + "spoonful.com", + "olleh.com", + "bgr.in", + "industrialthemes.com", + "adbkm.com", + "tsa-algerie.com", + "bloggang.com", + "mojang.com", + "geoiptool.com", + "autocarindia.com", + "ajansspor.com", + "onmeda.de", + "midwayusa.com", + "alicdn.com", + "sportdog.gr", + "ideeli.com", + "haspa.de", + "gladbux.com", + "porn-star.com", + "experiandirect.com", + "yicheshi.com", + "acervoamador.com", + "designwall.com", + "balkanweb.com", + "response.jp", + "nettoos.com", + "beruby.com", + "manageyourloans.com", + "kidstaff.com.ua", + "indeed.co.za", + "monoprice.com", + "chsi.com.cn", + "boorsekala.com", + "mstaml.com", + "trafficvance.com", + "tatatele.in", + "startbootstrap.com", + "graphicburger.com", + "ad-vid-webs.com", + "jp.dk", + "one.co.il", + "athensvoice.gr", + "payu2blog.com", + "epidemz.net", + "dondominio.com", + "doviz.com", + "disquscdn.com", + "work.ua", + "vsemayki.ru", + "ikyu.com", + "songlyrics.com", + "onlinebank.com", + "thebump.com", + "travian.com.tr", + "java2s.com", + "twittmate.com", + "ignou.ac.in", + "relink.us", + "4738.com", + "printvenue.com", + "tekstowo.pl", + "natro.com", + "dengi-tut.info", + "freeservers.com", + "inbound.org", + "free-press-release.com", + "mydirtyhobby.com", + "gamefly.com", + "overclockers.co.uk", + "eci.nic.in", + "196.1.211.6", + "versus.com", + "batdongsan.com.vn", + "vrisko.gr", + "albayan.ae", + "trendcounter.com", + "500.com", + "pagomiscuentas.com", + "wankoz.com", + "duote.com", + "javascript.ru", + "chevrolet.com", + "coach.com", + "decidatriunfar.net", + "rememberthemilk.com", + "virginmobileusa.com", + "google.com.jm", + "ucm.es", + "ninja.co.jp", + "rqzao.com", + "periodistadigital.com", + "heritage.org", + "momtubesex.xxx", + "vocabulary.com", + "kupujemprodajem.com", + "e-cigarette-forum.com", + "paxum.com", + "ilpost.it", + "abercrombie.com", + "pearsonmylabandmastering.com", + "echoecho.com", + "expedia.co.jp", + "ticketfly.com", + "colostate.edu", + "sjtu.edu.cn", + "seobudget.ru", + "gigabyte.com", + "gsmspain.com", + "misr5.com", + "enterfactory.com", + "ciao.de", + "wuv.de", + "dagospia.com", + "slashgear.com", + "urdupoint.com", + "themepunch.com", + "travian.com", + "discuz.com", + "pixroute.com", + "otzovik.com", + "ubergizmo.com", + "edreams.fr", + "internet-positif.org", + "asmcentral.com", + "copadomundo.uol.com.br", + "adjal.com", + "gotomypc.com", + "partycity.com", + "leggo.it", + "blogosfere.it", + "teamcoco.com", + "pricedealsindia.com", + "glennbeck.com", + "allyou.com", + "china.org.cn", + "xitek.com", + "photoshopessentials.com", + "infopraca.pl", + "boardingarea.com", + "geni.com", + "rotaban.ru", + "foxtab.com", + "sadistic.pl", + "socialbro.com", + "tradesparq.com", + "itp.ne.jp", + "swefilmer.com", + "jobsite.co.uk", + "topwar.ru", + "yemeksepeti.com", + "tokfm.pl", + "businessinsider.com.au", + "moe.gov.eg", + "swedbank.lt", + "jxgdw.com", + "bayern.de", + "alistapart.com", + "bebinin.com", + "annunci69.it", + "plimus.com", + "cuevana2.tv", + "viamichelin.fr", + "whatsmydns.net", + "tarad.com", + "hostgator.com.br", + "hasbro.com", + "hi-chic.com", + "driverscollection.com", + "onetad.com", + "game2.com.cn", + "elitepvpers.com", + "tvuol.uol.com.br", + "caclubindia.com", + "diariocorreo.pe", + "c-rewards.com", + "business.gov.au", + "chooseauto.com.cn", + "timetrade.com", + "alternate.de", + "syracuse.com", + "advanceautoparts.com", + "shopware.de", + "newpct.com", + "proranktracker.com", + "cex.io", + "thebot.net", + "tradekorea.com", + "legifrance.gouv.fr", + "cpan.org", + "rightel.ir", + "247wallst.com", + "goldentowns.com", + "bankifsccode.com", + "color-hex.com", + "consumerist.com", + "juegosdiarios.com", + "on24.com", + "stockfreeimages.com", + "m4.cn", + "sponsoredtweets.com", + "open.ac.uk", + "bilyoner.com", + "lexisnexis.com", + "gsu.edu", + "popscreen.com", + "stiftung-warentest.de", + "education.com", + "leckerficken.de", + "hostinger.ru", + "staples.ca", + "greenpeace.org", + "mes-meilleurs-films.fr", + "delish.com", + "register.it", + "idiva.com", + "static.squarespace.com", + "ololo.fm", + "basketball-reference.com", + "tro-ma-ktiko.blogspot.gr", + "arabianbusiness.com", + "avianca.com", + "watchuseek.com", + "6play.fr", + "kora-online.tv", + "armagedomfilmes.biz", + "531314.com", + "craigslist.co.uk", + "inchallah.com", + "bongdaso.com", + "toprankblog.com", + "k12.com", + "itimes.com", + "html.net", + "utsandiego.com", + "gyakorikerdesek.hu", + "viddyhddownload.com", + "wannonce.com", + "qudong.com", + "ibicn.com", + "w3resource.com", + "socialsecurity.gov", + "tvn.pl", + "momsexclipz.com", + "fstoppers.com", + "kafeteria.pl", + "boulanger.fr", + "guj.nic.in", + "divaina.com", + "desktopnexus.com", + "topseda.ir", + "payumoney.com", + "klicktel.de", + "foxtv.es", + "cuatro.com", + "publix.com", + "astrosage.com", + "kompass.com", + "web-hosting.com", + "nus.edu.sg", + "jamiiforums.com", + "lancers.jp", + "peixeurbano.com.br", + "buysub.com", + "yp.com", + "enstage.com", + "6eat.com", + "1ent.com.cn", + "unionpaysecure.com", + "pcgames.de", + "ccidcom.com", + "lavenir.net", + "novaposhta.ua", + "hotpornshow.com", + "telebank.ru", + "avaz.ba", + "doit.com.cn", + "setlinks.ru", + "zagat.com", + "sportmaster.ru", + "utarget.ru", + "lingualeo.ru", + "compucalitv.com", + "anastasiadate.com", + "webmaster-rank.info", + "fastcoexist.com", + "lavasoft.com", + "ppvguru.com", + "greatist.com", + "knack.be", + "wowwiki.com", + "ilcorsaronero.info", + "jword.jp", + "750g.com", + "pantipmarket.com", + "top.de", + "photofunia.com", + "dryicons.com", + "alweeam.com.sa", + "dnfight.com", + "wpolityce.pl", + "vimeocdn.com", + "visit-x.net", + "takungpao.com", + "perfectworld.eu", + "kat.ph", + "hefei.cc", + "cmse.ru", + "virginia.edu", + "fudan.edu.cn", + "sethgodin.typepad.com", + "caracaschronicles.com", + "18qt.com", + "be2.com.br", + "rakuten.tw", + "bukkit.org", + "miss-no1.com", + "nts.org.pk", + "ulozto.net", + "mixpanel.com", + "glavred.info", + "vui.vn", + "mindspark.com", + "worldmarket.com", + "walmart.com.mx", + "deperu.com", + "taxact.com", + "m5zn.com", + "navy.mil", + "hibapress.com", + "cpasbien.com", + "interjet.com.mx", + "llbean.com", + "kenrockwell.com", + "japan-guide.com", + "financialpost.com", + "rankingsinstitute.com", + "master-x.com", + "pandawill.com", + "marketingweek.co.uk", + "freeforums.org", + "ysear.ch", + "pokerstars.com", + "wired.co.uk", + "amino.dk", + "rapidcrush.com", + "vivanuncios.com", + "blogsvertise.com", + "cwb.gov.tw", + "piter.tv", + "gistmania.com", + "topdocumentaryfilms.com", + "shoghlanty.com", + "bankmillennium.pl", + "burberry.com", + "youporngay.com", + "webresourcesdepot.com", + "beachbody.com", + "asstr.org", + "pwc.com", + "competitor.com", + "rss2search.com", + "vocuspr.com", + "behindthename.com", + "moniker.com", + "jayde.com", + "opensuse.org", + "poemhunter.com", + "toranoana.jp", + "interaksyon.com", + "aeon.co.jp", + "gungho.jp", + "fgov.be", + "xpg.uol.com.br", + "logic-immo.com", + "worldsex.com", + "watchonlineseries.eu", + "google.co.mz", + "discas.net", + "chmail.ir", + "consumercomplaints.in", + "allstate.com", + "twelveskip.com", + "ibibo.com", + "noelshack.com", + "bonprix.ru", + "directupload.net", + "equipobimlatino.com", + "weather2umbrella.com", + "aibang.com", + "blog.wordpress.com", + "turner.com", + "mediapart.fr", + "bloog.pl", + "monotaro.com", + "online.net", + "bodmillenium.com", + "distancesfrom.com", + "naosalvo.com.br", + "ap7am.com", + "focus.ua", + "cokeandpopcorn.ch", + "qwant.com", + "megaplan.ru", + "sinhvienit.net", + "dream-marriage.com", + "voicefive.com", + "forex.com.cn", + "xabbs.com", + "netmums.com", + "tuxboard.com", + "cs-cart.com", + "bitcoin.it", + "boblil.com", + "nirsoft.net", + "neopets.com", + "spin.com", + "costco.ca", + "mirraw.com", + "ara.cat", + "klamm.de", + "glam.com", + "guenstiger.de", + "citruspay.com", + "dnslink.com", + "guyism.com", + "shopotam.ru", + "tim.it", + "3suisses.fr", + "gooroops.com", + "kau.edu.sa", + "ukbusinessforums.co.uk", + "gree.jp", + "komikid.com", + "redakcja.pl", + "qpic.cn", + "voyeurweb.com", + "mathworks.com", + "monster.fr", + "seniat.gob.ve", + "computrabajo.com.co", + "xilu.com", + "madrid.org", + "ostraining.com", + "bharian.com.my", + "telegram.org", + "sumofus.org", + "whosdatedwho.com", + "moreofit.com", + "meetic.it", + "femina.mk", + "itworld.com", + "indiaglitz.com", + "cheatcc.com", + "filmfanatic.com", + "the-bux.net", + "very.co.uk", + "fetishshrine.com", + "speakingtree.in", + "iu.edu", + "modern.az", + "tak.ru", + "internetmarketingninjas.com", + "example.com", + "sportgeza.hu", + "wrestlezone.com", + "jameda.de", + "caikuu.com", + "gioco.it", + "yourtv.com.au", + "euro.com.pl", + "anz.co.nz", + "daft.ie", + "tirerack.com", + "vivareal.com.br", + "definebabe.com", + "ittefaq.com.bd", + "lastfm.es", + "zocalo.com.mx", + "dpd.de", + "somethingawful.com", + "virgin.com", + "shtyle.fm", + "viralforest.com", + "dntrck.com", + "its-mo.com", + "q.cc", + "kinghost.net", + "elpais.com.co", + "joomlaspanish.org", + "sopitas.com", + "wallbase.cc", + "cpalead.com", + "torrentfunk.com", + "bizcom.com.ru", + "quizony.com", + "rcgroups.com", + "spring.io", + "kuaidi100.com", + "xatakandroid.com", + "mlstatic.com", + "borsaat.com", + "jagobd.com", + "cosmiq.de", + "crucial.com", + "cdstm.cn", + "betclic.fr", + "telewebion.com", + "exhentai.org", + "gnezdo.ru", + "cebupacificair.com", + "fusionmls.com", + "shafaf.ir", + "ticketmaster.co.uk", + "adslzone.net", + "k8.cn", + "recipdonor.com", + "in.gov", + "mforos.com", + "asscj.com", + "linkconnector.com", + "ohfreesex.com", + "cntraveler.com", + "scambioetico.org", + "ladenzeile.de", + "ntn24.com", + "beyazperde.com", + "motherearthnews.com", + "yupptv.com", + "220tube.com", + "fizzle.co", + "lakii.com", + "deutsche-wirtschafts-nachrichten.de", + "saaid.net", + "bimlatino.com", + "verisign.com", + "funpatogh.com", + "matadornetwork.com", + "seatguru.com", + "lululemon.com", + "uzise.com", + "networkworld.com", + "hottube.me", + "jobsdb.com.hk", + "life.hu", + "bonusvid.com", + "incometaxindiaefiling.gov.in", + "ey.com", + "alraimedia.com", + "allposters.com", + "authorityroi.com", + "bmwusa.com", + "ebaystatic.com", + "entertainmentcrave.com", + "ehow.com.br", + "prisonplanet.com", + "elnashra.com", + "email.cz", + "yes123.com.tw", + "uitzendinggemist.nl", + "mindtools.com", + "atv.com.tr", + "shoes.net.cn", + "joomlaportal.de", + "themoneyconverter.com", + "meteomedia.com", + "bankaustria.at", + "advertise.com", + "trafficswarm.com", + "aztecaporno.com", + "fourseasons.com", + "movie-blog.org", + "govdelivery.com", + "didigames.com", + "invideo.biz", + "linux.org.ru", + "omelete.uol.com.br", + "babybus.cn", + "oursogo.com", + "ero-an.com", + "mediafax.ro", + "football.ua", + "sii.cl", + "dreamamateurs.com", + "cheaperthandirt.com", + "iplocation.net", + "insideview.com", + "hihe.vn", + "stop-sex.com", + "fubiz.net", + "url.org", + "greenmangaming.com", + "pornup.me", + "indofeed.com", + "forumhouse.ru", + "restaurant.com", + "key.com", + "windstream.net", + "brightedge.com", + "foreignpolicy.com", + "noticiasdatv.uol.com.br", + "logopond.com", + "datemule.com", + "webrootanywhere.com", + "stomp.com.sg", + "trthaber.com", + "bitterstrawberry.com", + "turbo.az", + "kbs.co.kr", + "joblo.com", + "seatimes.com.vn", + "kuwait.tt", + "258.com", + "kp.ua", + "moviesmobile.net", + "flamingtext.com", + "youngt.com", + "etradenow.cn", + "copart.com", + "tuanweihui.com", + "zinkwaphd.com", + "adsl.free.fr", + "infomaniak.ch", + "re-direcciona.me", + "mejorenvo.com", + "instantservice.com", + "harpersbazaar.com", + "colorado.edu", + "belboon.com", + "telelistas.net", + "photofans.cn", + "51seer.com", + "danjur.com", + "wunderweib.de", + "extratorrentlive.com", + "serpfox.com", + "androidkade.com", + "raja.ir", + "tampabay.com", + "ser-mujer.org", + "imasters.com.br", + "coachfactory.com", + "mojopages.com", + "makezine.com", + "forexschoolonline.com", + "bochk.com", + "siamsport.co.th", + "mdpr.jp", + "serialssolutions.com", + "videosexarchive.com", + "enlnks.com", + "cision.com", + "scratchinginfo.com", + "skidrowgames.net", + "optimizely.com", + "video2brain.com", + "ariva.de", + "audiomack.com", + "pimproll.com", + "house.gov", + "acs.org", + "pijamasurf.com", + "autoc-one.jp", + "southcn.com", + "vesti.az", + "300mbfilms.com", + "speedmystream.com", + "wooservers.com", + "siteliner.com", + "tinmoi.vn", + "extremepornvideos.com", + "cqsq.com", + "hello-today.com", + "turkcealtyazi.org", + "tvyo.com", + "dealmoon.com", + "openenglish.com", + "golfchannel.com", + "irancloob.com", + "ebs.in", + "portalcorreio.uol.com.br", + "beemp3s.org", + "condenast.com", + "mtwebcenters.com.tw", + "hatelabo.jp", + "pathci.net", + "mamsy.ru", + "noticiasdelmundo.org", + "guru3d.com", + "logomaker.com", + "adhitz.com", + "swiki.jp", + "turnitin.com", + "patoghu.com", + "carnaval.uol.com.br", + "cancer.org", + "clickprime8.com", + "fahrinfo-berlin.de", + "unisender.com", + "flashkhor.com", + "goodfon.ru", + "ethiotube.net", + "cunbang.com", + "ti-da.net", + "1phads.com", + "bien.hu", + "home77.com", + "mbabycare.com", + "vudu.com", + "fantasy8.com", + "landrover.com", + "opencartforum.ru", + "antena3.ro", + "getapp.com", + "torlock.com", + "wind.it", + "expatads.com", + "bloglines.com", + "sexytube.me", + "frontier.com", + "asandownload.com", + "groupon.pl", + "storesonlinepro.com", + "brands4friends.de", + "infogr.am", + "webeffector.ru", + "google.com.mm", + "ppchero.com", + "privacystar.com", + "ocregister.com", + "liex.ru", + "kopatheme.com", + "toggl.com", + "raywenderlich.com", + "hecha.cn", + "vpls.net", + "manpianyi.com", + "1881.no", + "scbeasy.com", + "plex.tv", + "selfridges.com", + "anumex.com", + "postplanner.com", + "thisiscolossal.com", + "you-will-date.com", + "idealist.org", + "emarketer.com", + "anibis.ch", + "dansmovies.com", + "footballitarin.com", + "fragrantica.com", + "10minutemail.com", + "ntu.edu.tw", + "clasicooo.com", + "onsugar.com", + "vr-zone.com", + "walkerplus.com", + "easymobilerecharge.com", + "cz001.com.cn", + "sportsseoul.com", + "inazumanews2.com", + "marketingdirecto.com", + "chartboost.com", + "unblocked.co", + "rhymezone.com", + "portalinmobiliario.com", + "afrointroductions.com", + "transunion.com", + "panorama.it", + "powershow.com", + "avval.ir", + "onlinemeetingnow.com", + "xoom.com", + "wnbc.com", + "tlen.pl", + "jetairways.com", + "vtb24.ru", + "offcn.com", + "crx7601.com", + "adverts.ie", + "kasserver.com", + "minhavida.com.br", + "jkpj.com", + "123contactform.com", + "imgtiger.com", + "folha.com.br", + "npcgo.com", + "semprot.com", + "moonfruit.com", + "jizzbell.com", + "online-stopwatch.com", + "allhiphop.com", + "hackaday.com", + "digitalplayground.com", + "careers24.com", + "cwq.com", + "wondershare.com", + "filesend.to", + "puromarketing.com", + "workopolis.com", + "mudainodocument.com", + "home.ne.jp", + "shorte.st", + "tanea.gr", + "sporza.be", + "vgorode.ua", + "google.co.zm", + "argumentua.com", + "3m.com", + "komputerswiat.pl", + "bongdaplus.vn", + "supersonicads.com", + "soundclick.com", + "lollipop-network.com", + "lpcloudsvr304.com", + "313.cn", + "rtl2.de", + "ts.cn", + "mglradio.com", + "diynetwork.com", + "komikcaps.net", + "all-that-is-interesting.com", + "ultipro.com", + "frombar.com", + "sitedeals.nl", + "codingforums.com", + "kansascity.com", + "twtrland.com", + "cengage.com", + "lolspotsarticles.com", + "jackthreads.com", + "jobstreet.com.sg", + "diffen.com", + "eme2000.com", + "superbru.com", + "droid-life.com", + "itb-berlin.de", + "estibot.com", + "yourepeat.com", + "112.ua", + "70e.com", + "grid.mk", + "cromaretail.com", + "jobstreet.com.ph", + "gloadmarket.com", + "gemius.com", + "huuto.net", + "vogue.it", + "divxonline.info", + "mytoys.de", + "cashnhits.com", + "minkchan.com", + "talabat.com", + "helpareporter.com", + "grandascent.com", + "catalunyacaixa.com", + "sparwelt.de", + "tvboxnow.com", + "imagecurl.org", + "coocan.jp", + "eatingwell.com", + "truelife.com", + "binaryoptionsbrands.com", + "foxsportsla.com", + "jjshouse.com", + "makaan.com", + "yamaha.com", + "activerain.com", + "excelforum.com", + "mobinnet.ir", + "kasikornbankgroup.com", + "googleplex.com", + "medianama.com", + "fullsail.edu", + "makeupandbeauty.com", + "marketwired.com", + "beachbodycoach.com", + "taloussanomat.fi", + "youngliving.us", + "dana.ir", + "adsgostar.com", + "esquire.com.cn", + "meyerweb.com", + "positivemed.com", + "harvestapp.com", + "tim.com.br", + "sumorobo.net", + "zbigz.com", + "schulferien.org", + "quzhao.com", + "tsinghua.edu.cn", + "vogue.com", + "ecrater.com", + "internetbs.net", + "oper.ru", + "kingstone.com.tw", + "olympic.org", + "code.org", + "wp.tv", + "adchakra.net", + "passarela.com.br", + "damn.com", + "spn.com.cn", + "sacitaslan.com", + "uplod.ir", + "kununu.com", + "englishclub.com", + "ibood.com", + "buzzfed.com", + "peopledaily.com.cn", + "vivastreet.it", + "adshostnet.com", + "npmjs.org", + "lovethatsex.com", + "garena.tw", + "hq-sex-tube.com", + "tvsubtitles.net", + "trademarkia.com", + "96down.com", + "morhipo.com", + "chuguoqu.com", + "free-css.com", + "getsecuredfiles.com", + "justia.com", + "paypal-community.com", + "wezoner.com", + "google-analytics.com", + "outsideonline.com", + "simplymeasured.com", + "wattsupwiththat.com", + "vancl.com", + "leadership.ng", + "plaisio.gr", + "fastcocreate.com", + "fewo-direkt.de", + "dedecms.com", + "bostonherald.com", + "elmundo.com.ve", + "idhostinger.com", + "animaljam.com", + "infoq.com", + "exler.ru", + "leprosorium.ru", + "avis.com", + "ulusalpost.com", + "namnak.com", + "goindigo.in", + "activeboard.com", + "fatsecret.com", + "4umf.com", + "porn-w.org", + "sarkariexam.co.in", + "patagonia.com", + "3u5.net", + "roshd.ir", + "netechangisme.com", + "ojogo.pt", + "ujipin.com", + "topuniversities.com", + "consequenceofsound.net", + "no-ip.biz", + "webdesignerwall.com", + "fin24.com", + "openculture.com", + "pbh2.com", + "dota2.com", + "secureupload.eu", + "alfajertv.com", + "canlidizihdtv.com", + "muffingroup.com", + "imageforum.com", + "paris.fr", + "kaufda.de", + "pelis24.com", + "ixian.cn", + "ebenpagan.com", + "sportsauthority.com", + "wish.com", + "ganeshaspeaks.com", + "meritnation.com", + "ijie.com", + "vodafone.com.tr", + "trovit.es", + "farsisubtitle.com", + "musawvir.com", + "resellerratings.com", + "soyentrepreneur.com", + "samakal.net", + "dietnavi.com", + "55tuan.com", + "elcomercio.com", + "hereisthecity.com", + "buienradar.nl", + "arenabg.com", + "indiansexstories.net", + "slideboom.com", + "findsection.net", + "rankrecon.com", + "e-radio.gr", + "cnn.co.jp", + "gucci.com", + "natura.net", + "googlesyndication.com", + "advancedwebranking.com", + "lib.ru", + "sfora.pl", + "uci.edu", + "haaretz.co.il", + "ing.be", + "yokboylebirsey.com.tr", + "myfact.tv", + "yenibiris.com", + "stuff.tv", + "dn.pt", + "whydontyoutrythis.com", + "nediyor.com", + "worldoftanks.asia", + "megamillions.com", + "elemanonline.net", + "computrabajo.com.pe", + "arxiv.org", + "livescore.tv", + "aintitcool.com", + "girlschannel.net", + "madbid.com", + "tadawul.com.sa", + "liqpay.com", + "softonic.jp", + "alhilal.com", + "bizpowa.com", + "thatguywiththeglasses.com", + "yemeneconomist.com", + "pazar3.mk", + "repai.com", + "1jux.net", + "solvusoft.com", + "yotpo.com", + "cuantocabron.com", + "ba.gov.br", + "arin.net", + "loginza.ru", + "homebank.ro", + "0755car.com", + "carscoops.com", + "antarvasna.com", + "tizag.com", + "offerpop.com", + "techmeme.com", + "ipress.ua", + "emuparadise.me", + "sexvidx.com", + "whatseek.com", + "become.co.jp", + "snapfish.com", + "gsis.gr", + "speedyshare.com", + "uncrate.com", + "doda.jp", + "straighttalk.com", + "rsjoomla.com", + "vozpopuli.com", + "gotprint.net", + "finansbank.com.tr", + "chinatrust.com.tw", + "stratoserver.net", + "icnetwork.co.uk", + "getpopcornti.me", + "pornper.com", + "3dmgame.com", + "squirt.org", + "mbusa.com", + "katespade.com", + "skat.dk", + "radiko.jp", + "dexonline.ro", + "bicaps.net", + "bitsoma.com", + "brasil247.com", + "alittlemarket.com", + "bd-pratidin.com", + "amorincontro.com", + "bigxvideos.com", + "nissan.co.jp", + "incomediary.com", + "theranking.com", + "el7l.co", + "webcamtoy.com", + "steamgifts.com", + "miles-and-more.com", + "szonline.net", + "blocked-website.com", + "kaztorka.org", + "avforums.com", + "indulgy.com", + "extendcp.co.uk", + "yonkis.com", + "ad1111.com", + "unionbankph.com", + "job.com", + "samanepay.com", + "aceona.com", + "kar.nic.in", + "tipico.com", + "sa.ae", + "froma.com", + "songza.com", + "calciomercato.com", + "besaba.com", + "x-rates.com", + "arsys.es", + "sisal.it", + "amateur.tv", + "designyourway.net", + "alpha.gr", + "nimble.com", + "livefreefun.com", + "wipo.int", + "webssearches.com", + "vodjk.com", + "91tiger.com", + "swisscom.com", + "tuvaro.com", + "parsonline.net", + "uolhost.com.br", + "cnwest.com", + "roksa.pl", + "socialmention.com", + "unibytes.com", + "screamingfrog.co.uk", + "owncloud.org", + "jacquielawson.com", + "openclipart.org", + "zimbra.com", + "poczta-polska.pl", + "hayah.cc", + "ringtonematcher.com", + "emai.com", + "wealink.com", + "celebzen.com", + "pichost.me", + "rediffmail.com", + "dinos.co.jp", + "netvasco.com.br", + "meilele.com", + "career.ru", + "signupgenius.com", + "screencast-o-matic.com", + "nav.no", + "reactiongifs.com", + "italki.com", + "mo.gov", + "bandsintown.com", + "elwatan.com", + "jdate.com", + "quiente.net", + "skynet.be", + "googlekeywordtool.com", + "nubilefilms.com", + "memedeportes.com", + "rewardingzonez.com", + "litmus.com", + "iyaxin.com", + "collegeconfidential.com", + "semperplugins.com", + "sofort.com", + "ineedhits.com", + "live24.gr", + "acronymfinder.com", + "jobui.com", + "9show.com", + "thedirty.com", + "tsutaya.co.jp", + "9787.com", + "brighthouse.com", + "boxingscene.com", + "indeed.com.br", + "morazzia.com", + "privatejobshub.blogspot.in", + "searchenginepeople.com", + "pozdravok.ru", + "watchstore.com.cn", + "rts.ch", + "pluska.sk", + "villagevoice.com", + "sprintrade.com", + "conversionxl.com", + "evsuite.com", + "bci.cl", + "lampsplus.com", + "gossipcop.com", + "valuecommerce.com", + "zalora.co.id", + "fastshop.com.br", + "hockeysfuture.com", + "bootply.com", + "chinavasion.com", + "menards.com", + "kinobar.net", + "tokyodisneyresort.jp", + "knownhost.com", + "ozbargain.com.au", + "olx.com.ve", + "sgk.gov.tr", + "berliner-sparkasse.de", + "bancogalicia.com.ar", + "joomla-master.org", + "augsburger-allgemeine.de", + "kroger.com", + "pulse.ng", + "putlocker.com", + "jrtj.com", + "tapuz.co.il", + "madhyamam.com", + "donews.com", + "showhaber.com", + "allxnxx.com", + "uned.es", + "adsharebux.com", + "d-addicts.com", + "banki.ir", + "levi.com", + "e97527f0.se", + "entertainmentwise.com", + "xvideos-field.com", + "naver.net", + "urbia.de", + "fashionsfriend.com", + "emuch.net", + "yelp.fr", + "metatube.com", + "sina.com.hk", + "omb100.com", + "grameenphone.com", + "stylebook.de", + "funny-games.biz", + "googlewebmastercentral.blogspot.com", + "vogella.com", + "surveymonkey.net", + "forex4you.org", + "mex.tl", + "online-fish-games.com", + "millenniumbcp.pt", + "fileom.com", + "friendorfollow.com", + "downloads.nl", + "typekit.com", + "gongkong.com", + "iqoption.com", + "tori.fi", + "smartsource.com", + "1616.net", + "1shoppingcart.com", + "novosti.rs", + "yofond.com", + "idokep.hu", + "motor.ru", + "xbiao.com", + "colorcombos.com", + "pway.cn", + "egynews.net", + "elshaab.org", + "telus.com", + "olx.com.co", + "cctvcj.com", + "kpopstarz.com", + "razorsocial.com", + "ncrypt.in", + "zonetheme.com", + "gumroad.com", + "e-shop.gr", + "kugli.com", + "levif.be", + "bookfi.org", + "globalresearch.ca", + "sas.com", + "cloudaccess.net", + "uplooder.net", + "exposedwebcams.com", + "1prime.ru", + "webappers.com", + "crocko.com", + "remax.com", + "ikco.com", + "dinside.no", + "winbank.gr", + "mijnwoordenboek.nl", + "weatherzone.com.au", + "allday2.com", + "sunat.gob.pe", + "getit.in", + "entrance-exam.net", + "i2ya.com", + "tlife.gr", + "qyer.com", + "calibre-ebook.com", + "internetcorkboard.com", + "unitedhyipleague.com", + "spyrestudios.com", + "metal-archives.com", + "aeromexico.com", + "cv-library.co.uk", + "bdr1.net", + "aremo.com.br", + "blip.tv", + "afilio.com.br", + "renewalcoupons.com", + "proxfree.com", + "magix.com", + "jobs.net", + "souism.com", + "adtcstrk.com", + "goodhousekeeping.com", + "lastfm.ru", + "sharecare.com", + "porn.xxx", + "deathandtaxesmag.com", + "unocero.com", + "wer-kennt-wen.de", + "hotfrog.com", + "intraship.de", + "press24.mk", + "yournewscorner.com", + "rbc.com", + "eventure.com", + "minglebox.com", + "legendas.tv", + "drp.su", + "voicefrom.me", + "venezuelaaldia.com", + "cooks.com", + "numbeo.com", + "chilehardware.cl", + "cryptocointalk.com", + "venusfactor.com", + "recruitmentcareer.in", + "larazon.es", + "sptechs.com", + "asx.com.au", + "ahlife.com", + "st001.com", + "actudesfinances.info", + "talkarcades.com", + "nitori-net.jp", + "chinahr.com", + "websitetestlink.com", + "hypem.com", + "demorgen.be", + "sharefile.com", + "prensalibre.com", + "eldia.com.ar", + "billiger.de", + "3docean.net", + "streamsend.com", + "tuaw.com", + "portafolio.co", + "trivago.com", + "olxpix.com", + "geotrust.com", + "torrentgun.net", + "enha.kr", + "pcastuces.com", + "t.cn", + "dimsemenov.com", + "cam4.nl", + "basspro.com", + "macupdate.com", + "strawpoll.me", + "elintransigente.com", + "homeaway.co.uk", + "my.ecwid.com", + "graphpaperpress.com", + "architonic.com", + "flowplayer.org", + "syshl.com", + "18schoolgirlz.com" + }); + + return urls; +} diff --git a/examples/urls_large_data.h b/examples/urls_large_data.h new file mode 100644 index 00000000..5918d7c0 --- /dev/null +++ b/examples/urls_large_data.h @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef URLS_LARGE_DATA_H_INCLUDED +#define URLS_LARGE_DATA_H_INCLUDED + +#include + +std::vector const& +urls_large_data(); + +#endif diff --git a/examples/websocket_async_echo_peer.h b/examples/websocket_async_echo_peer.h new file mode 100644 index 00000000..e494cb85 --- /dev/null +++ b/examples/websocket_async_echo_peer.h @@ -0,0 +1,267 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// Asynchronous WebSocket echo client/server +// +class async_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + +public: + async_echo_peer(bool server, + endpoint_type const& ep, std::size_t threads) + : sock_(ios_) + , acceptor_(ios_) + { + if(server) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{std::move(sock_), ep}; + } + thread_.reserve(threads); + for(std::size_t i = 0; i < threads; ++i) + thread_.emplace_back( + [&]{ ios_.run(); }); + } + + ~async_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class Peer + { + struct data + { + int state = 0; + boost::optional ep; + websocket::stream ws; + websocket::opcode op; + beast::streambuf sb; + int id; + + data(socket_type&& sock_) + : ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + + data(socket_type&& sock_, + endpoint_type const& ep_) + : ep(ep_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + }; + + std::shared_ptr d_; + + public: + Peer(Peer&&) = default; + Peer(Peer const&) = default; + Peer& operator=(Peer&&) = delete; + Peer& operator=(Peer const&) = delete; + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "async_echo_server"); + } + }; + + template + explicit + Peer(socket_type&& sock, Args&&... args) + : d_(std::make_shared( + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.ws.set_option(decorate(identity{})); + d.ws.set_option(read_message_max(64 * 1024 * 1024)); + run(); + } + + void run() + { + auto& d = *d_; + if(! d.ep) + { + d.ws.async_accept(std::move(*this)); + } + else + { + d.state = 4; + d.ws.next_layer().async_connect( + *d.ep, std::move(*this)); + } + } + + void operator()(error_code ec) + { + auto& d = *d_; + switch(d_->state) + { + // did accept + case 0: + if(ec) + return fail(ec, "async_accept"); + + // start + case 1: + if(ec) + return fail(ec, "async_handshake"); + d.sb.consume(d.sb.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.sb, std::move(*this)); + return; + + // got message + case 2: + if(ec == websocket::error::closed) + return; + if(ec) + return fail(ec, "async_read"); + // write message + d.state = 1; + d.ws.set_option(websocket::message_type(d.op)); + d.ws.async_write(d.sb.data(), std::move(*this)); + return; + + // connected + case 4: + if(ec) + return fail(ec, "async_connect"); + d.state = 1; + d.ws.async_handshake( + d.ep->address().to_string() + ":" + + std::to_string(d.ep->port()), + "/", std::move(*this)); + return; + } + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != websocket::error::closed) + std::cerr << "#" << d_->id << " " << + what << ": " << ec.message() << std::endl; + } + }; + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + Peer{std::move(sock)}; + } +}; + +} // websocket +} // beast + +#endif diff --git a/examples/websocket_echo.cpp b/examples/websocket_echo.cpp new file mode 100644 index 00000000..deb1ef8b --- /dev/null +++ b/examples/websocket_echo.cpp @@ -0,0 +1,36 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "websocket_async_echo_peer.h" +#include "websocket_sync_echo_peer.h" +#include "sig_wait.h" + +int main() +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + beast::websocket::async_echo_peer s1(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6000 }, 4); + + beast::websocket::sync_echo_peer s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6001 }); + + sig_wait(); +} diff --git a/examples/websocket_example.cpp b/examples/websocket_example.cpp new file mode 100644 index 00000000..59f75a1c --- /dev/null +++ b/examples/websocket_example.cpp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include + +int main() +{ + // Normal boost::asio setup + std::string const host = "echo.websocket.org"; + boost::asio::io_service ios; + boost::asio::ip::tcp::resolver r(ios); + boost::asio::ip::tcp::socket sock(ios); + boost::asio::connect(sock, + r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); + + using namespace beast::websocket; + + // WebSocket connect and send message using beast + stream ws(sock); + ws.handshake(host, "/"); + ws.write(boost::asio::buffer("Hello, world!")); + + // Receive WebSocket message, print and close using beast + beast::streambuf sb; + opcode op; + ws.read(op, sb); + ws.close(close_code::normal); + std::cout << + beast::debug::buffers_to_string(sb.data()) << "\n"; +} diff --git a/examples/websocket_sync_echo_peer.h b/examples/websocket_sync_echo_peer.h new file mode 100644 index 00000000..6b2fe40b --- /dev/null +++ b/examples/websocket_sync_echo_peer.h @@ -0,0 +1,192 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// Synchronous WebSocket echo client/server +// +class sync_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::thread thread_; + +public: + sync_echo_peer(bool server, endpoint_type ep) + : sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + + ~sync_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + static + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + static + void + fail(int id, error_code ec, std::string what) + { + std::cerr << "#" << std::to_string(id) << " " << + what << ": " << ec.message() << std::endl; + } + + static + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + struct lambda + { + int id; + sync_echo_peer& self; + socket_type sock; + boost::asio::io_service::work work; + + lambda(int id_, sync_echo_peer& self_, + socket_type&& sock_) + : id(id_) + , self(self_) + , sock(std::move(sock_)) + , work(sock.get_io_service()) + { + } + + void operator()() + { + self.do_peer(id, std::move(sock)); + } + }; + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + static int id_ = 0; + std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "sync_echo_server"); + } + }; + + void + do_peer(int id, socket_type&& sock) + { + websocket::stream ws(std::move(sock)); + ws.set_option(decorate(identity{})); + ws.set_option(read_message_max(64 * 1024 * 1024)); + error_code ec; + ws.accept(ec); + if(ec) + { + fail(id, ec, "accept"); + return; + } + for(;;) + { + websocket::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + break; + ws.set_option(websocket::message_type(op)); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != websocket::error::closed) + { + fail(id, ec, "read"); + } + } +}; + +} // websocket +} // beast + +#endif diff --git a/include/beast/async_completion.hpp b/include/beast/async_completion.hpp new file mode 100644 index 00000000..121c037f --- /dev/null +++ b/include/beast/async_completion.hpp @@ -0,0 +1,87 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_ASYNC_COMPLETION_HPP +#define BEAST_ASYNC_COMPLETION_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** Helper for customizing the return type of asynchronous initiation functions. + + This class template is used to transform caller-provided completion + tokens in calls to asynchronous initiation functions. The transformation + allows customization of the return type of the initiating function, and the + function signature of the final handler. + + @tparam CompletionToken A CompletionHandler, or a user defined type + with specializations for customizing the return type (for example, + `boost::asio::use_future` or `boost::asio::yield_context`). + + @tparam Signature The callable signature of the final completion handler. + + Example: + @code + ... + template + typename async_completion::result_type + async_initfn(..., CompletionToken&& token) + { + async_completion completion(token); + ... + return completion.result.get(); + } + @endcode + + See + Library Foundations For Asynchronous Operations +*/ +template +struct async_completion +{ + /** The type of the final handler called by the asynchronous initiation function. + + Objects of this type will be callable with the specified signature. + */ + using handler_type = + typename boost::asio::handler_type< + CompletionToken, Signature>::type; + + /// The type of the value returned by the asynchronous initiation function. + using result_type = typename + boost::asio::async_result::type; + + /** Construct the helper. + + @param token The completion token. Copies will be made as + required. If `CompletionToken` is movable, it may also be moved. + */ + async_completion(typename std::remove_reference::type& token) + : handler(std::forward(token)) + , result(handler) + { + static_assert(is_Handler::value, + "Handler requirements not met"); + } + + /// The final completion handler, callable with the specified signature. + handler_type handler; + + /// The return value of the asynchronous initiation function. + boost::asio::async_result result; +}; + +} // beast + +#endif diff --git a/include/beast/basic_streambuf.hpp b/include/beast/basic_streambuf.hpp new file mode 100644 index 00000000..9ff52af5 --- /dev/null +++ b/include/beast/basic_streambuf.hpp @@ -0,0 +1,298 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BASIC_STREAMBUF_HPP +#define BEAST_BASIC_STREAMBUF_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A `Streambuf` that uses multiple buffers internally. + + The implementation uses a sequence of one or more character arrays + of varying sizes. Additional character array objects are appended to + the sequence to accommodate changes in the size of the character + sequence. + + @tparam Allocator The allocator to use for managing memory. +*/ +template +class basic_streambuf +#if ! GENERATING_DOCS + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: + /// The type of allocator used. + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; + +private: + // Storage for the list of buffers representing the input + // and output sequences. The allocation for each element + // contains `element` followed by raw storage bytes. + class element; + + using alloc_traits = std::allocator_traits; + using list_type = typename boost::intrusive::make_list>::type; + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + + using size_type = typename std::allocator_traits::size_type; + using const_buffer = boost::asio::const_buffer; + using mutable_buffer = boost::asio::mutable_buffer; + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + list_type list_; // list of allocated buffers + iterator out_; // element that contains out_pos_ + size_type alloc_size_; // min amount to allocate + size_type in_size_ = 0; // size of the input sequence + size_type in_pos_ = 0; // input offset in list_.front() + size_type out_pos_ = 0; // output offset in *out_ + size_type out_end_ = 0; // output end offset in list_.back() + +public: +#if GENERATING_DOCS + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = implementation_defined; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = implementation_defined; + +#else + class const_buffers_type; + + class mutable_buffers_type; + +#endif + + /// Destructor. + ~basic_streambuf(); + + /** Move constructor. + + The output sequence of this object will be empty. + + After the move, the moved-from object will have an + empty input and output sequence, with no internal + buffers allocated. + + @param other The stream buffer to move from. + */ + basic_streambuf(basic_streambuf&& other); + + /** Move constructor. + + The output sequence of this object will be empty. + + After the move, the moved-from object will have an + empty input and output sequence, with no internal + buffers allocated. + + @param other The stream buffer to move from. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_streambuf(basic_streambuf&& other, + allocator_type const& alloc); + + /** Move assignment. + + The output sequence of this object will be empty. + + After the move, the moved-from object will have an + empty input and output sequence, with no internal + buffers allocated. + + @param other The stream buffer to move from. + */ + basic_streambuf& + operator=(basic_streambuf&& other); + + /// Copy constructor. + basic_streambuf(basic_streambuf const& other); + + /** Copy constructor. + + The output sequence of this object will be empty. + + @param other The stream buffer to copy. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_streambuf(basic_streambuf const& other, + allocator_type const& alloc); + + /** Copy assignment. + + The output sequence of this object will be empty. + + @param other The stream buffer to copy. + */ + basic_streambuf& operator=(basic_streambuf const& other); + + /** Copy constructor. + + The output sequence of this object will be empty. + + @param other The stream buffer to copy. + */ + template + basic_streambuf(basic_streambuf const& other); + + /** Copy constructor. + + The output sequence of this object will be empty. + + @param other The stream buffer to copy. + + @param alloc The allocator to associate with the + stream buffer. + */ + template + basic_streambuf(basic_streambuf const& other, + allocator_type const& alloc); + + /** Copy assignment. + + The output sequence of this object will be empty. + + @param other The stream buffer to copy. + */ + template + basic_streambuf& operator=(basic_streambuf const& other); + + /** Default constructor. + + @param alloc_size The size of buffer to allocate. This is a soft + limit, calls to prepare for buffers exceeding this size will allocate + the larger size. + + @param alloc The allocator to use. + */ + explicit + basic_streambuf(std::size_t alloc_size = 1024, + Allocator const& alloc = allocator_type{}); + + /// Get the associated allocator + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Get the maximum size of the basic_streambuf. + size_type + max_size() const + { + return std::numeric_limits::max(); + } + + /// Get the size of the input sequence. + size_type + size() const + { + return in_size_; + } + + /// Get a list of buffers that represents the output sequence, with the given size. + mutable_buffers_type + prepare(size_type n); + + /// Move bytes from the output sequence to the input sequence. + void + commit(size_type n); + + /// Get a list of buffers that represents the input sequence. + const_buffers_type + data() const; + + /// Remove bytes from the input sequence. + void + consume(size_type n); + + /// Clear everything. + void + clear(); + + // Helper for read_until + template + friend + std::size_t + read_size_helper(basic_streambuf< + OtherAllocator> const& streambuf, std::size_t max_size); + +private: + void + move_assign(basic_streambuf& other, std::false_type); + + void + move_assign(basic_streambuf& other, std::true_type); + + void + copy_assign(basic_streambuf const& other, std::false_type); + + void + copy_assign(basic_streambuf const& other, std::true_type); + + void + delete_list(); + + std::size_t + prepare_size() const; + + void + debug_check() const; +}; + +/** Format output to a stream buffer. + + @param streambuf The streambuf to write to. + + @param t The object to write. + + @return The stream buffer. +*/ +template +basic_streambuf& +operator<<(basic_streambuf& streambuf, T const& t); + +/** Convert the entire basic_streambuf to a string. + + @param streambuf The streambuf to convert. + + @return A string representing the contents of the input sequence. +*/ +template +std::string +to_string(basic_streambuf const& streambuf); + +} // beast + +#include + +#endif diff --git a/include/beast/bind_handler.hpp b/include/beast/bind_handler.hpp new file mode 100644 index 00000000..cd40645c --- /dev/null +++ b/include/beast/bind_handler.hpp @@ -0,0 +1,162 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BIND_HANDLER_HPP +#define BEAST_BIND_HANDLER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +/* Nullary handler that calls Handler with bound arguments. + + The bound handler provides the same io_service execution + guarantees as the original handler. +*/ +template +class bound_handler +{ +private: + using args_type = std::tuple::type...>; + + Handler h_; + args_type args_; + + template + static void invoke(Handler& h, Tuple& args, + index_sequence) + { + h(std::get(args)...); + } + +public: + using result_type = void; + + template + explicit + bound_handler(DeducedHandler&& handler, Args&&... args) + : h_(std::forward(handler)) + , args_(std::forward(args)...) + { + } + + void + operator()() + { + invoke(h_, args_, + index_sequence_for ()); + } + + void + operator()() const + { + invoke(h_, args_, + index_sequence_for ()); + } + + friend + void* + asio_handler_allocate( + std::size_t size, bound_handler* h) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, h->h_); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, bound_handler* h) + { + boost_asio_handler_alloc_helpers:: + deallocate(p, size, h->h_); + } + + friend + bool + asio_handler_is_continuation(bound_handler* h) + { + return boost_asio_handler_cont_helpers:: + is_continuation (h->h_); + } + + template + friend + void + asio_handler_invoke(F&& f, bound_handler* h) + { + boost_asio_handler_invoke_helpers:: + invoke(f, h->h_); + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +/** Bind parameters to a completion handler, creating a wrapped handler. + + This function creates a new handler which invoked with no parameters + calls the original handler with the list of bound arguments. The passed + handler and arguments are forwarded into the returned handler, which + provides the same `io_service` execution guarantees as the original + handler. + + Unlike `io_service::wrap`, the returned handler can be used in a + subsequent call to `io_service::post` instead of `io_service::dispatch`, + to ensure that the handler will not be invoked immediately by the + calling function. + + Example: + @code + template + void + do_cancel(AsyncReadStream& stream, ReadHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::forward(handler), + boost::asio::error::operation_aborted, 0)); + } + @endcode + + @param handler The handler to wrap. + + @param args A list of arguments to bind to the handler. The + arguments are forwarded into the returned object. +*/ +template +#if GENERATING_DOCS +implementation_defined +#else +detail::bound_handler< + typename std::decay::type, Args...> +#endif +bind_handler(CompletionHandler&& handler, Args&&... args) +{ + return detail::bound_handler::type, Args...>(std::forward< + CompletionHandler>(handler), + std::forward(args)...); +} + +} // beast + +namespace std { +template +void bind(beast::detail::bound_handler< + Handler, Args...>, ...) = delete; +} // std + +#endif diff --git a/include/beast/buffer_cat.hpp b/include/beast/buffer_cat.hpp new file mode 100644 index 00000000..bfbb2d2e --- /dev/null +++ b/include/beast/buffer_cat.hpp @@ -0,0 +1,506 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BUFFER_CAT_HPP +#define BEAST_BUFFER_CAT_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +template +class buffer_cat_helper +{ + std::tuple bs_; + +public: + using value_type = ValueType; + + class const_iterator; + + buffer_cat_helper(buffer_cat_helper&&) = default; + buffer_cat_helper(buffer_cat_helper const&) = default; + buffer_cat_helper& operator=(buffer_cat_helper&&) = default; + buffer_cat_helper& operator=(buffer_cat_helper const&) = default; + + explicit + buffer_cat_helper(Bs const&... bs) + : bs_(bs...) + { + } + + const_iterator + begin() const; + + const_iterator + end() const; +}; + +template +std::size_t constexpr +max_sizeof() +{ + return sizeof(U); +} + +template +std::size_t constexpr +max_sizeof() +{ + return + max_sizeof() > max_sizeof() ? + max_sizeof() : max_sizeof(); +} + +template +class buffer_cat_helper< + ValueType, Bs...>::const_iterator +{ + std::size_t n_; + std::tuple const* bs_; + std::array()> buf_; + + friend class buffer_cat_helper; + + template + using C = std::integral_constant; + + template + using iter_t = typename std::tuple_element< + I, std::tuple>::type::const_iterator; + + template + iter_t& + iter() + { + return *reinterpret_cast< + iter_t*>(buf_.data()); + } + + template + iter_t const& + iter() const + { + return *reinterpret_cast< + iter_t const*>(buf_.data()); + } + +public: + using value_type = ValueType; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + ~const_iterator(); + const_iterator(); + const_iterator(const_iterator&& other); + const_iterator(const_iterator const& other); + const_iterator& operator=(const_iterator&& other); + const_iterator& operator=(const_iterator const& other); + + bool + operator==(const_iterator const& other) const; + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const; + + pointer + operator->() const = delete; + + const_iterator& + operator++(); + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--(); + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + const_iterator( + std::tuple const& bs, bool at_end); + + void + construct(C) + { + auto constexpr I = sizeof...(Bs); + n_ = I; + } + + template + void + construct(C) + { + if(std::get(*bs_).begin() != + std::get(*bs_).end()) + { + n_ = I; + new(buf_.data()) iter_t{ + std::get(*bs_).begin()}; + return; + } + construct(C{}); + } + + void + destroy(C) + { + return; + } + + template + void + destroy(C) + { + if(n_ == I) + { + using Iter = iter_t; + iter().~Iter(); + return; + } + destroy(C{}); + } + + void + move(C, const_iterator&&) + { + return; + } + + template + void + move(C, const_iterator&& other) + { + if(n_ == I) + { + new(buf_.data()) iter_t{ + std::move(other.iter())}; + return; + } + move(C{}, std::move(other)); + } + + void + copy(C, const_iterator const&) + { + return; + } + + template + void + copy(C, const_iterator const& other) + { + if(n_ == I) + { + new(buf_.data()) iter_t{ + other.iter()}; + return; + } + copy(C{}, other); + } + + bool + equal(C, + const_iterator const&) const + { + return true; + } + + template + bool + equal(C, const_iterator const& other) const + { + if(n_ == I) + return iter() == other.iter(); + return equal(C{}, other); + } + + [[noreturn]] + reference + dereference(C) const + { + throw std::logic_error("invalid iterator"); + } + + template + reference + dereference(C) const + { + if(n_ == I) + return *iter(); + return dereference(C{}); + } + + [[noreturn]] + void + increment(C) + { + throw std::logic_error("invalid iterator"); + } + + template + void + increment(C) + { + if(n_ == I) + { + if(++iter() != + std::get(*bs_).end()) + return; + using Iter = iter_t; + iter().~Iter(); + return construct(C{}); + } + increment(C{}); + } + + void + decrement(C) + { + auto constexpr I = sizeof...(Bs); + if(n_ == I) + { + --n_; + new(buf_.data()) iter_t{ + std::get(*bs_).end()}; + } + decrement(C{}); + } + + void + decrement(C<0>) + { + auto constexpr I = 0; + if(iter() != std::get(*bs_).begin()) + { + --iter(); + return; + } + throw std::logic_error("invalid iterator"); + } + + template + void + decrement(C) + { + if(n_ == I) + { + if(iter() != std::get(*bs_).begin()) + { + --iter(); + return; + } + --n_; + using Iter = iter_t; + iter().~Iter(); + new(buf_.data()) iter_t{ + std::get(*bs_).end()}; + } + decrement(C{}); + } +}; + +//------------------------------------------------------------------------------ + +template +buffer_cat_helper:: +const_iterator::~const_iterator() +{ + destroy(C<0>{}); +} + +template +buffer_cat_helper:: +const_iterator::const_iterator() + : n_(sizeof...(Bs)) + , bs_(nullptr) +{ +} + +template +buffer_cat_helper:: +const_iterator::const_iterator( + std::tuple const& bs, bool at_end) + : bs_(&bs) +{ + if(at_end) + n_ = sizeof...(Bs); + else + construct(C<0>{}); +} + +template +buffer_cat_helper:: +const_iterator::const_iterator(const_iterator&& other) + : n_(other.n_) + , bs_(other.bs_) +{ + move(C<0>{}, std::move(other)); +} + +template +buffer_cat_helper:: +const_iterator::const_iterator(const_iterator const& other) + : n_(other.n_) + , bs_(other.bs_) +{ + copy(C<0>{}, other); +} + +template +auto +buffer_cat_helper:: +const_iterator::operator=(const_iterator&& other) -> + const_iterator& +{ + if(&other == this) + return *this; + destroy(C<0>{}); + n_ = other.n_; + bs_ = other.bs_; + move(C<0>{}, std::move(other)); + return *this; +} + +template +auto +buffer_cat_helper:: +const_iterator::operator=(const_iterator const& other) -> +const_iterator& +{ + if(&other == this) + return *this; + destroy(C<0>{}); + n_ = other.n_; + bs_ = other.bs_; + copy(C<0>{}, other); + return *this; +} + +template +bool +buffer_cat_helper:: +const_iterator::operator==(const_iterator const& other) const +{ + if(bs_ != other.bs_) + return false; + if(n_ != other.n_) + return false; + return equal(C<0>{}, other); +} + +template +auto +buffer_cat_helper:: +const_iterator::operator*() const -> + reference +{ + return dereference(C<0>{}); +} + +template +auto +buffer_cat_helper:: +const_iterator::operator++() -> + const_iterator& +{ + increment(C<0>{}); + return *this; +} + +template +auto +buffer_cat_helper:: +const_iterator::operator--() -> + const_iterator& +{ + decrement(C{}); + return *this; +} + +template +auto +buffer_cat_helper::begin() const -> + const_iterator +{ + return const_iterator(bs_, false); +} + +template +auto +buffer_cat_helper::end() const -> + const_iterator +{ + return const_iterator(bs_, true); +} + +} // detail + +//------------------------------------------------------------------------------ + +/** Concatenate 2 or more buffer sequences to form a `ConstBufferSequence`. + + This function returns a `ConstBufferSequence` that when iterated, + efficiently concatenates the input buffer sequences. Copies of the + arguments passed will be made; however, the returned object does + not take ownership of the underlying memory. The application is still + responsible for managing the lifetime of the referenced memory. + + @param buffers The list of buffer sequences to concatenate. + + @return A new `ConstBufferSequence` that represents the concatenation + of the input buffer sequences. +*/ +#if GENERATING_DOCS +template +implementation_defined +buffer_cat(BufferSequence const&... buffers) +#else +template +detail::buffer_cat_helper< + boost::asio::const_buffer, B1, B2, Bn...> +buffer_cat(B1 const& b1, B2 const& b2, Bn const&... bn) +#endif +{ + return detail::buffer_cat_helper< + boost::asio::const_buffer, + B1, B2, Bn...>(b1, b2, bn...); +} + +} // beast + +#endif diff --git a/include/beast/buffers_adapter.hpp b/include/beast/buffers_adapter.hpp new file mode 100644 index 00000000..708b8144 --- /dev/null +++ b/include/beast/buffers_adapter.hpp @@ -0,0 +1,142 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BUFFERS_ADAPTER_HPP +#define BEAST_BUFFERS_ADAPTER_HPP + +#include +#include + +namespace beast { + +/** Adapts a `MutableBufferSequence` into a `Streambuf`. + + This class wraps a `MutableBufferSequence` to meet the requirements + of `Streambuf`. Upon construction the input and output sequences are + empty. A copy of the mutable buffer sequence object is stored; however, + ownership of the underlying memory is not transferred. The caller is + responsible for making sure that referenced memory remains valid + for the duration of any operations. + + The size of the mutable buffer sequence determines the maximum + number of bytes which may be prepared and committed. + + @tparam Buffers The type of mutable buffer sequence to wrap. +*/ +template +class buffers_adapter +{ +private: + using buffers_type = typename std::decay::type; + using iter_type = typename buffers_type::const_iterator; + + static auto constexpr is_mutable = + std::is_constructible::value_type>::value; + + Buffers bs_; + iter_type begin_; + iter_type out_; + iter_type end_; + std::size_t max_size_; + std::size_t in_pos_ = 0; // offset in *begin_ + std::size_t in_size_ = 0; // size of input sequence + std::size_t out_pos_ = 0; // offset in *out_ + std::size_t out_end_ = 0; // output end offset + + template + buffers_adapter(Deduced&& other, + std::size_t nbegin, std::size_t nout, + std::size_t nend) + : bs_(std::forward(other).bs_) + , begin_(std::next(bs_.begin(), nbegin)) + , out_(std::next(bs_.begin(), nout)) + , end_(std::next(bs_.begin(), nend)) + , max_size_(other.max_size_) + , in_pos_(other.in_pos_) + , in_size_(other.in_size_) + , out_pos_(other.out_pos_) + , out_end_(other.out_end_) + { + } + +public: +#if GENERATING_DOCS + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = implementation_defined; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = implementation_defined; + +#else + class const_buffers_type; + + class mutable_buffers_type; + +#endif + + /// Move constructor. + buffers_adapter(buffers_adapter&& other); + + /// Copy constructor. + buffers_adapter(buffers_adapter const& other); + + /// Move assignment. + buffers_adapter& operator=(buffers_adapter&& other); + + /// Copy assignment. + buffers_adapter& operator=(buffers_adapter const&); + + /** Construct a buffers adapter. + + @param buffers The mutable buffer sequence to wrap. A copy of + the object will be made, but ownership of the memory is not + transferred. + */ + explicit + buffers_adapter(Buffers const& buffers); + + /// Returns the largest size output sequence possible. + std::size_t + max_size() const + { + return max_size_; + } + + /// Get the size of the input sequence. + std::size_t + size() const + { + return in_size_; + } + + /** Get a list of buffers that represents the output sequence, with the given size. + + @throws std::length_error if the size would exceed the limit + imposed by the underlying mutable buffer sequence. + */ + mutable_buffers_type + prepare(std::size_t n); + + /// Move bytes from the output sequence to the input sequence. + void + commit(std::size_t n); + + /// Get a list of buffers that represents the input sequence. + const_buffers_type + data() const; + + /// Remove bytes from the input sequence. + void + consume(std::size_t n); +}; + +} // beast + +#include + +#endif diff --git a/include/beast/buffers_debug.hpp b/include/beast/buffers_debug.hpp new file mode 100644 index 00000000..61566863 --- /dev/null +++ b/include/beast/buffers_debug.hpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BUFFERS_DEBUG_HPP +#define BEAST_BUFFERS_DEBUG_HPP + +#include +#include + +namespace beast { +namespace debug { + +/** Diagnostic utility to convert a `ConstBufferSequence` to a string. + + @note Carriage returns and linefeeds will have additional escape + representations printed for visibility. +*/ +template +std::string +buffers_to_string(Buffers const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + for(auto i = s.size(); i-- > 0;) + if(s[i] == '\r') + s.replace(i, 1, "\\r"); + else if(s[i] == '\n') + s.replace(i, 1, "\\n\n"); + return s; +} + +} // debug +} // beast + +#endif diff --git a/include/beast/consuming_buffers.hpp b/include/beast/consuming_buffers.hpp new file mode 100644 index 00000000..752c3dbe --- /dev/null +++ b/include/beast/consuming_buffers.hpp @@ -0,0 +1,122 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CONSUMING_BUFFERS_HPP +#define BEAST_CONSUMING_BUFFERS_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** Adapter to trim the front of a `BufferSequence`. + + This adapter wraps a buffer sequence to create a new sequence + which may be incrementally consumed. Bytes consumed are removed + from the front of the buffer. The underlying memory is not changed, + instead the adapter efficiently iterates through a subset of + the buffers wrapped. + + The wrapped buffer is not modified, a copy is made instead. + Ownership of the underlying memory is not transferred, the application + is still responsible for managing its lifetime. + + @tparam Buffers The buffer sequence to wrap. + + @ptaram ValueType The type of buffer of the final buffer sequence. This + can be different from the buffer type of the wrapped sequence. For + example, a `MutableBufferSequence` can be transformed into a + consumable `ConstBufferSequence`. Violations of buffer const safety + are not permitted, and will result in a compile error. +*/ +template +class consuming_buffers +{ + using iter_type = + typename Buffers::const_iterator; + + static_assert(std::is_constructible::value_type>::value, + "ValueType requirements not met"); + + Buffers bs_; + iter_type begin_; + std::size_t skip_ = 0; + + template + consuming_buffers(Deduced&& other, std::size_t nbegin) + : bs_(std::forward(other).bs_) + , begin_(std::next(bs_.begin(), nbegin)) + , skip_(other.skip_) + { + } + +public: + /// The type for each element in the list of buffers. + using value_type = ValueType; + +#if GENERATING_DOCS + /// A bidirectional iterator type that may be used to read elements. + using const_iterator = implementation_defined; + +#else + class const_iterator; + +#endif + + /// Move constructor. + consuming_buffers(consuming_buffers&&); + + /// Copy constructor. + consuming_buffers(consuming_buffers const&); + + /// Move assignment. + consuming_buffers& operator=(consuming_buffers&&); + + /// Copy assignment. + consuming_buffers& operator=(consuming_buffers const&); + + /** Construct to represent a buffer sequence. + + A copy of the buffer sequence is made. Ownership of the + underlying memory is not transferred or copied. + */ + explicit + consuming_buffers(Buffers const& buffers); + + /// Get a bidirectional iterator to the first element. + const_iterator + begin() const; + + /// Get a bidirectional iterator for one past the last element. + const_iterator + end() const; + + /** Remove bytes from the beginning of the sequence. + + @param n The number of bytes to remove. If this is + larger than the number of bytes remaining, all the + bytes remaining are removed. + */ + void + consume(std::size_t n); +}; + +/// Returns a consumed buffer +template +consuming_buffers +consumed_buffers(Buffers const& bs, std::size_t n); + +} // beast + +#include + +#endif diff --git a/include/beast/detail/base64.hpp b/include/beast/detail/base64.hpp new file mode 100644 index 00000000..0256aa7d --- /dev/null +++ b/include/beast/detail/base64.hpp @@ -0,0 +1,178 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_BASE64_HPP +#define BEAST_DETAIL_BASE64_HPP + +#include +#include + +namespace beast { +namespace detail { + +/* + Portions from http://www.adp-gmbh.ch/cpp/common/base64.html + Copyright notice: + + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +template +std::string const& +base64_alphabet() +{ + static std::string const alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return alphabet; +} + +inline +bool +is_base64(unsigned char c) +{ + return (std::isalnum(c) || (c == '+') || (c == '/')); +} + +template +std::string +base64_encode (std::uint8_t const* data, + std::size_t in_len) +{ + unsigned char c3[3], c4[4]; + int i = 0; + int j = 0; + + std::string ret; + ret.reserve (3 + in_len * 8 / 6); + + char const* alphabet (base64_alphabet().data()); + + while(in_len--) + { + c3[i++] = *(data++); + if(i == 3) + { + c4[0] = (c3[0] & 0xfc) >> 2; + c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); + c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); + c4[3] = c3[2] & 0x3f; + for(i = 0; (i < 4); i++) + ret += alphabet[c4[i]]; + i = 0; + } + } + + if(i) + { + for(j = i; j < 3; j++) + c3[j] = '\0'; + + c4[0] = (c3[0] & 0xfc) >> 2; + c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); + c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); + c4[3] = c3[2] & 0x3f; + + for(j = 0; (j < i + 1); j++) + ret += alphabet[c4[j]]; + + while((i++ < 3)) + ret += '='; + } + + return ret; + +} + +template +std::string +base64_encode (std::string const& s) +{ + return base64_encode (reinterpret_cast < + std::uint8_t const*> (s.data()), s.size()); +} + +template +std::string +base64_decode(std::string const& data) +{ + int in_len = data.size(); + unsigned char c3[3], c4[4]; + int i = 0; + int j = 0; + int in_ = 0; + + std::string ret; + ret.reserve (in_len * 6 / 8); // ??? + + while(in_len-- && (data[in_] != '=') && + is_base64(data[in_])) + { + c4[i++] = data[in_]; in_++; + if(i == 4) { + for(i = 0; i < 4; i++) + c4[i] = static_cast( + base64_alphabet().find(c4[i])); + + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(i = 0; (i < 3); i++) + ret += c3[i]; + i = 0; + } + } + + if(i) + { + for(j = i; j < 4; j++) + c4[j] = 0; + + for(j = 0; j < 4; j++) + c4[j] = static_cast( + base64_alphabet().find(c4[j])); + + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(j = 0; (j < i - 1); j++) + ret += c3[j]; + } + + return ret; +} + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/ci_char_traits.hpp b/include/beast/detail/ci_char_traits.hpp new file mode 100644 index 00000000..8484f273 --- /dev/null +++ b/include/beast/detail/ci_char_traits.hpp @@ -0,0 +1,86 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_CI_CHAR_TRAITS_HPP +#define BEAST_DETAIL_CI_CHAR_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +/** Case-insensitive function object for performing less than comparisons. */ +struct ci_less +{ + static bool const is_transparent = true; + + bool + operator()(boost::string_ref const& lhs, + boost::string_ref const& rhs) const noexcept + { + using std::begin; + using std::end; + return std::lexicographical_compare( + begin(lhs), end(lhs), begin(rhs), end(rhs), + [](char lhs, char rhs) + { + return std::tolower(lhs) < std::tolower(rhs); + } + ); + } +}; + +inline +bool +ci_equal(std::pair lhs, + std::pair rhs) +{ + if(lhs.second != rhs.second) + return false; + return std::equal (lhs.first, lhs.first + lhs.second, + rhs.first, + [] (char lhs, char rhs) + { + return std::tolower(lhs) == std::tolower(rhs); + } + ); +} + +template +inline +std::pair +view(const char (&s)[N]) +{ + return {s, N-1}; +} + +inline +std::pair +view(std::string const& s) +{ + return {s.data(), s.size()}; +} + +/** Returns `true` if strings are case-insensitive equal. */ +template +inline +bool +ci_equal(String1 const& lhs, String2 const& rhs) +{ + return ci_equal(view(lhs), view(rhs)); +} + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/const_container.hpp b/include/beast/detail/const_container.hpp new file mode 100644 index 00000000..72f2c32b --- /dev/null +++ b/include/beast/detail/const_container.hpp @@ -0,0 +1,89 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_CONST_CONTAINER_HPP +#define BEAST_DETAIL_CONST_CONTAINER_HPP + +namespace beast { +namespace detail { + +/** Adapter to constrain a container interface. + The interface allows for limited read only operations. Derived classes + provide additional behavior. +*/ +template +class const_container +{ +private: + using cont_type = Container; + + cont_type m_cont; + +protected: + cont_type& cont() + { + return m_cont; + } + + cont_type const& cont() const + { + return m_cont; + } + +public: + using value_type = typename cont_type::value_type; + using size_type = typename cont_type::size_type; + using difference_type = typename cont_type::difference_type; + using iterator = typename cont_type::const_iterator; + using const_iterator = typename cont_type::const_iterator; + + /** Returns `true` if the container is empty. */ + bool + empty() const + { + return m_cont.empty(); + } + + /** Returns the number of items in the container. */ + size_type + size() const + { + return m_cont.size(); + } + + /** Returns forward iterators for traversal. */ + /** @{ */ + const_iterator + begin() const + { + return m_cont.cbegin(); + } + + const_iterator + cbegin() const + { + return m_cont.cbegin(); + } + + const_iterator + end() const + { + return m_cont.cend(); + } + + const_iterator + cend() const + { + return m_cont.cend(); + } + /** @} */ +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/empty_base_optimization.hpp b/include/beast/detail/empty_base_optimization.hpp new file mode 100644 index 00000000..1be8ccde --- /dev/null +++ b/include/beast/detail/empty_base_optimization.hpp @@ -0,0 +1,94 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_EMPTY_BASE_OPTIMIZATION_HPP +#define BEAST_DETAIL_EMPTY_BASE_OPTIMIZATION_HPP + +#include +#include + +namespace beast { +namespace detail { + +template +struct empty_base_optimization_decide + : std::integral_constant ::value +#ifdef __clang__ + && !__is_final(T) +#endif + > +{ +}; + +template < + class T, + int UniqueID = 0, + bool ShouldDeriveFrom = + empty_base_optimization_decide::value +> +class empty_base_optimization : private T +{ +public: + empty_base_optimization() = default; + + empty_base_optimization(T const& t) + : T (t) + {} + + empty_base_optimization(T&& t) + : T (std::move (t)) + {} + + T& member() noexcept + { + return *this; + } + + T const& member() const noexcept + { + return *this; + } +}; + +//------------------------------------------------------------------------------ + +template < + class T, + int UniqueID +> +class empty_base_optimization +{ +public: + empty_base_optimization() = default; + + empty_base_optimization(T const& t) + : m_t (t) + {} + + empty_base_optimization(T&& t) + : m_t (std::move (t)) + {} + + T& member() noexcept + { + return m_t; + } + + T const& member() const noexcept + { + return m_t; + } + +private: + T m_t; +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/integer_sequence.hpp b/include/beast/detail/integer_sequence.hpp new file mode 100644 index 00000000..73389c53 --- /dev/null +++ b/include/beast/detail/integer_sequence.hpp @@ -0,0 +1,145 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED +#define BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED + +#include +#include +#include + +namespace beast { +namespace detail { + +template +struct integer_sequence +{ + using value_type = T; + static_assert (std::is_integral::value, + "std::integer_sequence can only be instantiated with an integral type" ); + + static std::size_t constexpr static_size = sizeof...(Ints); + + static std::size_t constexpr size() + { + return sizeof...(Ints); + } +}; + +template +using index_sequence = integer_sequence; + +// This workaround is needed for broken sizeof... +template +struct sizeof_workaround +{ + static std::size_t constexpr size = sizeof... (Args); +}; + +#ifdef _MSC_VER + +// This implementation compiles on MSVC and clang but not gcc + +template +struct make_integer_sequence_unchecked; + +template +struct make_integer_sequence_unchecked< + T, N, integer_sequence> +{ + using type = typename make_integer_sequence_unchecked< + T, N-1, integer_sequence>::type; +}; + +template +struct make_integer_sequence_unchecked< + T, 0, integer_sequence> +{ + using type = integer_sequence; +}; + +template +struct make_integer_sequence_checked +{ + static_assert (std::is_integral::value, + "T must be an integral type"); + + static_assert (N >= 0, + "N must be non-negative"); + + using type = typename make_integer_sequence_unchecked< + T, N, integer_sequence>::type; +}; + +template +using make_integer_sequence = + typename make_integer_sequence_checked::type; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = + make_index_sequence::size>; + +#else + +// This implementation compiles on gcc but not MSVC + +template +struct index_tuple +{ + using next = index_tuple; + +}; + +template +struct build_index_tuple +{ + using type = typename build_index_tuple::type::next; +}; + +template<> +struct build_index_tuple<0> +{ + using type = index_tuple<>; +}; + +template::type +> +struct integer_sequence_helper; + +template +struct integer_sequence_helper> +{ + static_assert (std::is_integral::value, + "T must be an integral type"); + + static_assert (N >= 0, + "N must be non-negative"); + + using type = integer_sequence (Ints)...>; +}; + +template +using make_integer_sequence = + typename integer_sequence_helper::type; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = + make_index_sequence::size>; + +#endif + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/is_call_possible.hpp b/include/beast/detail/is_call_possible.hpp new file mode 100644 index 00000000..c14dbd34 --- /dev/null +++ b/include/beast/detail/is_call_possible.hpp @@ -0,0 +1,90 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_IS_CALL_POSSIBLE_HPP +#define BEAST_DETAIL_IS_CALL_POSSIBLE_HPP + +#include + +namespace beast { +namespace detail { + +template +auto +is_call_possible_test(C&& c, int, A&& ...a) + -> decltype(std::is_convertible< + decltype(c(a...)), R>::value || + std::is_same::value, + std::true_type()); + +template +std::false_type +is_call_possible_test(C&& c, long, A&& ...a); + +/** Metafunction returns `true` if F callable as R(A...) + Example: + is_call_possible +*/ +/** @{ */ +template +struct is_call_possible + : std::false_type +{ +}; + +template +struct is_call_possible + : decltype(is_call_possible_test( + std::declval(), 1, std::declval()...)) +{ +}; +/** @} */ + +namespace test { + +struct is_call_possible_udt1 +{ + void operator()(int) const; +}; + +struct is_call_possible_udt2 +{ + int operator()(int) const; +}; + +struct is_call_possible_udt3 +{ + int operator()(int); +}; + +static_assert(is_call_possible< + is_call_possible_udt1, void(int)>::value, ""); + +static_assert(! is_call_possible< + is_call_possible_udt1, void(void)>::value, ""); + +static_assert(is_call_possible< + is_call_possible_udt2, int(int)>::value, ""); + +static_assert(! is_call_possible< + is_call_possible_udt2, int(void)>::value, ""); + +static_assert(! is_call_possible< + is_call_possible_udt2, void(void)>::value, ""); + +static_assert(is_call_possible< + is_call_possible_udt3, int(int)>::value, ""); + +static_assert(! is_call_possible< + is_call_possible_udt3 const, int(int)>::value, ""); + +} // test + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/sha1.hpp b/include/beast/detail/sha1.hpp new file mode 100644 index 00000000..c34b1bb8 --- /dev/null +++ b/include/beast/detail/sha1.hpp @@ -0,0 +1,310 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_SHA1_HPP +#define BEAST_DETAIL_SHA1_HPP + +#include +#include +#include + +// Based on https://github.com/vog/sha1 +/* + Original authors: + Steve Reid (Original C Code) + Bruce Guenter (Small changes to fit into bglibs) + Volker Grabsch (Translation to simpler C++ Code) + Eugene Hopkinson (Safety improvements) + Vincent Falco (beast adaptation) +*/ + +namespace beast { +namespace detail { + +namespace sha1 { + +static std::size_t constexpr BLOCK_INTS = 16; +static std::size_t constexpr BLOCK_BYTES = 64; +static std::size_t constexpr DIGEST_BYTES = 20; + +inline +std::uint32_t +rol(std::uint32_t value, std::size_t bits) +{ + return (value << bits) | (value >> (32 - bits)); +} + +inline +std::uint32_t +blk(std::uint32_t block[BLOCK_INTS], std::size_t i) +{ + return rol( + block[(i+13)&15] ^ block[(i+8)&15] ^ + block[(i+2)&15] ^ block[i], 1); +} + +inline +void +R0(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +inline +void +R1(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R2(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R3(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R4(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +make_block(std::uint8_t const* p, + std::uint32_t block[BLOCK_INTS]) +{ + for(std::size_t i = 0; i < BLOCK_INTS; i++) + block[i] = + (static_cast(p[4*i+3])) | + (static_cast(p[4*i+2]))<< 8 | + (static_cast(p[4*i+1]))<<16 | + (static_cast(p[4*i+0]))<<24; +} + +template +void +transform( + std::uint32_t digest[], std::uint32_t block[BLOCK_INTS]) +{ + std::uint32_t a = digest[0]; + std::uint32_t b = digest[1]; + std::uint32_t c = digest[2]; + std::uint32_t d = digest[3]; + std::uint32_t e = digest[4]; + + R0(block, a, b, c, d, e, 0); + R0(block, e, a, b, c, d, 1); + R0(block, d, e, a, b, c, 2); + R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); + R0(block, a, b, c, d, e, 5); + R0(block, e, a, b, c, d, 6); + R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); + R0(block, b, c, d, e, a, 9); + R0(block, a, b, c, d, e, 10); + R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); + R0(block, c, d, e, a, b, 13); + R0(block, b, c, d, e, a, 14); + R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 0); + R1(block, d, e, a, b, c, 1); + R1(block, c, d, e, a, b, 2); + R1(block, b, c, d, e, a, 3); + R2(block, a, b, c, d, e, 4); + R2(block, e, a, b, c, d, 5); + R2(block, d, e, a, b, c, 6); + R2(block, c, d, e, a, b, 7); + R2(block, b, c, d, e, a, 8); + R2(block, a, b, c, d, e, 9); + R2(block, e, a, b, c, d, 10); + R2(block, d, e, a, b, c, 11); + R2(block, c, d, e, a, b, 12); + R2(block, b, c, d, e, a, 13); + R2(block, a, b, c, d, e, 14); + R2(block, e, a, b, c, d, 15); + R2(block, d, e, a, b, c, 0); + R2(block, c, d, e, a, b, 1); + R2(block, b, c, d, e, a, 2); + R2(block, a, b, c, d, e, 3); + R2(block, e, a, b, c, d, 4); + R2(block, d, e, a, b, c, 5); + R2(block, c, d, e, a, b, 6); + R2(block, b, c, d, e, a, 7); + R3(block, a, b, c, d, e, 8); + R3(block, e, a, b, c, d, 9); + R3(block, d, e, a, b, c, 10); + R3(block, c, d, e, a, b, 11); + R3(block, b, c, d, e, a, 12); + R3(block, a, b, c, d, e, 13); + R3(block, e, a, b, c, d, 14); + R3(block, d, e, a, b, c, 15); + R3(block, c, d, e, a, b, 0); + R3(block, b, c, d, e, a, 1); + R3(block, a, b, c, d, e, 2); + R3(block, e, a, b, c, d, 3); + R3(block, d, e, a, b, c, 4); + R3(block, c, d, e, a, b, 5); + R3(block, b, c, d, e, a, 6); + R3(block, a, b, c, d, e, 7); + R3(block, e, a, b, c, d, 8); + R3(block, d, e, a, b, c, 9); + R3(block, c, d, e, a, b, 10); + R3(block, b, c, d, e, a, 11); + R4(block, a, b, c, d, e, 12); + R4(block, e, a, b, c, d, 13); + R4(block, d, e, a, b, c, 14); + R4(block, c, d, e, a, b, 15); + R4(block, b, c, d, e, a, 0); + R4(block, a, b, c, d, e, 1); + R4(block, e, a, b, c, d, 2); + R4(block, d, e, a, b, c, 3); + R4(block, c, d, e, a, b, 4); + R4(block, b, c, d, e, a, 5); + R4(block, a, b, c, d, e, 6); + R4(block, e, a, b, c, d, 7); + R4(block, d, e, a, b, c, 8); + R4(block, c, d, e, a, b, 9); + R4(block, b, c, d, e, a, 10); + R4(block, a, b, c, d, e, 11); + R4(block, e, a, b, c, d, 12); + R4(block, d, e, a, b, c, 13); + R4(block, c, d, e, a, b, 14); + R4(block, b, c, d, e, a, 15); + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; +} + +} // sha1 + +struct sha1_context +{ + static unsigned int const block_size = sha1::BLOCK_BYTES; + static unsigned int const digest_size = 20; + + std::uint32_t digest[5]; + std::uint8_t buf[block_size]; + std::size_t buflen; + std::size_t blocks; +}; + +template +void +init(sha1_context& ctx) noexcept +{ + ctx.buflen = 0; + ctx.digest[0] = 0x67452301; + ctx.digest[1] = 0xefcdab89; + ctx.digest[2] = 0x98badcfe; + ctx.digest[3] = 0x10325476; + ctx.digest[4] = 0xc3d2e1f0; + ctx.blocks = 0; +} + +template +void +update(sha1_context& ctx, + void const* message, std::size_t size) noexcept +{ + auto p = reinterpret_cast< + std::uint8_t const*>(message); + for(;;) + { + auto const n = std::min( + size, sizeof(ctx.buf) - ctx.buflen); + std::memcpy(ctx.buf + ctx.buflen, p, n); + ctx.buflen += n; + if(ctx.buflen != 64) + return; + p += n; + size -= n; + ctx.buflen = 0; + std::uint32_t block[sha1::BLOCK_INTS]; + sha1::make_block(ctx.buf, block); + sha1::transform(ctx.digest, block); + ++ctx.blocks; + } +} + +template +void +finish(sha1_context& ctx, void* digest) noexcept +{ + using sha1::BLOCK_INTS; + using sha1::BLOCK_BYTES; + + std::uint64_t total_bits = + (ctx.blocks*64 + ctx.buflen) * 8; + // pad + auto const buflen = ctx.buflen; + ctx.buf[ctx.buflen++] = 0x80; + while(ctx.buflen < 64) + ctx.buf[ctx.buflen++] = 0x00; + std::uint32_t block[BLOCK_INTS]; + sha1::make_block(ctx.buf, block); + if (buflen > BLOCK_BYTES - 8) + { + ++ctx.blocks; + sha1::transform(ctx.digest, block); + for (size_t i = 0; i < BLOCK_INTS - 2; i++) + block[i] = 0; + } + + /* Append total_bits, split this uint64_t into two uint32_t */ + block[BLOCK_INTS - 1] = total_bits & 0xffffffff; + block[BLOCK_INTS - 2] = (total_bits >> 32); + sha1::transform(ctx.digest, block); + for(std::size_t i = 0; i < sha1::DIGEST_BYTES/4; i++) + { + std::uint8_t* d = + reinterpret_cast(digest) + 4 * i; + d[3] = ctx.digest[i] & 0xff; + d[2] = (ctx.digest[i] >> 8) & 0xff; + d[1] = (ctx.digest[i] >> 16) & 0xff; + d[0] = (ctx.digest[i] >> 24) & 0xff; + } +} + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/stream/abstract_ostream.hpp b/include/beast/detail/stream/abstract_ostream.hpp new file mode 100644 index 00000000..43a3c223 --- /dev/null +++ b/include/beast/detail/stream/abstract_ostream.hpp @@ -0,0 +1,22 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STREAM_ABSTRACT_OSTREAM_HPP +#define BEAST_DETAIL_STREAM_ABSTRACT_OSTREAM_HPP + +#include + +namespace beast { +namespace detail { + +/** An abstract ostream for `char`. */ +using abstract_ostream = basic_abstract_ostream ; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/stream/basic_abstract_ostream.hpp b/include/beast/detail/stream/basic_abstract_ostream.hpp new file mode 100644 index 00000000..098886a2 --- /dev/null +++ b/include/beast/detail/stream/basic_abstract_ostream.hpp @@ -0,0 +1,87 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STREAM_BASIC_ABSTRACT_OSTREAM_HPP +#define BEAST_DETAIL_STREAM_BASIC_ABSTRACT_OSTREAM_HPP + +#include +#include +#include +#include + +namespace beast { +namespace detail { + +/** Abstraction for an output stream similar to std::basic_ostream. */ +template < + class CharT, + class Traits = std::char_traits +> +class basic_abstract_ostream +{ +public: + using string_type = std::basic_string ; + using scoped_stream_type = basic_scoped_ostream ; + + basic_abstract_ostream() = default; + + virtual + ~basic_abstract_ostream() = default; + + basic_abstract_ostream (basic_abstract_ostream const&) = default; + basic_abstract_ostream& operator= ( + basic_abstract_ostream const&) = default; + + /** Returns `true` if the stream is active. + Inactive streams do not produce output. + */ + /** @{ */ + virtual + bool + active() const + { + return true; + } + + explicit + operator bool() const + { + return active(); + } + /** @} */ + + /** Called to output each string. */ + virtual + void + write (string_type const& s) = 0; + + scoped_stream_type + operator<< (std::ostream& manip (std::ostream&)) + { + return scoped_stream_type (manip, + [this](string_type const& s) + { + this->write (s); + }); + } + + template + scoped_stream_type + operator<< (T const& t) + { + return scoped_stream_type (t, + [this](string_type const& s) + { + this->write (s); + }); + } +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/stream/basic_scoped_ostream.hpp b/include/beast/detail/stream/basic_scoped_ostream.hpp new file mode 100644 index 00000000..262f3cd4 --- /dev/null +++ b/include/beast/detail/stream/basic_scoped_ostream.hpp @@ -0,0 +1,138 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STREAM_BASIC_SCOPED_OSTREAM_HPP +#define BEAST_DETAIL_STREAM_BASIC_SCOPED_OSTREAM_HPP + +#include +#include +#include + +// gcc libstd++ doesn't have move constructors for basic_ostringstream +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 +// +#ifndef BEAST_NO_STDLIB_STREAM_MOVE +# ifdef __GLIBCXX__ +# define BEAST_NO_STDLIB_STREAM_MOVE 1 +# else +# define BEAST_NO_STDLIB_STREAM_MOVE 0 +# endif +#endif + +namespace beast { +namespace detail { + +template < + class CharT, + class Traits +> +class basic_abstract_ostream; + +/** Scoped output stream that forwards to a functor upon destruction. */ +template < + class CharT, + class Traits = std::char_traits , + class Allocator = std::allocator +> +class basic_scoped_ostream +{ +private: + using handler_t = std::function const&)>; + + using stream_type = std::basic_ostringstream < + CharT, Traits, Allocator>; + + handler_t m_handler; + +#if BEAST_NO_STDLIB_STREAM_MOVE + std::unique_ptr m_ss; + + stream_type& stream() + { + return *m_ss; + } + +#else + stream_type m_ss; + + stream_type& stream() + { + return m_ss; + } + +#endif + +public: + using string_type = std::basic_string ; + + // Disallow copy since that would duplicate the output + basic_scoped_ostream (basic_scoped_ostream const&) = delete; + basic_scoped_ostream& operator= (basic_scoped_ostream const) = delete; + + template + explicit basic_scoped_ostream (Handler&& handler) + : m_handler (std::forward (handler)) + #if BEAST_NO_STDLIB_STREAM_MOVE + , m_ss (new stream_type()) + #endif + { + } + + template + basic_scoped_ostream (T const& t, Handler&& handler) + : m_handler (std::forward (handler)) + #if BEAST_NO_STDLIB_STREAM_MOVE + , m_ss (new stream_type()) + #endif + { + stream() << t; + } + + basic_scoped_ostream (basic_abstract_ostream < + CharT, Traits>& ostream) + : m_handler ( + [&](string_type const& s) + { + ostream.write (s); + }) + { + } + + basic_scoped_ostream (basic_scoped_ostream&& other) + : m_handler (std::move (other.m_handler)) + , m_ss (std::move (other.m_ss)) + { + } + + ~basic_scoped_ostream() + { + auto const& s (stream().str()); + if (! s.empty()) + m_handler (s); + } + + basic_scoped_ostream& + operator<< (std::ostream& manip (std::ostream&)) + { + stream() << manip; + return *this; + } + + template + basic_scoped_ostream& + operator<< (T const& t) + { + stream() << t; + return *this; + } +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/stream/basic_std_ostream.hpp b/include/beast/detail/stream/basic_std_ostream.hpp new file mode 100644 index 00000000..91734212 --- /dev/null +++ b/include/beast/detail/stream/basic_std_ostream.hpp @@ -0,0 +1,62 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STREAM_BASIC_STD_OSTREAM_H_INCLUDED +#define BEAST_DETAIL_STREAM_BASIC_STD_OSTREAM_H_INCLUDED + +#include +#include + +namespace beast { +namespace detail { + +/** Wraps an existing std::basic_ostream as an abstract_ostream. */ +template < + class CharT, + class Traits = std::char_traits +> +class basic_std_ostream + : public basic_abstract_ostream +{ +private: + using typename basic_abstract_ostream ::string_type; + + std::reference_wrapper m_stream; + +public: + explicit basic_std_ostream ( + std::basic_ostream & stream) + : m_stream (stream) + { + } + + void + write (string_type const& s) override + { + m_stream.get() << s << std::endl; + } +}; + +using std_ostream = basic_std_ostream ; + +//------------------------------------------------------------------------------ + +/** Returns a basic_std_ostream using template argument deduction. */ +template < + class CharT, + class Traits = std::char_traits +> +basic_std_ostream +make_std_ostream (std::basic_ostream & stream) +{ + return basic_std_ostream (stream); +} + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/stream/debug_ostream.hpp b/include/beast/detail/stream/debug_ostream.hpp new file mode 100644 index 00000000..1f3bc465 --- /dev/null +++ b/include/beast/detail/stream/debug_ostream.hpp @@ -0,0 +1,80 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STREAM_DEBUG_OSTREAM_HPP +#define BEAST_DETAIL_STREAM_DEBUG_OSTREAM_HPP + +#include +#include + +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +#include +# undef WIN32_LEAN_AND_MEAN +# else +#include +# endif +# ifdef min +# undef min +# endif +# ifdef max +# undef max +# endif +#endif + +namespace beast { +namespace detail { + +#ifdef _MSC_VER +/** A basic_abstract_ostream that redirects output to an attached debugger. */ +class debug_ostream + : public abstract_ostream +{ +private: + bool m_debugger; + +public: + debug_ostream() + : m_debugger (IsDebuggerPresent() != FALSE) + { + // Note that the check for an attached debugger is made only + // during construction time, for efficiency. A stream created before + // the debugger is attached will not have output redirected. + } + + void + write (string_type const& s) override + { + if (m_debugger) + { + OutputDebugStringA ((s + "\n").c_str()); + return; + } + + std::cout << s << std::endl; + } +}; + +#else +class debug_ostream + : public abstract_ostream +{ +public: + void + write (string_type const& s) override + { + std::cout << s << std::endl; + } +}; + +#endif + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/temp_dir.hpp b/include/beast/detail/temp_dir.hpp new file mode 100644 index 00000000..54a0d905 --- /dev/null +++ b/include/beast/detail/temp_dir.hpp @@ -0,0 +1,73 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_TEMP_DIR_H_INCLUDED +#define BEAST_DETAIL_TEMP_DIR_H_INCLUDED + +#include +#include + +namespace beast { +namespace detail { + +/** RAII temporary directory. + + The directory and all its contents are deleted when + the instance of `temp_dir` is destroyed. +*/ +class temp_dir +{ + boost::filesystem::path path_; + +public: +#if ! GENERATING_DOCS + temp_dir(const temp_dir&) = delete; + temp_dir& operator=(const temp_dir&) = delete; +#endif + + /// Construct a temporary directory. + temp_dir() + { + auto const dir = + boost::filesystem::temp_directory_path(); + do + { + path_ = + dir / boost::filesystem::unique_path(); + } + while(boost::filesystem::exists(path_)); + boost::filesystem::create_directory (path_); + } + + /// Destroy a temporary directory. + ~temp_dir() + { + boost::filesystem::remove_all (path_); + } + + /// Get the native path for the temporary directory + std::string + path() const + { + return path_.string(); + } + + /** Get the native path for the a file. + + The file does not need to exist. + */ + std::string + file(std::string const& name) const + { + return (path_ / name).string(); + } +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test.h b/include/beast/detail/unit_test.h new file mode 100644 index 00000000..a53379e0 --- /dev/null +++ b/include/beast/detail/unit_test.h @@ -0,0 +1,23 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_H_INCLUDED +#define BEAST_DETAIL_UNIT_TEST_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/beast/detail/unit_test/amount.hpp b/include/beast/detail/unit_test/amount.hpp new file mode 100644 index 00000000..c07b4cad --- /dev/null +++ b/include/beast/detail/unit_test/amount.hpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_AMOUNT_HPP +#define BEAST_DETAIL_UNIT_TEST_AMOUNT_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** Utility for producing nicely composed output of amounts with units. */ +class amount +{ +private: + std::size_t n_; + std::string const& what_; + +public: + amount (amount const&) = default; + amount& operator= (amount const&) = delete; + + template + amount (std::size_t n, std::string const& what); + + friend + std::ostream& + operator<< (std::ostream& s, amount const& t); +}; + +template +amount::amount (std::size_t n, std::string const& what) + : n_ (n) + , what_ (what) +{ +} + +inline +std::ostream& +operator<< (std::ostream& s, amount const& t) +{ + s << t.n_ << " " << t.what_ << ((t.n_ != 1) ? "s" : ""); + return s; +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/define_print.cpp b/include/beast/detail/unit_test/define_print.cpp new file mode 100644 index 00000000..6540d4dc --- /dev/null +++ b/include/beast/detail/unit_test/define_print.cpp @@ -0,0 +1,90 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include + +// Include this .cpp in your project to gain access to the printing suite + +namespace beast { +namespace detail { + +inline +namespace unit_test { +namespace detail { + +/** A suite that prints the list of globally defined suites. */ +class print_test : public suite +{ +private: + template + void + do_run(); + +public: + template + static + std::string + prefix (suite_info const& s); + + template + void + print (suite_list &c); + + void + run() + { + do_run(); + } +}; + +template +void +print_test::do_run() +{ + log << "------------------------------------------"; + print (global_suites()); + log << "------------------------------------------"; + pass(); +} + +template +std::string +print_test::prefix (suite_info const& s) +{ + if (s.manual()) + return "|M| "; + return " "; +} + +template +void +print_test::print (suite_list &c) +{ + std::size_t manual (0); + for (auto const& s : c) + { + log << + prefix (s) << + s.full_name(); + if (s.manual()) + ++manual; + } + log << + amount (c.size(), "suite") << " total, " << + amount (manual, "manual suite") + ; +} + +BEAST_DEFINE_TESTSUITE_MANUAL(print,unit_test,beast); + +} // detail +} // unit_test +} // detail +} // beast diff --git a/include/beast/detail/unit_test/global_suites.hpp b/include/beast/detail/unit_test/global_suites.hpp new file mode 100644 index 00000000..c7a7d8a3 --- /dev/null +++ b/include/beast/detail/unit_test/global_suites.hpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_GLOBAL_SUITES_HPP +#define BEAST_DETAIL_UNIT_TEST_GLOBAL_SUITES_HPP + +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +namespace detail { + +template +suite_list& +global_suites() +{ + static suite_list s; + return s; +} + +template +struct insert_suite +{ + template + insert_suite (char const* name, char const* module, + char const* library, bool manual); +}; + +template +template +insert_suite::insert_suite (char const* name, + char const* module, char const* library, bool manual) +{ + global_suites().insert ( + name, module, library, manual); +} + +} // detail + +/** Holds suites registered during static initialization. */ +inline +suite_list const& +global_suites() +{ + return detail::global_suites(); +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/match.hpp b/include/beast/detail/unit_test/match.hpp new file mode 100644 index 00000000..664bae8a --- /dev/null +++ b/include/beast/detail/unit_test/match.hpp @@ -0,0 +1,177 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_MATCH_HPP +#define BEAST_DETAIL_UNIT_TEST_MATCH_HPP + +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +// Predicate for implementing matches +class selector +{ +public: + enum mode_t + { + // Run all tests except manual ones + all, + + // Run tests that match in any field + automatch, + + // Match on suite + suite, + + // Match on library + library, + + // Match on module (used internally) + module, + + // Match nothing (used internally) + none + }; + +private: + mode_t mode_; + std::string pat_; + std::string library_; + +public: + template + explicit + selector (mode_t mode, std::string const& pattern = ""); + + template + bool + operator() (suite_info const& s); +}; + +//------------------------------------------------------------------------------ + +template +selector::selector (mode_t mode, std::string const& pattern) + : mode_ (mode) + , pat_ (pattern) +{ + if (mode_ == automatch && pattern.empty()) + mode_ = all; +} + +template +bool +selector::operator() (suite_info const& s) +{ + switch (mode_) + { + case automatch: + // suite or full name + if (s.name() == pat_ || s.full_name() == pat_) + { + mode_ = none; + return true; + } + + // check module + if (pat_ == s.module()) + { + mode_ = module; + library_ = s.library(); + return ! s.manual(); + } + + // check library + if (pat_ == s.library()) + { + mode_ = library; + return ! s.manual(); + } + + return false; + + case suite: + return pat_ == s.name(); + + case module: + return pat_ == s.module() && ! s.manual(); + + case library: + return pat_ == s.library() && ! s.manual(); + + case none: + return false; + + case all: + default: + // fall through + break; + }; + + return ! s.manual(); +} + +//------------------------------------------------------------------------------ + +// Utility functions for producing predicates to select suites. + +/** Returns a predicate that implements a smart matching rule. + The predicate checks the suite, module, and library fields of the + suite_info in that order. When it finds a match, it changes modes + depending on what was found: + + If a suite is matched first, then only the suite is selected. The + suite may be marked manual. + + If a module is matched first, then only suites from that module + and library not marked manual are selected from then on. + + If a library is matched first, then only suites from that library + not marked manual are selected from then on. + +*/ +inline +selector +match_auto (std::string const& name) +{ + return selector (selector::automatch, name); +} + +/** Return a predicate that matches all suites not marked manual. */ +inline +selector +match_all() +{ + return selector (selector::all); +} + +/** Returns a predicate that matches a specific suite. */ +inline +selector +match_suite (std::string const& name) +{ + return selector (selector::suite, name); +} + +/** Returns a predicate that matches all suites in a library. */ +inline +selector +match_library (std::string const& name) +{ + return selector (selector::library, name); +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/print.hpp b/include/beast/detail/unit_test/print.hpp new file mode 100644 index 00000000..04957db5 --- /dev/null +++ b/include/beast/detail/unit_test/print.hpp @@ -0,0 +1,71 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_PRINT_H_INCLUDED +#define BEAST_DETAIL_UNIT_TEST_PRINT_H_INCLUDED + +#include +#include +#include +#include + +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** Write test results to the specified output stream. */ +/** @{ */ +template +void +print (results const& r, beast::detail::abstract_ostream& stream) +{ + for (auto const& s : r) + { + for (auto const& c : s) + { + stream << + s.name() << + (c.name().empty() ? "" : ("." + c.name())); + + std::size_t i (1); + for (auto const& t : c.tests) + { + if (! t.pass) + stream << + "#" << i << + " failed: " << t.reason; + ++i; + } + } + } + + stream << + amount (r.size(), "suite") << ", " << + amount (r.cases(), "case") << ", " << + amount (r.total(), "test") << " total, " << + amount (r.failed(), "failure") + ; +} + +template +void +print (results const& r, std::ostream& stream = std::cout) +{ + auto s (make_std_ostream (stream)); + print (r, s); +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/recorder.hpp b/include/beast/detail/unit_test/recorder.hpp new file mode 100644 index 00000000..8c711cf8 --- /dev/null +++ b/include/beast/detail/unit_test/recorder.hpp @@ -0,0 +1,96 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_RECORDER_HPP +#define BEAST_DETAIL_UNIT_TEST_RECORDER_HPP + +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** A test runner that stores the results. */ +class recorder : public runner +{ +private: + results m_results; + suite_results m_suite; + case_results m_case; + +public: + recorder() = default; + recorder (recorder const&) = default; + recorder& operator= (recorder const&) = default; + + /** Returns a report with the results of all completed suites. */ + results const& + report() const + { + return m_results; + } + +private: + virtual + void + on_suite_begin (suite_info const& info) override + { + m_suite = suite_results (info.full_name()); + } + + virtual + void + on_suite_end() override + { + m_results.insert (std::move (m_suite)); + } + + virtual + void + on_case_begin (std::string const& name) override + { + m_case = case_results (name); + } + + virtual + void + on_case_end() override + { + if (m_case.tests.size() > 0) + m_suite.insert (std::move (m_case)); + } + + virtual + void + on_pass() override + { + m_case.tests.pass(); + } + + virtual + void + on_fail (std::string const& reason) override + { + m_case.tests.fail (reason); + } + + virtual + void + on_log (std::string const& s) override + { + m_case.log.insert (s); + } +}; + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/reporter.hpp b/include/beast/detail/unit_test/reporter.hpp new file mode 100644 index 00000000..93a0f960 --- /dev/null +++ b/include/beast/detail/unit_test/reporter.hpp @@ -0,0 +1,324 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_REPORTER_HPP +#define BEAST_DETAIL_UNIT_TEST_REPORTER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +namespace detail { + +/** A simple test runner that writes everything to a stream in real time. + The totals are output when the object is destroyed. +*/ +template +class reporter : public runner +{ +private: + using clock_type = std::chrono::steady_clock; + + struct case_results + { + std::string name; + std::size_t total = 0; + std::size_t failed = 0; + + case_results (std::string const& name_ = ""); + }; + + struct suite_results + { + std::string name; + std::size_t cases = 0; + std::size_t total = 0; + std::size_t failed = 0; + typename clock_type::time_point start = + clock_type::now(); + + explicit + suite_results (std::string const& name_ = ""); + + void + add (case_results const& r); + }; + + struct results + { + using run_time = std::pair; + + enum + { + max_top = 10 + }; + + std::size_t suites = 0; + std::size_t cases = 0; + std::size_t total = 0; + std::size_t failed = 0; + std::vector top; + typename clock_type::time_point start = + clock_type::now(); + + void + add (suite_results const& r); + }; + + boost::optional std_ostream_; + std::reference_wrapper stream_; + results results_; + suite_results suite_results_; + case_results case_results_; + +public: + reporter (reporter const&) = delete; + reporter& operator= (reporter const&) = delete; + + ~reporter(); + + explicit + reporter (std::ostream& stream = std::cout); + + explicit + reporter (beast::detail::abstract_ostream& stream); + +private: + static + std::string + fmtdur (typename clock_type::duration const& d); + + virtual + void + on_suite_begin (suite_info const& info) override; + + virtual + void + on_suite_end() override; + + virtual + void + on_case_begin (std::string const& name) override; + + virtual + void + on_case_end() override; + + virtual + void + on_pass() override; + + virtual + void + on_fail (std::string const& reason) override; + + virtual + void + on_log (std::string const& s) override; +}; + +//------------------------------------------------------------------------------ + +template +reporter<_>::case_results::case_results ( + std::string const& name_) + : name (name_) +{ +} + +template +reporter<_>::suite_results::suite_results ( + std::string const& name_) + : name (name_) +{ +} + +template +void +reporter<_>::suite_results::add (case_results const& r) +{ + ++cases; + total += r.total; + failed += r.failed; +} + +template +void +reporter<_>::results::add ( + suite_results const& r) +{ + ++suites; + total += r.total; + cases += r.cases; + failed += r.failed; + + auto const elapsed = + clock_type::now() - r.start; + if (elapsed >= std::chrono::seconds(1)) + { + auto const iter = std::lower_bound(top.begin(), + top.end(), elapsed, + [](run_time const& t1, + typename clock_type::duration const& t2) + { + return t1.second > t2; + }); + if (iter != top.end()) + { + if (top.size() == max_top) + top.resize(top.size() - 1); + top.emplace(iter, r.name, elapsed); + } + else if (top.size() < max_top) + { + top.emplace_back(r.name, elapsed); + } + } +} + +//------------------------------------------------------------------------------ + +template +reporter<_>::reporter ( + std::ostream& stream) + : std_ostream_ (std::ref (stream)) + , stream_ (*std_ostream_) +{ +} + +template +reporter<_>::~reporter() +{ + if (results_.top.size() > 0) + { + stream_.get() << "Longest suite times:"; + for (auto const& i : results_.top) + stream_.get() << std::setw(8) << + fmtdur(i.second) << " " << i.first; + } + auto const elapsed = + clock_type::now() - results_.start; + stream_.get() << + fmtdur(elapsed) << ", " << + amount (results_.suites, "suite") << ", " << + amount (results_.cases, "case") << ", " << + amount (results_.total, "test") << " total, " << + amount (results_.failed, "failure"); +} + +template +reporter<_>::reporter ( + beast::detail::abstract_ostream& stream) + : stream_ (stream) +{ +} + +template +std::string +reporter<_>::fmtdur ( + typename clock_type::duration const& d) +{ + using namespace std::chrono; + auto const ms = + duration_cast(d); + if (ms < seconds(1)) + return std::to_string(ms.count()) + "ms"; + std::stringstream ss; + ss << std::fixed << std::setprecision(1) << + (ms.count()/1000.) << "s"; + return ss.str(); +} + +template +void +reporter<_>::on_suite_begin ( + suite_info const& info) +{ + suite_results_ = suite_results (info.full_name()); +} + +template +void +reporter<_>::on_suite_end() +{ + results_.add (suite_results_); +} + +template +void +reporter<_>::on_case_begin ( + std::string const& name) +{ + case_results_ = case_results (name); + + stream_.get() << + suite_results_.name << + (case_results_.name.empty() ? + "" : (" " + case_results_.name)); +} + +template +void +reporter<_>::on_case_end() +{ + suite_results_.add (case_results_); +} + +template +void +reporter<_>::on_pass() +{ + ++case_results_.total; +} + +template +void +reporter<_>::on_fail ( + std::string const& reason) +{ + ++case_results_.failed; + ++case_results_.total; + stream_.get() << + "#" << case_results_.total << + " failed" << + (reason.empty() ? "" : ": ") << reason; +} + +template +void +reporter<_>::on_log ( + std::string const& s) +{ + stream_.get() << s; +} + +} // detail + +using reporter = detail::reporter<>; + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/results.hpp b/include/beast/detail/unit_test/results.hpp new file mode 100644 index 00000000..75231fa8 --- /dev/null +++ b/include/beast/detail/unit_test/results.hpp @@ -0,0 +1,246 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_RESULTS_HPP +#define BEAST_DETAIL_UNIT_TEST_RESULTS_HPP + +#include + +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** Holds a set of test condition outcomes in a testcase. */ +class case_results +{ +public: + /** Holds the result of evaluating one test condition. */ + struct test + { + explicit test (bool pass_) + : pass (pass_) + { + } + + test (bool pass_, std::string const& reason_) + : pass (pass_) + , reason (reason_) + { + } + + bool pass; + std::string reason; + }; + +private: + class tests_t + : public const_container > + { + private: + std::size_t failed_; + + public: + tests_t () + : failed_ (0) + { + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return cont().size(); + } + + /** Returns the number of failed test conditions. */ + std::size_t + failed() const + { + return failed_; + } + + /** Register a successful test condition. */ + void + pass() + { + cont().emplace_back (true); + } + + /** Register a failed test condition. */ + void + fail (std::string const& reason = "") + { + ++failed_; + cont().emplace_back (false, reason); + } + }; + + class log_t + : public const_container > + { + public: + /** Insert a string into the log. */ + void + insert (std::string const& s) + { + cont().push_back (s); + } + }; + + std::string name_; + +public: + explicit case_results (std::string const& name = "") + : name_ (name) + { + } + + /** Returns the name of this testcase. */ + std::string const& + name() const + { + return name_; + } + + /** Memberspace for a container of test condition outcomes. */ + tests_t tests; + + /** Memberspace for a container of testcase log messages. */ + log_t log; +}; + +//-------------------------------------------------------------------------- + +/** Holds the set of testcase results in a suite. */ +class suite_results + : public const_container > +{ +private: + std::string name_; + std::size_t total_ = 0; + std::size_t failed_ = 0; + +public: + explicit suite_results (std::string const& name = "") + : name_ (name) + { + } + + /** Returns the name of this suite. */ + std::string const& + name() const + { + return name_; + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return total_; + } + + /** Returns the number of failures. */ + std::size_t + failed() const + { + return failed_; + } + + /** Insert a set of testcase results. */ + /** @{ */ + void + insert (case_results&& r) + { + cont().emplace_back (std::move (r)); + total_ += r.tests.total(); + failed_ += r.tests.failed(); + } + + void + insert (case_results const& r) + { + cont().push_back (r); + total_ += r.tests.total(); + failed_ += r.tests.failed(); + } + /** @} */ +}; + +//------------------------------------------------------------------------------ + +// VFALCO TODO Make this a template class using scoped allocators +/** Holds the results of running a set of testsuites. */ +class results + : public const_container > +{ +private: + std::size_t m_cases; + std::size_t total_; + std::size_t failed_; + +public: + results() + : m_cases (0) + , total_ (0) + , failed_ (0) + { + } + + /** Returns the total number of test cases. */ + std::size_t + cases() const + { + return m_cases; + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return total_; + } + + /** Returns the number of failures. */ + std::size_t + failed() const + { + return failed_; + } + + /** Insert a set of suite results. */ + /** @{ */ + void + insert (suite_results&& r) + { + m_cases += r.size(); + total_ += r.total(); + failed_ += r.failed(); + cont().emplace_back (std::move (r)); + } + + void + insert (suite_results const& r) + { + m_cases += r.size(); + total_ += r.total(); + failed_ += r.failed(); + cont().push_back (r); + } + /** @} */ +}; + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/runner.hpp b/include/beast/detail/unit_test/runner.hpp new file mode 100644 index 00000000..6f467745 --- /dev/null +++ b/include/beast/detail/unit_test/runner.hpp @@ -0,0 +1,338 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_RUNNER_H_INCLUDED +#define BEAST_DETAIL_UNIT_TEST_RUNNER_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** Unit test runner interface. + Derived classes can customize the reporting behavior. This interface is + injected into the unit_test class to receive the results of the tests. +*/ +class runner +{ +private: + // Reroutes log output to the runner + class stream_t : public beast::detail::abstract_ostream + { + private: + runner& owner_; + + public: + stream_t() = delete; + stream_t& operator= (stream_t const&) = delete; + + template + stream_t (runner& owner); + + void + write (string_type const& s) override + { + owner_.log (s); + } + }; + + stream_t stream_; + std::string arg_; + bool default_ = false; + bool failed_ = false; + bool cond_ = false; + std::recursive_mutex mutex_; + +public: + virtual ~runner() = default; + runner (runner const&) = default; + runner& operator= (runner const&) = default; + + template + runner(); + + /** Set the argument string. + The argument string is available to suites and + allows for customization of the test. Each suite + defines its own syntax for the argumnet string. + The same argument is passed to all suites. + */ + void + arg (std::string const& s) + { + arg_ = s; + } + + /** Returns the argument string. */ + std::string const& + arg() const + { + return arg_; + } + + /** Run the specified suite. + @return `true` if any conditions failed. + */ + template + bool + run (suite_info const& s); + + /** Run a sequence of suites. + The expression + `FwdIter::value_type` + must be convertible to `suite_info`. + @return `true` if any conditions failed. + */ + template + bool + run (FwdIter first, FwdIter last); + + /** Conditionally run a sequence of suites. + pred will be called as: + @code + bool pred (suite_info const&); + @endcode + @return `true` if any conditions failed. + */ + template + bool + run_if (FwdIter first, FwdIter last, Pred pred = Pred{}); + + /** Run all suites in a container. + @return `true` if any conditions failed. + */ + template + bool + run_each (SequenceContainer const& c); + + /** Conditionally run suites in a container. + pred will be called as: + @code + bool pred (suite_info const&); + @endcode + @return `true` if any conditions failed. + */ + template + bool + run_each_if (SequenceContainer const& c, Pred pred = Pred{}); + +private: + // + // Overrides + // + + /** Called when a new suite starts. */ + virtual + void + on_suite_begin (suite_info const&) + { + } + + /** Called when a suite ends. */ + virtual + void + on_suite_end() + { + } + + /** Called when a new case starts. */ + virtual + void + on_case_begin (std::string const&) + { + } + + /** Called when a new case ends. */ + virtual + void + on_case_end() + { + } + + /** Called for each passing condition. */ + virtual + void + on_pass () + { + } + + /** Called for each failing condition. */ + virtual + void + on_fail (std::string const&) + { + } + + /** Called when a test logs output. */ + virtual + void + on_log (std::string const&) + { + } + +private: + friend class suite; + + abstract_ostream& + stream() + { + return stream_; + } + + // Start a new testcase. + template + void + testcase (std::string const& name); + + template + void + pass(); + + template + void + fail (std::string const& reason); + + template + void + log (std::string const& s); +}; + +//------------------------------------------------------------------------------ + +template +runner::stream_t::stream_t (runner& owner) + : owner_ (owner) +{ +} + +//------------------------------------------------------------------------------ + +template +runner::runner() + : stream_ (*this) +{ +} + +template +bool +runner::run (suite_info const& s) +{ + // Enable 'default' testcase + default_ = true; + failed_ = false; + on_suite_begin (s); + s.run (*this); + // Forgot to call pass or fail. + assert (cond_); + on_case_end(); + on_suite_end(); + return failed_; +} + +template +bool +runner::run (FwdIter first, FwdIter last) +{ + bool failed (false); + for (;first != last; ++first) + failed = run (*first) || failed; + return failed; +} + +template +bool +runner::run_if (FwdIter first, FwdIter last, Pred pred) +{ + bool failed (false); + for (;first != last; ++first) + if (pred (*first)) + failed = run (*first) || failed; + return failed; +} + +template +bool +runner::run_each (SequenceContainer const& c) +{ + bool failed (false); + for (auto const& s : c) + failed = run (s) || failed; + return failed; +} + +template +bool +runner::run_each_if (SequenceContainer const& c, Pred pred) +{ + bool failed (false); + for (auto const& s : c) + if (pred (s)) + failed = run (s) || failed; + return failed; +} + +template +void +runner::testcase (std::string const& name) +{ + std::lock_guard lock(mutex_); + // Name may not be empty + assert (default_ || ! name.empty()); + // Forgot to call pass or fail + assert (default_ || cond_); + if (! default_) + on_case_end(); + default_ = false; + cond_ = false; + on_case_begin (name); +} + +template +void +runner::pass() +{ + std::lock_guard lock(mutex_); + if (default_) + testcase (""); + on_pass(); + cond_ = true; +} + +template +void +runner::fail (std::string const& reason) +{ + std::lock_guard lock(mutex_); + if (default_) + testcase (""); + on_fail (reason); + failed_ = true; + cond_ = true; +} + +template +void +runner::log (std::string const& s) +{ + std::lock_guard lock(mutex_); + if (default_) + testcase (""); + on_log (s); +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/suite.hpp b/include/beast/detail/unit_test/suite.hpp new file mode 100644 index 00000000..3732224e --- /dev/null +++ b/include/beast/detail/unit_test/suite.hpp @@ -0,0 +1,608 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_SUITE_HPP +#define BEAST_DETAIL_UNIT_TEST_SUITE_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +class thread; + +/** A testsuite class. + Derived classes execute a series of testcases, where each testcase is + a series of pass/fail tests. To provide a unit test using this class, + derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a + translation unit. +*/ +class suite +{ +public: + enum abort_t + { + no_abort_on_fail, + abort_on_fail + }; + +private: + bool abort_ = false; + bool aborted_ = false; + runner* runner_ = nullptr; + + // This exception is thrown internally to stop the current suite + // in the event of a failure, if the option to stop is set. + struct abort_exception : public std::exception + { + char const* + what() const noexcept override + { + return "suite aborted"; + } + }; + +public: + // Memberspace + class log_t + { + private: + friend class suite; + suite* suite_ = nullptr; + + public: + log_t () = default; + + template + abstract_ostream::scoped_stream_type + operator<< (T const& t); + + /** Returns the raw stream used for output. */ + abstract_ostream& + stream(); + }; +private: + + class scoped_testcase; + + // Memberspace + class testcase_t + { + private: + friend class suite; + suite* suite_ = nullptr; + std::stringstream ss_; + + public: + testcase_t() = default; + + /** Open a new testcase. + A testcase is a series of evaluated test conditions. A test suite + may have multiple test cases. A test is associated with the last + opened testcase. When the test first runs, a default unnamed + case is opened. Tests with only one case may omit the call + to testcase. + @param abort If `true`, the suite will be stopped on first failure. + */ + void + operator() (std::string const& name, + abort_t abort = no_abort_on_fail); + + /** Stream style composition of testcase names. */ + /** @{ */ + scoped_testcase + operator() (abort_t abort); + + template + scoped_testcase + operator<< (T const& t); + /** @} */ + }; + +public: + /** Type for scoped stream logging. + To use this type, declare a local variable of the type + on the stack in derived class member function and construct + it from log.stream(); + + @code + + scoped_stream ss (log.stream(); + + ss << "Hello" << std::endl; + ss << "world" << std::endl; + + @endcode + + Streams constructed in this fashion will not have the line + ending automatically appended. + + Thread safety: + + The scoped_stream may only be used by one thread. + Multiline output sent to the stream will be atomically + written to the underlying abstract_Ostream + */ + using scoped_stream = abstract_ostream::scoped_stream_type; + + /** Memberspace for logging. */ + log_t log; + + /** Memberspace for declaring test cases. */ + testcase_t testcase; + + /** Returns the "current" running suite. + If no suite is running, nullptr is returned. + */ + static suite* this_suite() + { + return *p_this_suite(); + } + + /** Invokes the test using the specified runner. + Data members are set up here instead of the constructor as a + convenience to writing the derived class to avoid repetition of + forwarded constructor arguments to the base. + Normally this is called by the framework for you. + */ + template + void + operator() (runner& r); + + /** Evaluate a test condition. + The condition is passed as a template argument instead of `bool` so + that implicit conversion is not required. The `reason` argument is + logged if the condition is false. + @return `true` if the test condition indicates success. + */ + template + bool + expect(Condition const& shouldBeTrue, + String const& reason); + + template + bool + expect(Condition const& shouldBeTrue) + { + return expect(shouldBeTrue, ""); + } + + /** Expect an exception from f() */ + /** @{ */ + template + bool + except (F&& f, String const& reason); + + template + bool + except (F&& f) + { + return except(f, ""); + } + /** @} */ + + /** Expect an exception of the given type from f() */ + /** @{ */ + template + bool + except (F&& f, String const& reason); + + template + bool + except (F&& f) + { + return except(f, ""); + } + /** @} */ + + /** Fail if f() throws */ + /** @{ */ + template + bool + unexcept (F&& f, String const& reason); + + template + bool + unexcept (F&& f) + { + return unexcept(f, ""); + } + /** @} */ + + /** Return the argument associated with the runner. */ + std::string const& + arg() const + { + return runner_->arg(); + } + + // DEPRECATED + // @return `true` if the test condition indicates success (a false value) + template + bool + unexpected (Condition shouldBeFalse, + String const& reason); + + template + bool + unexpected (Condition shouldBeFalse) + { + return unexpected (shouldBeFalse, ""); + } + + /** Record a successful test condition. */ + template + void + pass(); + + /** Record a failure. */ + template + void + fail (std::string const& reason = ""); + +private: + friend class thread; + + static + suite** + p_this_suite() + { + static suite* pts = nullptr; + return &pts; + } + + /** Runs the suite. */ + virtual + void + run() = 0; + + void + propagate_abort(); + + template + void + run (runner& r); +}; + +//------------------------------------------------------------------------------ + +template +inline +abstract_ostream::scoped_stream_type +suite::log_t::operator<< (T const& t) +{ + return suite_->runner_->stream() << t; +} + +/** Returns the raw stream used for output. */ +inline +abstract_ostream& +suite::log_t::stream() +{ + return suite_->runner_->stream(); +} + +//------------------------------------------------------------------------------ + +// Helper for streaming testcase names +class suite::scoped_testcase +{ +private: + suite* suite_; + std::stringstream* ss_; + +public: + ~scoped_testcase(); + + scoped_testcase (suite* s, std::stringstream* ss); + + template + scoped_testcase (suite* s, std::stringstream* ss, T const& t); + + scoped_testcase& operator= (scoped_testcase const&) = delete; + + template + scoped_testcase& + operator<< (T const& t); +}; + +inline +suite::scoped_testcase::~scoped_testcase() +{ + auto const& name (ss_->str()); + if (! name.empty()) + suite_->runner_->testcase (name); +} + +inline +suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss) + : suite_ (s) + , ss_ (ss) +{ + ss_->clear(); + ss_->str({}); + +} + +template +inline +suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss, T const& t) + : suite_ (s) + , ss_ (ss) +{ + ss_->clear(); + ss_->str({}); + *ss_ << t; +} + +template +inline +suite::scoped_testcase& +suite::scoped_testcase::operator<< (T const& t) +{ + *ss_ << t; + return *this; +} + +//------------------------------------------------------------------------------ + +inline +void +suite::testcase_t::operator() (std::string const& name, + abort_t abort) +{ + suite_->abort_ = abort == abort_on_fail; + suite_->runner_->testcase (name); +} + +inline +suite::scoped_testcase +suite::testcase_t::operator() (abort_t abort) +{ + suite_->abort_ = abort == abort_on_fail; + return { suite_, &ss_ }; +} + +template +inline +suite::scoped_testcase +suite::testcase_t::operator<< (T const& t) +{ + return { suite_, &ss_, t }; +} + +//------------------------------------------------------------------------------ + +template +void +suite::operator()(runner& r) +{ + *p_this_suite() = this; + try + { + run(r); + *p_this_suite() = nullptr; + } + catch(...) + { + *p_this_suite() = nullptr; + throw; + } +} + +template +inline +bool +suite::expect(Condition const& shouldBeTrue, + String const& reason) +{ + if(shouldBeTrue) + { + pass(); + return true; + } + fail(reason); + return false; +} + +template +bool +suite::except (F&& f, String const& reason) +{ + try + { + f(); + fail(reason); + return false; + } + catch(...) + { + pass(); + } + return true; +} + +template +bool +suite::except (F&& f, String const& reason) +{ + try + { + f(); + fail(reason); + return false; + } + catch(E const&) + { + pass(); + } + return true; +} + +template +bool +suite::unexcept (F&& f, String const& reason) +{ + try + { + f(); + pass(); + return true; + } + catch(...) + { + fail(reason); + } + return false; +} + +template +inline +bool +suite::unexpected (Condition shouldBeFalse, + String const& reason) +{ + bool const b = + static_cast(shouldBeFalse); + if (! b) + pass(); + else + fail (reason); + return ! b; +} + +template +void +suite::pass() +{ + propagate_abort(); + runner_->pass(); +} + +template +void +suite::fail (std::string const& reason) +{ + propagate_abort(); + runner_->fail (reason); + if (abort_) + { + aborted_ = true; + throw abort_exception(); + } +} + +inline +void +suite::propagate_abort() +{ + if (abort_ && aborted_) + throw abort_exception(); +} + +template +void +suite::run (runner& r) +{ + runner_ = &r; + log.suite_ = this; + testcase.suite_ = this; + + try + { + run(); + } + catch (abort_exception const&) + { + // ends the suite + } + catch (std::exception const& e) + { + runner_->fail ("unhandled exception: " + + std::string (e.what())); + } + catch (...) + { + runner_->fail ("unhandled exception"); + } +} + +} // unit_test +} // detail +} // beast + +//------------------------------------------------------------------------------ + +// detail: +// This inserts the suite with the given manual flag +#define BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,manual) \ + static beast::detail::unit_test::detail::insert_suite \ + Library ## Module ## Class ## _test_instance ( \ + #Class, #Module, #Library, manual); + +//------------------------------------------------------------------------------ + +// Preprocessor directives for controlling unit test definitions. + +// If this is already defined, don't redefine it. This allows +// programs to provide custom behavior for testsuite definitions +// +#ifndef BEAST_DEFINE_TESTSUITE + +/** Enables insertion of test suites into the global container. + The default is to insert all test suite definitions into the global + container. If BEAST_DEFINE_TESTSUITE is user defined, this macro + has no effect. +*/ +#ifndef BEAST_NO_UNIT_TEST_INLINE +#define BEAST_NO_UNIT_TEST_INLINE 0 +#endif + +/** Define a unit test suite. + + Class The type representing the class being tested. + Module Identifies the module. + Library Identifies the library. + + The declaration for the class implementing the test should be the same + as Class ## _test. For example, if Class is aged_ordered_container, the + test class must be declared as: + + @code + + struct aged_ordered_container_test : beast::unit_test::suite + { + //... + }; + + @endcode + + The macro invocation must appear in the same namespace as the test class. +*/ + +#if BEAST_NO_UNIT_TEST_INLINE +#define BEAST_DEFINE_TESTSUITE(Class,Module,Library) + +#else +#include +#define BEAST_DEFINE_TESTSUITE(Class,Module,Library) \ + BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,false) +#define BEAST_DEFINE_TESTSUITE_MANUAL(Class,Module,Library) \ + BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,true) + +#endif + +#endif + +//------------------------------------------------------------------------------ + +#endif diff --git a/include/beast/detail/unit_test/suite_info.hpp b/include/beast/detail/unit_test/suite_info.hpp new file mode 100644 index 00000000..2176bb82 --- /dev/null +++ b/include/beast/detail/unit_test/suite_info.hpp @@ -0,0 +1,119 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_SUITE_INFO_HPP +#define BEAST_DETAIL_UNIT_TEST_SUITE_INFO_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +class runner; + +/** Associates a unit test type with metadata. */ +class suite_info +{ +private: + using run_type = std::function ; + + std::string name_; + std::string module_; + std::string library_; + bool m_manual; + run_type m_run; + +public: + template + suite_info (std::string const& name, std::string const& module, + std::string const& library, bool manual, run_type run); + + std::string const& + name() const + { + return name_; + } + + std::string const& + module() const + { + return module_; + } + + std::string const& + library() const + { + return library_; + } + + /** Returns `true` if this suite only runs manually. */ + bool + manual() const + { + return m_manual; + } + + /** Return the canonical suite name as a string. */ + template + std::string + full_name() const; + + /** Run a new instance of the associated test suite. */ + void + run (runner& r) const + { + m_run (r); + } +}; + +//------------------------------------------------------------------------------ + +template +suite_info::suite_info (std::string const& name, std::string const& module, + std::string const& library, bool manual, run_type run) + : name_ (name) + , module_ (module) + , library_ (library) + , m_manual (manual) + , m_run (std::move (run)) +{ +} + +template +std::string +suite_info::full_name() const +{ + return library_ + "." + module_ + "." + name_; +} + +inline +bool +operator< (suite_info const& lhs, suite_info const& rhs) +{ + return lhs.full_name() < rhs.full_name(); +} + +/** Convenience for producing suite_info for a given test type. */ +template +suite_info +make_suite_info (std::string const& name, std::string const& module, + std::string const& library, bool manual) +{ + return suite_info(name, module, library, manual, + [](runner& r) { return Suite{}(r); }); +} + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/unit_test/suite_list.hpp b/include/beast/detail/unit_test/suite_list.hpp new file mode 100644 index 00000000..1393d406 --- /dev/null +++ b/include/beast/detail/unit_test/suite_list.hpp @@ -0,0 +1,75 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_SUITE_LIST_HPP +#define BEAST_DETAIL_UNIT_TEST_SUITE_LIST_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** A container of test suites. */ +class suite_list + : public const_container > +{ +private: +#ifndef NDEBUG + std::unordered_set names_; + std::unordered_set classes_; +#endif + +public: + /** Insert a suite into the set. + The suite must not already exist. + */ + template + void + insert (char const* name, char const* module, char const* library, + bool manual); +}; + +//------------------------------------------------------------------------------ + +template +void +suite_list::insert (char const* name, char const* module, char const* library, + bool manual) +{ +#ifndef NDEBUG + { + std::string s; + s = std::string(library) + "." + module + "." + name; + auto const result (names_.insert(s)); + assert (result.second); // Duplicate name + } + + { + auto const result (classes_.insert ( + std::type_index (typeid(Suite)))); + assert (result.second); // Duplicate type + } +#endif + + cont().emplace (std::move (make_suite_info ( + name, module, library, manual))); +} + +} // unit_test +} // detail +} // beast + +#endif + diff --git a/include/beast/detail/unit_test/thread.hpp b/include/beast/detail/unit_test/thread.hpp new file mode 100644 index 00000000..a684abd3 --- /dev/null +++ b/include/beast/detail/unit_test/thread.hpp @@ -0,0 +1,128 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_UNIT_TEST_THREAD_HPP +#define BEAST_DETAIL_UNIT_TEST_THREAD_HPP + +#include +#include +#include +#include + +namespace beast { +namespace detail { + +inline +namespace unit_test { + +/** Replacement for std::thread that handles exceptions in unit tests. */ +class thread +{ +private: + suite* s_ = nullptr; + std::thread t_; + +public: + using id = std::thread::id; + using native_handle_type = std::thread::native_handle_type; + + thread() = default; + thread (thread const&) = delete; + thread& operator= (thread const&) = delete; + + thread (thread&& other) + : s_ (other.s_) + , t_ (std::move(other.t_)) + { + } + + thread& operator= (thread&& other) + { + s_ = other.s_; + t_ = std::move(other.t_); + return *this; + } + + template + explicit + thread (suite& s, F&& f, Args&&... args) + : s_ (&s) + { + std::function b = + std::bind(std::forward(f), + std::forward(args)...); + t_ = std::thread (&thread::run, this, + std::move(b)); + } + + bool + joinable() const + { + return t_.joinable(); + } + + std::thread::id + get_id() const + { + return t_.get_id(); + } + + static + unsigned + hardware_concurrency() noexcept + { + return std::thread::hardware_concurrency(); + } + + void + join() + { + t_.join(); + s_->propagate_abort(); + } + + void + detach() + { + t_.detach(); + } + + void + swap (thread& other) + { + std::swap(s_, other.s_); + std::swap(t_, other.t_); + } + +private: + void + run (std::function f) + { + try + { + f(); + } + catch (suite::abort_exception const&) + { + } + catch (std::exception const& e) + { + s_->fail ("unhandled exception: " + + std::string (e.what())); + } + catch (...) + { + s_->fail ("unhandled exception"); + } + } +}; + +} // unit_test +} // detail +} // beast + +#endif diff --git a/include/beast/detail/write_streambuf.hpp b/include/beast/detail/write_streambuf.hpp new file mode 100644 index 00000000..ccac4b4c --- /dev/null +++ b/include/beast/detail/write_streambuf.hpp @@ -0,0 +1,146 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_WRITE_STREAMBUF_HPP +#define BEAST_DETAIL_WRITE_STREAMBUF_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +// detects string literals. +template +struct is_string_literal : std::integral_constant::type>::value && + std::is_same::type>::value> +{ +}; + +// `true` if a call to boost::asio::buffer(T const&) is possible +// note: we exclude string literals because boost::asio::buffer() +// will include the null terminator, which we don't want. +template +class is_BufferConvertible +{ + template()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value && + ! is_string_literal::value; +}; + +template +inline +void +write_streambuf(Streambuf&) +{ +} + +template +void +write_streambuf(Streambuf& streambuf, + boost::asio::const_buffer const& buffer) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + streambuf.commit(buffer_copy( + streambuf.prepare(buffer_size(buffer)), + buffer)); +} + +template +void +write_streambuf(Streambuf& streambuf, + boost::asio::mutable_buffer const& buffer) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + streambuf.commit(buffer_copy( + streambuf.prepare(buffer_size(buffer)), + buffer)); +} + +template +typename std::enable_if< + is_BufferConvertible::value && + ! std::is_convertible::value && + ! std::is_convertible::value +>::type +write_streambuf(Streambuf& streambuf, T const& t) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const buffers = boost::asio::buffer(t); + streambuf.commit(buffer_copy( + streambuf.prepare(buffer_size(buffers)), + buffers)); +} + +template +typename std::enable_if< + is_ConstBufferSequence::value && + ! is_BufferConvertible::value && + ! std::is_convertible::value && + ! std::is_convertible::value +>::type +write_streambuf(Streambuf& streambuf, Buffers const& buffers) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + streambuf.commit(buffer_copy( + streambuf.prepare(buffer_size(buffers)), + buffers)); +} + +template +void +write_streambuf(Streambuf& streambuf, const char (&s)[N]) +{ + using boost::asio::buffer_copy; + streambuf.commit(buffer_copy( + streambuf.prepare(N - 1), + boost::asio::buffer(s, N - 1))); +} + +template +typename std::enable_if< + ! is_string_literal::value && + ! is_ConstBufferSequence::value && + ! is_BufferConvertible::value && + ! std::is_convertible::value && + ! std::is_convertible::value +>::type +write_streambuf(Streambuf& streambuf, T const& t) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + auto const s = boost::lexical_cast(t); + streambuf.commit(buffer_copy( + streambuf.prepare(s.size()), buffer(s))); +} + +template +void +write_streambuf(Streambuf& streambuf, T0&& t0, T1&& t1, TN... tn) +{ + write_streambuf(streambuf, std::forward(t0)); + write_streambuf(streambuf, std::forward(t1)); + write_streambuf(streambuf, std::forward(tn)...); +} + +} // detail +} // beast + +#endif diff --git a/include/beast/handler_alloc.hpp b/include/beast/handler_alloc.hpp new file mode 100644 index 00000000..37ca25b1 --- /dev/null +++ b/include/beast/handler_alloc.hpp @@ -0,0 +1,148 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HANDLER_ALLOC_HPP +#define BEAST_HANDLER_ALLOC_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +// Guidance from +// http://howardhinnant.github.io/allocator_boilerplate.html + +/** An allocator that uses handler customizations. + + This allocator uses the handler customizations `asio_handler_allocate` + and `asio_handler_deallocate` to manage memory. It meets the requirements + of `Allocator` and can be used anywhere a `std::allocator` is + accepted. + + @tparam T The type of objects allocated by the allocator. + + @tparam Handler The type of handler. + + @note Allocated memory is only valid until the handler is called. The + caller is still responsible for freeing memory. +*/ +#if GENERATING_DOCS +template +class handler_alloc; +#else +template +class handler_alloc +{ +private: + // We want a partial template specialization as a friend + // but that isn't allowed so we friend all versions. This + // should produce a compile error if Handler is not + // constructible from H. + // + template + friend class handler_alloc; + + Handler h_; + +public: + using value_type = T; + using is_always_equal = std::true_type; + + handler_alloc() = delete; + handler_alloc(handler_alloc&&) = default; + handler_alloc(handler_alloc const&) = default; + handler_alloc& operator=(handler_alloc&&) = default; + handler_alloc& operator=(handler_alloc const&) = default; + + /** Construct the allocator. + + The handler is moved or copied into the allocator. + */ + explicit + handler_alloc(Handler&& h) + : h_(std::move(h)) + { + } + + /** Construct the allocator. + + A copy of the handler is made. + */ + explicit + handler_alloc(Handler const& h) + : h_(h) + { + } + + template + handler_alloc( + handler_alloc&& other) + : h_(std::move(other.h_)) + { + } + + template + handler_alloc( + handler_alloc const& other) + : h_(other.h_) + { + } + + value_type* + allocate(std::ptrdiff_t n) + { + auto const size = n * sizeof(T); + return static_cast( + boost_asio_handler_alloc_helpers::allocate( + size, h_)); + } + + void + deallocate(value_type* p, std::ptrdiff_t n) + { + auto const size = n * sizeof(T); + boost_asio_handler_alloc_helpers::deallocate( + p, size, h_); + } + +#ifdef _MSC_VER + // Work-around for MSVC not using allocator_traits + // in the implementation of shared_ptr + // + void + destroy(T* t) + { + t->~T(); + } +#endif + + template + friend + bool + operator==(handler_alloc const& lhs, + handler_alloc const& rhs) + { + return true; + } + + template + friend + bool + operator!=(handler_alloc const& lhs, + handler_alloc const& rhs) + { + return !(lhs == rhs); + } +}; +#endif + +} // beast + +#endif diff --git a/include/beast/http.hpp b/include/beast/http.hpp new file mode 100644 index 00000000..104f409d --- /dev/null +++ b/include/beast/http.hpp @@ -0,0 +1,28 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_HPP_INCLUDED +#define BEAST_HTTP_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/beast/http/basic_headers.hpp b/include/beast/http/basic_headers.hpp new file mode 100644 index 00000000..500d0b61 --- /dev/null +++ b/include/beast/http/basic_headers.hpp @@ -0,0 +1,434 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BASIC_HEADERS_HPP +#define BEAST_HTTP_BASIC_HEADERS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +class basic_headers; + +namespace detail { + +class basic_headers_base +{ +public: + struct value_type + { + std::string first; + std::string second; + + value_type(boost::string_ref const& name_, + boost::string_ref const& value_) + : first(name_) + , second(value_) + { + } + + boost::string_ref + name() const + { + return first; + } + + boost::string_ref + value() const + { + return second; + } + }; + +protected: + template + friend class beast::http::basic_headers; + + struct element + : boost::intrusive::set_base_hook < + boost::intrusive::link_mode < + boost::intrusive::normal_link>> + , boost::intrusive::list_base_hook < + boost::intrusive::link_mode < + boost::intrusive::normal_link>> + { + value_type data; + + element(boost::string_ref const& name, + boost::string_ref const& value) + : data(name, value) + { + } + }; + + struct less : private beast::detail::ci_less + { + template + bool + operator()(String const& lhs, element const& rhs) const + { + return ci_less::operator()(lhs, rhs.data.first); + } + + template + bool + operator()(element const& lhs, String const& rhs) const + { + return ci_less::operator()(lhs.data.first, rhs); + } + + bool + operator()(element const& lhs, element const& rhs) const + { + return ci_less::operator()( + lhs.data.first, rhs.data.first); + } + }; + + using list_t = typename boost::intrusive::make_list< + element, boost::intrusive::constant_time_size>::type; + + using set_t = typename boost::intrusive::make_set< + element, boost::intrusive::constant_time_size, + boost::intrusive::compare>::type; + + // data + set_t set_; + list_t list_; + + basic_headers_base(set_t&& set, list_t&& list) + : set_(std::move(set)) + , list_(std::move(list)) + { + + } + +public: + class const_iterator; + + using iterator = const_iterator; + + basic_headers_base() = default; + + /// Returns an iterator to the beginning of the field sequence. + iterator + begin() const; + + /// Returns an iterator to the end of the field sequence. + iterator + end() const; + + /// Returns an iterator to the beginning of the field sequence. + iterator + cbegin() const; + + /// Returns an iterator to the end of the field sequence. + iterator + cend() const; +}; + +//------------------------------------------------------------------------------ + +class basic_headers_base::const_iterator +{ + using iter_type = list_t::const_iterator; + + iter_type it_; + + template + friend class beast::http::basic_headers; + + friend class basic_headers_base; + + const_iterator(iter_type it) + : it_(it) + { + } + +public: + using value_type = + typename basic_headers_base::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->data; + } + + pointer + operator->() const + { + return &**this; + } + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +/** Container to store HTTP headers. + + Meets the requirements of `FieldSequence`. +*/ +template +class basic_headers +#if ! GENERATING_DOCS + : private beast::detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc< + detail::basic_headers_base::element>> + , public detail::basic_headers_base +#endif +{ + using alloc_type = typename + std::allocator_traits:: + template rebind_alloc< + detail::basic_headers_base::element>; + + using alloc_traits = + std::allocator_traits; + + using size_type = + typename std::allocator_traits::size_type; + + void + delete_all(); + + void + move_assign(basic_headers&, std::false_type); + + void + move_assign(basic_headers&, std::true_type); + + void + copy_assign(basic_headers const&, std::false_type); + + void + copy_assign(basic_headers const&, std::true_type); + + template + void + copy_from(FieldSequence const& fs) + { + for(auto const& e : fs) + insert(e.first, e.second); + } + +public: + /// The type of allocator used. + using allocator_type = Allocator; + + /// Default constructor. + basic_headers() = default; + + /// Destructor + ~basic_headers(); + + /** Construct the headers. + + @param alloc The allocator to use. + */ + explicit + basic_headers(Allocator const& alloc); + + /** Move constructor. + + The moved-from object becomes an empty field sequence. + + @param other The object to move from. + */ + basic_headers(basic_headers&& other); + + /** Move assignment. + + The moved-from object becomes an empty field sequence. + + @param other The object to move from. + */ + basic_headers& operator=(basic_headers&& other); + + /// Copy constructor. + basic_headers(basic_headers const&); + + /// Copy assignment. + basic_headers& operator=(basic_headers const&); + + /// Copy constructor. + template + basic_headers(basic_headers const&); + + /// Copy assignment. + template + basic_headers& operator=(basic_headers const&); + + /// Construct from a field sequence. + template + basic_headers(FwdIt first, FwdIt last); + + /// Returns `true` if the field sequence contains no elements. + bool + empty() const + { + return set_.empty(); + } + + /// Returns the number of elements in the field sequence. + std::size_t + size() const + { + return set_.size(); + } + + /** Returns `true` if the specified field exists. */ + bool + exists(boost::string_ref const& name) const + { + return set_.find(name, less{}) != set_.end(); + } + + /** Returns an iterator to the case-insensitive matching header. */ + iterator + find(boost::string_ref const& name) const; + + /** Returns the value for a case-insensitive matching header, or "" */ + boost::string_ref + operator[](boost::string_ref const& name) const; + + /** Clear the contents of the basic_headers. */ + void + clear() noexcept; + + /** Remove a field. + + @return The number of fields removed. + */ + std::size_t + erase(boost::string_ref const& name); + + /** Insert a field value. + + If a field value already exists the new value will be + extended as per RFC2616 Section 4.2. + */ + // VFALCO TODO Consider allowing rvalue references for std::move? + void + insert(boost::string_ref const& name, + boost::string_ref const& value); + + /** Insert a field value. + + If a field value already exists the new value will be + extended as per RFC2616 Section 4.2. + */ + template::value>::type> + void + insert(boost::string_ref name, T const& value) + { + insert(name, + boost::lexical_cast(value)); + } + + /** Replace a field value. + + The current field value, if any, is removed. Then the + specified value is inserted as if by insert(field, value). + */ + void + replace(boost::string_ref const& name, + boost::string_ref const& value); + + /** Replace a field value. + + The current field value, if any, is removed. Then the + specified value is inserted as if by insert(field, value). + */ + template::value>::type> + void + replace(boost::string_ref const& name, T const& value) + { + replace(name, + boost::lexical_cast(value)); + } +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp new file mode 100644 index 00000000..027f3a8a --- /dev/null +++ b/include/beast/http/basic_parser.hpp @@ -0,0 +1,761 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BASIC_PARSER_HPP +#define BEAST_HTTP_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace parse_flag { +enum values +{ + chunked = 1 << 0, + connection_keep_alive = 1 << 1, + connection_close = 1 << 2, + connection_upgrade = 1 << 3, + trailing = 1 << 4, + upgrade = 1 << 5, + skipbody = 1 << 6, + contentlength = 1 << 7 +}; +} // parse_flag + +/** Parser for producing HTTP requests and responses. + + During parsing, callbacks will be made to the derived class + if those members are present (detected through SFINAE). The + signatures which can be present in the derived class are:
+ + @li `void on_method(boost::string_ref const&, error_code& ec)` + + Called for each piece of the Request-Method + + @li `void on_uri(boost::string_ref const&, error_code& ec)` + + Called for each piece of the Request-URI + + @li `void on_reason(boost::string_ref const&, error_code& ec)` + + Called for each piece of the reason-phrase + + @li `void on_request(error_code& ec)` + + Called after the entire Request-Line has been parsed successfully. + + @li `void on_response(error_code& ec)` + + Called after the entire Response-Line has been parsed successfully. + + @li `void on_field(boost::string_ref const&, error_code& ec)` + + Called for each piece of the current header field. + + @li `void on_value(boost::string_ref const&, error_code& ec)` + + Called for each piece of the current header value. + + @li `int on_headers(error_code& ec)` + + Called when all the headers have been parsed successfully. + + @li `void on_body(boost::string_ref const&, error_code& ec)` + + Called for each piece of the body. If the headers indicated + chunked encoding, the chunk encoding is removed from the + buffer before being passed to the callback. + + @li `void on_complete(error_code& ec)` + + Called when the entire message has been parsed successfully. + At this point, basic_parser::complete() returns `true`, and + the parser is ready to parse another message if keep_alive() + would return `true`. + + The return value of `on_headers` is special, it controls whether + or not the parser should expect a body. These are the return values: + + @li *0* The parser should expect a body + + @li *1* The parser should skip the body. For example, this is + used when sending a response to a HEAD request. + + @li *2* The parser should skip ths body, this is an + upgrade to a different protocol. + + The parser uses traits to determine if the callback is possible. + If the Derived type omits one or more callbacks, they are simply + skipped with no compilation error. The default behavior of on_body + when the derived class does not provide the member, is to specify that + the body should not be skipped. + + If a callback sets an error, parsing stops at the current octet + and the error is returned to the caller. +*/ +template +class basic_parser +{ +private: + using self = basic_parser; + typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); + + static std::uint64_t constexpr no_content_length = + std::numeric_limits::max(); + + enum state : std::uint8_t + { + s_closed = 1, + + s_req_start, + s_req_method_start, + s_req_method, + s_req_space_before_url, + s_req_url_start, + s_req_url, + s_req_http_start, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_major_start, + s_req_major, + s_req_minor_start, + s_req_minor, + s_req_line_end, + + s_res_start, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_major_start, + s_res_major, + s_res_minor_start, + s_res_minor, + s_res_status_code_start, + s_res_status_code, + s_res_status_start, + s_res_status, + s_res_line_almost_done, + s_res_line_done, + + s_header_field_start, + s_header_field, + s_header_value_start, + s_header_value_discard_lWs0, + s_header_value_discard_ws0, + s_header_value_almost_done0, + s_header_value_text_start, + s_header_value_discard_lWs, + s_header_value_discard_ws, + s_header_value_text, + s_header_value_almost_done, + + s_headers_almost_done, + s_headers_done, + + s_chunk_size_start, + s_chunk_size, + s_chunk_parameters, + s_chunk_size_almost_done, + + // states below do not count towards + // the limit on the size of the message + + s_body_identity0, + s_body_identity, + s_body_identity_eof0, + s_body_identity_eof, + + s_chunk_data_start, + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done, + + s_complete, + s_restart + }; + + enum field_state : std::uint8_t + { + h_general = 0, + h_C, + h_CO, + h_CON, + + h_matching_connection, + h_matching_proxy_connection, + h_matching_content_length, + h_matching_transfer_encoding, + h_matching_upgrade, + + h_connection, + h_content_length, + h_transfer_encoding, + h_upgrade, + + h_matching_transfer_encoding_chunked, + h_matching_connection_token_start, + h_matching_connection_keep_alive, + h_matching_connection_close, + h_matching_connection_upgrade, + h_matching_connection_token, + + h_transfer_encoding_chunked, + h_connection_keep_alive, + h_connection_close, + h_connection_upgrade, + }; + + std::uint64_t content_length_; + std::uint64_t nread_; + pmf_t cb_; + state s_ : 8; + unsigned flags_ : 8; + unsigned fs_ : 8; + unsigned pos_ : 8; // position in field state + unsigned http_major_ : 16; + unsigned http_minor_ : 16; + unsigned status_code_ : 16; + bool upgrade_ : 1; // true if parser exited for upgrade + +public: + /// Copy constructor. + basic_parser(basic_parser const&) = default; + + /// Copy assignment. + basic_parser& operator=(basic_parser const&) = default; + + /// Constructor + basic_parser() + { + init(std::integral_constant{}); + } + + /// Returns internal flags associated with the parser. + unsigned + flags() const + { + return flags_; + } + + /** Returns `true` if the message end is indicated by eof. + + This function returns true if the semantics of the message require + that the end of the message is signaled by an end of file. For + example, if the message is a HTTP/1.0 message and the Content-Length + is unspecified, the end of the message is indicated by an end of file. + + @return `true` if write_eof must be used to indicate the message end. + */ + bool + needs_eof() const + { + return needs_eof( + std::integral_constant{}); + } + + /** Returns the major HTTP version number. + + Examples: + * Returns 1 for HTTP/1.1 + * Returns 1 for HTTP/1.0 + + @return The HTTP major version number. + */ + unsigned + http_major() const + { + return http_major_; + } + + /** Returns the minor HTTP version number. + + Examples: + * Returns 1 for HTTP/1.1 + * Returns 0 for HTTP/1.0 + + @return The HTTP minor version number. + */ + unsigned + http_minor() const + { + return http_minor_; + } + + /** Returns `true` if the message is an upgrade message. + + A value of `true` indicates that the parser has successfully + completed parsing a HTTP upgrade message. + + @return `true` if the message is an upgrade message. + */ + bool + upgrade() const + { + return upgrade_; + } + + /** Returns the numeric HTTP Status-Code of a response. + + @return The Status-Code. + */ + unsigned + status_code() const + { + return status_code_; + } + + /** Returns `true` if the connection should be kept open. + + @note This function is only valid to call when the parser + is complete. + */ + bool + keep_alive() const; + + /** Returns `true` if the parse has completed succesfully. + + When the parse has completed successfully, and the semantics + of the parsed message indicate that the connection is still + active, a subsequent call to `write` will begin parsing a + new message. + + @return `true` If the parsing has completed successfully. + */ + bool + complete() const + { + return s_ == s_restart; + } + + /** Write data to the parser. + + @param buffers An object meeting the requirements of + ConstBufferSequence that represents the input sequence. + + @param ec Set to the error, if any error occurred. + + @return The number of bytes consumed in the input sequence. + */ + template + std::size_t + write(ConstBufferSequence const& buffers, error_code& ec); + + /** Write data to the parser. + + @param data A pointer to a buffer representing the input sequence. + @param size The number of bytes in the buffer pointed to by data. + @param ec Set to the error, if any error occurred. + + @return The number of bytes consumed in the input sequence. + */ + std::size_t + write(void const* data, std::size_t size, error_code& ec); + + /** Called to indicate the end of file. + + HTTP needs to know where the end of the stream is. For example, + sometimes servers send responses without Content-Length and + expect the client to consume input (for the body) until EOF. + Callbacks and errors will still be processed as usual. + + @note This is typically called when a socket read returns eof. + + @throws boost::system::system_error Thrown on failure. + */ + void + write_eof(error_code& ec); + +private: + Derived& + impl() + { + return *static_cast(this); + } + + void + init(std::true_type) + { + s_ = s_req_start; + } + + void + init(std::false_type) + { + s_ = s_res_start; + } + + bool + needs_eof(std::true_type) const; + + bool + needs_eof(std::false_type) const; + + template + class has_on_method_t + { + template().on_method( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_method = + std::integral_constant::value>; + + template + class has_on_uri_t + { + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_uri = + std::integral_constant::value>; + + template + class has_on_reason_t + { + template().on_reason( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_reason = + std::integral_constant::value>; + + template + class has_on_request_t + { + template().on_request( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_request = + std::integral_constant::value>; + + template + class has_on_response_t + { + template().on_response( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_response = + std::integral_constant::value>; + + template + class has_on_field_t + { + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_field = + std::integral_constant::value>; + + template + class has_on_value_t + { + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_value = + std::integral_constant::value>; + + template + class has_on_headers_t + { + template().on_headers( + std::declval()))>> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_headers = + std::integral_constant::value>; + + template + class has_on_body_t + { + template().on_body( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_body = + std::integral_constant::value>; + + template + class has_on_complete_t + { + template().on_complete( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_complete = + std::integral_constant::value>; + + void call_on_method(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_method(s, ec); + } + + void call_on_method(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_method(error_code& ec, + boost::string_ref const& s) + { + call_on_method(ec, s, std::integral_constant::value>{}); + } + + void call_on_uri(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_uri(s, ec); + } + + void call_on_uri(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_uri(error_code& ec, boost::string_ref const& s) + { + call_on_uri(ec, s, std::integral_constant::value>{}); + } + + void call_on_reason(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_reason(s, ec); + } + + void call_on_reason(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_reason(error_code& ec, boost::string_ref const& s) + { + call_on_reason(ec, s, std::integral_constant::value>{}); + } + + void call_on_request(error_code& ec, std::true_type) + { + impl().on_request(ec); + } + + void call_on_request(error_code&, std::false_type) + { + } + + void call_on_request(error_code& ec) + { + call_on_request(ec, std::integral_constant::value>{}); + } + + void call_on_response(error_code& ec, std::true_type) + { + impl().on_response(ec); + } + + void call_on_response(error_code&, std::false_type) + { + } + + void call_on_response(error_code& ec) + { + call_on_response(ec, std::integral_constant::value>{}); + } + + void call_on_field(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_field(s, ec); + } + + void call_on_field(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_field(error_code& ec, boost::string_ref const& s) + { + call_on_field(ec, s, has_on_field{}); + } + + void call_on_value(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_value(s, ec); + } + + void call_on_value(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_value(error_code& ec, boost::string_ref const& s) + { + call_on_value(ec, s, has_on_value{}); + } + + int call_on_headers(error_code& ec, std::true_type) + { + return impl().on_headers(ec); + } + + int call_on_headers(error_code& ec, std::false_type) + { + return 0; + } + + int call_on_headers(error_code& ec) + { + return call_on_headers(ec, has_on_headers{}); + } + + void call_on_body(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_body(s, ec); + } + + void call_on_body(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_body(error_code& ec, boost::string_ref const& s) + { + call_on_body(ec, s, has_on_body{}); + } + + void call_on_complete(error_code& ec, std::true_type) + { + impl().on_complete(ec); + } + + void call_on_complete(error_code&, std::false_type) + { + } + + void call_on_complete(error_code& ec) + { + call_on_complete(ec, has_on_complete{}); + } +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/chunk_encode.hpp b/include/beast/http/chunk_encode.hpp new file mode 100644 index 00000000..13944fbd --- /dev/null +++ b/include/beast/http/chunk_encode.hpp @@ -0,0 +1,282 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_CHUNK_ENCODE_HPP +#define BEAST_HTTP_CHUNK_ENCODE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +class chunk_encoded_buffers +{ +private: + using const_buffer = boost::asio::const_buffer; + + Buffers buffers_; + const_buffer head_; + const_buffer tail_; + + // Storage for the longest hex string we might need, plus delimiters. + std::array data_; + +public: + using value_type = boost::asio::const_buffer; + + class const_iterator; + + chunk_encoded_buffers() = delete; + chunk_encoded_buffers (chunk_encoded_buffers const&) = default; + chunk_encoded_buffers& operator= (chunk_encoded_buffers const&) = default; + + chunk_encoded_buffers (Buffers const& buffers, bool final_chunk); + + const_iterator + begin() const + { + return const_iterator(*this, false); + } + + const_iterator + end() const + { + return const_iterator(*this, true); + } + +private: + // Unchecked conversion of unsigned to hex string + template + static + typename std::enable_if< + std::is_unsigned::value, OutIter>::type + to_hex(OutIter const first, OutIter const last, Unsigned n); +}; + +template +class chunk_encoded_buffers::const_iterator + : public std::iterator +{ +private: + using iterator = typename Buffers::const_iterator; + enum class Where { head, input, end }; + chunk_encoded_buffers const* buffers_; + Where where_; + iterator iter_; + +public: + const_iterator(); + const_iterator (const_iterator const&) = default; + const_iterator& operator= (const_iterator const&) = default; + bool operator== (const_iterator const& other) const; + bool operator!= (const_iterator const& other) const; + const_iterator& operator++(); + const_iterator& operator--(); + const_iterator operator++(int) const; + const_iterator operator--(int) const; + const_buffer operator*() const; + +private: + friend class chunk_encoded_buffers; + const_iterator(chunk_encoded_buffers const& buffers, bool past_the_end); +}; + +//------------------------------------------------------------------------------ + +template +chunk_encoded_buffers::chunk_encoded_buffers ( + Buffers const& buffers, bool final_chunk) + : buffers_(buffers) +{ + auto const size = boost::asio::buffer_size(buffers); + data_[data_.size() - 2] = '\r'; + data_[data_.size() - 1] = '\n'; + auto pos = to_hex(data_.begin(), data_.end() - 2, size); + head_ = const_buffer(&*pos, + std::distance(pos, data_.end())); + if (size > 0 && final_chunk) + tail_ = const_buffer("\r\n0\r\n\r\n", 7); + else + tail_ = const_buffer("\r\n", 2); +} + +template +template +typename std::enable_if< + std::is_unsigned::value, OutIter>::type +chunk_encoded_buffers::to_hex( + OutIter const first, OutIter const last, Unsigned n) +{ + assert(first != last); + OutIter iter = last; + if(n == 0) + { + *--iter = '0'; + return iter; + } + while(n) + { + assert(iter != first); + *--iter = "0123456789abcdef"[n&0xf]; + n>>=4; + } + return iter; +} + +template +chunk_encoded_buffers::const_iterator::const_iterator() + : buffers_(nullptr) + , where_(Where::end) +{ +} + +template +bool +chunk_encoded_buffers::const_iterator::operator==( + const_iterator const& other) const +{ + return buffers_ == other.buffers_ && + where_ == other.where_ && iter_ == other.iter_; +} + +template +bool +chunk_encoded_buffers::const_iterator::operator!=( + const_iterator const& other) const +{ + return buffers_ != other.buffers_ || + where_ != other.where_ || iter_ != other.iter_; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator++() -> + const_iterator& +{ + assert(buffers_); + assert(where_ != Where::end); + if (where_ == Where::head) + where_ = Where::input; + else if (iter_ != buffers_->buffers_.end()) + ++iter_; + else + where_ = Where::end; + return *this; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator--() -> + const_iterator& +{ + assert(buffers_); + assert(where_ != Where::head); + if (where_ == Where::end) + where_ = Where::input; + else if (iter_ != buffers_->buffers_.begin()) + --iter_; + else + where_ = Where::head; + return *this; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator++(int) const -> + const_iterator +{ + auto iter = *this; + ++iter; + return iter; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator--(int) const -> + const_iterator +{ + auto iter = *this; + --iter; + return iter; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator*() const -> + const_buffer +{ + assert(buffers_); + assert(where_ != Where::end); + if (where_ == Where::head) + return buffers_->head_; + if (iter_ != buffers_->buffers_.end()) + return *iter_; + return buffers_->tail_; +} + +template +chunk_encoded_buffers::const_iterator::const_iterator( + chunk_encoded_buffers const& buffers, bool past_the_end) + : buffers_(&buffers) + , where_(past_the_end ? Where::end : Where::head) + , iter_(past_the_end ? buffers_->buffers_.end() : + buffers_->buffers_.begin()) +{ +} + +} // detail + +/** Returns a chunk-encoded BufferSequence. + + See: + http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 + + @param buffers The input buffer sequence. + + @param final_chunk `true` If this should include a final-chunk. + + @return A chunk-encoded ConstBufferSequence representing the input. +*/ +template +#if GENERATING_DOCS +implementation_defined +#else +detail::chunk_encoded_buffers +#endif +chunk_encode(ConstBufferSequence const& buffers, + bool final_chunk = false) +{ + return detail::chunk_encoded_buffers< + ConstBufferSequence>{buffers, final_chunk}; +} + +/// Returns a chunked encoding final chunk. +inline +#if GENERATING_DOCS +implementation_defined +#else +boost::asio::const_buffers_1 +#endif +chunk_encode_final() +{ + return boost::asio::const_buffers_1( + "0\r\n\r\n", 5); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp new file mode 100644 index 00000000..27ee8e26 --- /dev/null +++ b/include/beast/http/detail/basic_parser.hpp @@ -0,0 +1,203 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +// '0'...'9' +inline +bool +is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +inline +bool +is_token(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 + }}; + return tab[static_cast(c)] != 0; +} + +inline +bool +is_text(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)] != 0; +} + +// converts to lower case, +// returns 0 if not a valid token char +// +inline +char +to_field_char(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 + }}; + return tab[static_cast(c)]; +} + +// converts to lower case, +// returns 0 if not a valid text char +// +inline +char +to_value_char(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 + }}; + return static_cast(tab[static_cast(c)]); +} + +inline +std::uint8_t +unhex(char c) +{ + static std::array constexpr tab = {{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 + }}; + return tab[static_cast(c)]; +}; + +template +struct parser_str_t +{ + static char constexpr close[6] = "close"; + static char constexpr chunked[8] = "chunked"; + static char constexpr keep_alive[11] = "keep-alive"; + + static char constexpr upgrade[8] = "upgrade"; + static char constexpr connection[11] = "connection"; + static char constexpr content_length[15] = "content-length"; + static char constexpr proxy_connection[17] = "proxy-connection"; + static char constexpr transfer_encoding[18] = "transfer-encoding"; +}; + +template +char constexpr +parser_str_t<_>::close[6]; + +template +char constexpr +parser_str_t<_>::chunked[8]; + +template +char constexpr +parser_str_t<_>::keep_alive[11]; + +template +char constexpr +parser_str_t<_>::upgrade[8]; + +template +char constexpr +parser_str_t<_>::connection[11]; + +template +char constexpr +parser_str_t<_>::content_length[15]; + +template +char constexpr +parser_str_t<_>::proxy_connection[17]; + +template +char constexpr +parser_str_t<_>::transfer_encoding[18]; + +using parser_str = parser_str_t<>; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/detail/write_preparation.hpp b/include/beast/http/detail/write_preparation.hpp new file mode 100644 index 00000000..13dd6f13 --- /dev/null +++ b/include/beast/http/detail/write_preparation.hpp @@ -0,0 +1,123 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_WRITE_PREPARATION_HPP +#define BEAST_HTTP_DETAIL_WRITE_PREPARATION_HPP + +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +class has_content_length_value +{ + template().content_length()), + std::size_t>> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + // `true` if `T` meets the requirements. + static bool const value = type::value; +}; + +// Determines if the writer can provide the content length +template +using has_content_length = + std::integral_constant::value>; + +template +struct write_preparation +{ + using headers_type = + basic_headers>; + + message const& msg; + typename Body::writer w; + streambuf sb; + bool chunked; + bool close; + + explicit + write_preparation( + message const& msg_) + : msg(msg_) + , w(msg) + { + } + + void + init(error_code& ec) + { + w.init(ec); + if(ec) + return; + // VFALCO TODO This implementation requires making a + // copy of the headers, we can do better. + // VFALCO Should we be using handler_alloc? + headers_type h(msg.headers.begin(), msg.headers.end()); + set_content_length(h, has_content_length< + typename Body::writer>{}); + + // VFALCO TODO Keep-Alive + + if(close) + { + if(msg.version >= 11) + h.insert("Connection", "close"); + } + else + { + if(msg.version < 11) + h.insert("Connection", "keep-alive"); + } + + msg.write_firstline(sb); + write_fields(sb, h); + beast::write(sb, "\r\n"); + } + +private: + void + set_content_length(headers_type& h, + std::true_type) + { + close = false; + chunked = false; + h.insert("Content-Length", w.content_length()); + } + + void + set_content_length(headers_type& h, + std::false_type) + { + if(msg.version >= 11) + { + close = false; + chunked = true; + h.insert("Transfer-Encoding", "chunked"); + } + else + { + close = true; + chunked = false; + } + } +}; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp new file mode 100644 index 00000000..11d2ff91 --- /dev/null +++ b/include/beast/http/empty_body.hpp @@ -0,0 +1,82 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_EMPTY_BODY_HPP +#define BEAST_HTTP_EMPTY_BODY_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** An empty content-body. +*/ +struct empty_body +{ +#if GENERATING_DOCS + /// The type of the `message::body` member + using value_type = void; +#else + struct value_type {}; +#endif + +#if GENERATING_DOCS +private: +#endif + + struct reader + { + template + explicit + reader(message&) + { + } + + void + write(void const*, std::size_t, error_code&) + { + } + }; + + struct writer + { + template + explicit + writer(message const& m) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return 0; + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(boost::asio::null_buffers{}); + return true; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/error.hpp b/include/beast/http/error.hpp new file mode 100644 index 00000000..d82d45bd --- /dev/null +++ b/include/beast/http/error.hpp @@ -0,0 +1,21 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_ERROR_HPP +#define BEAST_HTTP_ERROR_HPP + +#include + +namespace beast { +namespace http { + +using error_code = boost::system::error_code; + +} // http +} // beast + +#endif diff --git a/include/beast/http/headers.hpp b/include/beast/http/headers.hpp new file mode 100644 index 00000000..9b919071 --- /dev/null +++ b/include/beast/http/headers.hpp @@ -0,0 +1,26 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_HEADERS_HPP +#define BEAST_HTTP_HEADERS_HPP + +#include +#include + +namespace beast { +namespace http { + +template +using headers = basic_headers; + +using http_headers = + basic_headers>; + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/basic_headers.ipp b/include/beast/http/impl/basic_headers.ipp new file mode 100644 index 00000000..8880bd89 --- /dev/null +++ b/include/beast/http/impl/basic_headers.ipp @@ -0,0 +1,299 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_BASIC_HEADERS_IPP +#define BEAST_HTTP_IMPL_BASIC_HEADERS_IPP + +#include + +namespace beast { +namespace http { + +namespace detail { + +inline +auto +basic_headers_base::begin() const -> + const_iterator +{ + return list_.cbegin(); +} + +inline +auto +basic_headers_base::end() const -> + const_iterator +{ + return list_.cend(); +} + +inline +auto +basic_headers_base::cbegin() const -> + const_iterator +{ + return list_.cbegin(); +} + +inline +auto +basic_headers_base::cend() const -> + const_iterator +{ + return list_.cend(); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +basic_headers:: +delete_all() +{ + for(auto it = list_.begin(); it != list_.end();) + { + auto& e = *it++; + e.~element(); + alloc_traits::deallocate( + this->member(), &e, 1); + } +} + +template +inline +void +basic_headers:: +move_assign(basic_headers& other, std::false_type) +{ + if(this->member() != other.member()) + { + copy_from(other); + other.clear(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + } +} + +template +inline +void +basic_headers:: +move_assign(basic_headers& other, std::true_type) +{ + this->member() = std::move(other.member()); + set_ = std::move(other.set_); + list_ = std::move(other.list_); +} + +template +inline +void +basic_headers:: +copy_assign(basic_headers const& other, std::false_type) +{ + copy_from(other); +} + +template +inline +void +basic_headers:: +copy_assign(basic_headers const& other, std::true_type) +{ + this->member() = other.member(); + copy_from(other); +} + +//------------------------------------------------------------------------------ + +template +basic_headers:: +~basic_headers() +{ + delete_all(); +} + +template +basic_headers:: +basic_headers(Allocator const& alloc) + : beast::detail::empty_base_optimization< + alloc_type>(alloc) +{ +} + +template +basic_headers:: +basic_headers(basic_headers&& other) + : beast::detail::empty_base_optimization( + std::move(other.member())) + , detail::basic_headers_base( + std::move(other.set_), std::move(other.list_)) +{ + other.list_.clear(); + other.set_.clear(); +} + +template +auto +basic_headers:: +operator=(basic_headers&& other) -> + basic_headers& +{ + if(this == &other) + return *this; + clear(); + move_assign(other, std::integral_constant{}); + return *this; +} + +template +basic_headers:: +basic_headers(basic_headers const& other) + : basic_headers(alloc_traits:: + select_on_container_copy_construction(other.member())) +{ + copy_from(other); +} + +template +auto +basic_headers:: +operator=(basic_headers const& other) -> + basic_headers& +{ + clear(); + copy_assign(other, std::integral_constant{}); + return *this; +} + +template +template +basic_headers:: +basic_headers(basic_headers const& other) +{ + copy_from(other); +} + +template +template +auto +basic_headers:: +operator=(basic_headers const& other) -> + basic_headers& +{ + clear(); + copy_from(other); + return *this; +} + +template +template +basic_headers:: +basic_headers(FwdIt first, FwdIt last) +{ + for(;first != last; ++first) + insert(first->name(), first->value()); +} + +template +auto +basic_headers:: +find(boost::string_ref const& name) const -> + iterator +{ + auto const it = set_.find(name, less{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template +boost::string_ref +basic_headers:: +operator[](boost::string_ref const& name) const +{ + // VFALCO This none object looks sketchy + static boost::string_ref const none; + auto const it = find(name); + if(it == end()) + return none; + return it->second; +} + +template +void +basic_headers:: +clear() noexcept +{ + delete_all(); + list_.clear(); + set_.clear(); +} + +template +std::size_t +basic_headers:: +erase(boost::string_ref const& name) +{ + auto const it = set_.find(name, less{}); + if(it == set_.end()) + return 0; + auto& e = *it; + set_.erase(set_.iterator_to(e)); + list_.erase(list_.iterator_to(e)); + alloc_traits::deallocate(this->member(), &e, 1); + return 1; +} + +template +void +basic_headers:: +insert(boost::string_ref const& name, + boost::string_ref const& value) +{ + typename set_t::insert_commit_data d; + auto const result = + set_.insert_check(name, less{}, d); + if (result.second) + { + auto const p = alloc_traits::allocate( + this->member(), 1); + alloc_traits::construct( + this->member(), p, name, value); + list_.push_back(*p); + set_.insert_commit(*p, d); + return; + } + // If field already exists, insert comma + // separated value as per RFC2616 section 4.2 + auto& cur = result.first->data.second; + cur.reserve(cur.size() + 1 + value.size()); + cur.append(1, ','); + cur.append(value.data(), value.size()); +} + +template +void +basic_headers:: +replace(boost::string_ref const& name, + boost::string_ref const& value) +{ + erase(name); + insert(name, value); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp new file mode 100644 index 00000000..c63549f9 --- /dev/null +++ b/include/beast/http/impl/basic_parser.ipp @@ -0,0 +1,1074 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP +#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP + +namespace beast { +namespace http { + +template +bool +basic_parser:: +keep_alive() const +{ + if(http_major_ > 0 && http_minor_ > 0) + { + if(flags_ & parse_flag::connection_close) + return false; + } + else + { + if(! (flags_ & parse_flag::connection_keep_alive)) + return false; + } + return ! needs_eof(); +} + +// Implementation inspired by nodejs/http-parser + +template +std::size_t +basic_parser:: +write(void const* data, std::size_t size, error_code& ec) +{ + using beast::http::detail::is_digit; + using beast::http::detail::is_token; + using beast::http::detail::is_text; + using beast::http::detail::to_field_char; + using beast::http::detail::to_value_char; + using beast::http::detail::unhex; + + if(size == 0 && s_ != s_closed) + return 0; + + auto begin = + reinterpret_cast(data); + auto const end = begin + size; + auto p = begin; + auto used = [&] + { + return p - reinterpret_cast(data); + }; + auto err = [&](parse_error ev) + { + ec = ev; + s_ = s_closed; + return used(); + }; + auto piece = [&] + { + return boost::string_ref{ + begin, static_cast(p - begin)}; + }; + auto cb = [&](pmf_t next) + { + if(cb_ && p != begin) + { + (this->*cb_)(ec, piece()); + if(ec) + return true; // error + } + cb_ = next; + if(cb_) + begin = p; + return false; + }; + for(;p != end; ++p) + { + unsigned char ch = *p; + redo: + switch(s_) + { + case s_closed: + return err(parse_error::connection_closed); + break; + + case s_req_start: + flags_ = 0; + cb_ = nullptr; + content_length_ = no_content_length; + s_ = s_req_method_start; + goto redo; + + case s_req_method_start: + if(! is_token(ch)) + return err(parse_error::bad_method); + cb_ = &self::call_on_method; + s_ = s_req_method; + break; + + case s_req_method: + if(! is_token(ch)) + { + if(cb(nullptr)) + return used(); + s_ = s_req_space_before_url; + goto redo; + } + break; + + case s_req_space_before_url: + if(ch != ' ') + return err(parse_error::bad_request); + s_ = s_req_url_start; + break; + + case s_req_url_start: + if(ch == ' ') + return err(parse_error::bad_uri); + // VFALCO TODO Require valid URL character + if(cb(&self::call_on_uri)) + return used(); + s_ = s_req_url; + break; + + case s_req_url: + if(ch == ' ') + { + if(cb(nullptr)) + return used(); + s_ = s_req_http_start; + break; + } + // VFALCO TODO Require valid URL character + break; + + case s_req_http_start: + if(ch != 'H') + return err(parse_error::bad_version); + s_ = s_req_http_H; + break; + + case s_req_http_H: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_req_http_HT; + break; + + case s_req_http_HT: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_req_http_HTT; + break; + + case s_req_http_HTT: + if(ch != 'P') + return err(parse_error::bad_version); + s_ = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + if(ch != '/') + return err(parse_error::bad_version); + s_ = s_req_major_start; + break; + + case s_req_major_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_req_major; + break; + + case s_req_major: + if(ch == '.') + { + s_ = s_req_minor_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = 10 * http_major_ + ch - '0'; + if(http_major_ > 999) + return err(parse_error::bad_version); + break; + + case s_req_minor_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_req_minor; + break; + + case s_req_minor: + if(ch == '\r') + { + s_ = s_req_line_end; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = 10 * http_minor_ + ch - '0'; + if(http_minor_ > 999) + return err(parse_error::bad_version); + break; + + case s_req_line_end: + if(ch != '\n') + return err(parse_error::bad_crlf); + call_on_request(ec); + if(ec) + return used(); + s_ = s_header_field_start; + break; + + //-------------------------------------------- + + case s_res_start: + flags_ = 0; + cb_ = nullptr; + content_length_ = no_content_length; + switch(ch) + { + case 'H': s_ = s_res_H; break; + case '\r': + case '\n': + break; + default: + return err(parse_error::bad_version); + } + break; + + case s_res_H: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_res_HT; + break; + + case s_res_HT: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_res_HTT; + break; + + case s_res_HTT: + if(ch != 'P') + return err(parse_error::bad_version); + s_ = s_res_HTTP; + break; + + case s_res_HTTP: + if(ch != '/') + return err(parse_error::bad_version); + s_ = s_res_major_start; + break; + + case s_res_major_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_res_major; + break; + + case s_res_major: + if(ch == '.') + { + s_ = s_res_minor_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = 10 * http_major_ + ch - '0'; + if(http_major_ > 999) + return err(parse_error::bad_version); + break; + + case s_res_minor_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_res_minor; + break; + + case s_res_minor: + if(ch == ' ') + { + s_ = s_res_status_code_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = 10 * http_minor_ + ch - '0'; + if(http_minor_ > 999) + return err(parse_error::bad_version); + break; + + case s_res_status_code_start: + if(! is_digit(ch)) + { + if(ch == ' ') + break; + return err(parse_error::bad_status_code); + } + status_code_ = ch - '0'; + s_ = s_res_status_code; + break; + + case s_res_status_code: + if(! is_digit(ch)) + { + switch(ch) + { + case ' ': s_ = s_res_status_start; break; + case '\r': s_ = s_res_line_almost_done; break; + case '\n': s_ = s_header_field_start; break; + default: + return err(parse_error::bad_status_code); + } + break; + } + status_code_ = status_code_ * 10 + ch - '0'; + if(status_code_ > 999) + return err(parse_error::bad_status_code); + break; + + case s_res_status_start: + if(ch == '\r') + { + s_ = s_res_line_almost_done; + break; + } + if(ch == '\n') + { + s_ = s_header_field_start; + break; + } + if(cb(&self::call_on_reason)) + return used(); + pos_ = 0; + s_ = s_res_status; + break; + + case s_res_status: + if(ch == '\r') + { + if(cb(nullptr)) + return used(); + s_ = s_res_line_almost_done; + break; + } + if(ch == '\n') + { + if(cb(nullptr)) + return used(); + s_ = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_res_line_done; + break; + + case s_res_line_done: + call_on_response(ec); + if(ec) + return used(); + s_ = s_header_field_start; + goto redo; + + //-------------------------------------------- + + // message-header = field-name ":" [ field-value ] + // field-name = token + + case s_header_field_start: + { + if(ch == '\r') + { + s_ = s_headers_almost_done; + break; + } + auto c = to_field_char(ch); + if(! c) + return err(parse_error::bad_field); + switch(c) + { + case 'c': pos_ = 0; fs_ = h_C; break; + case 'p': pos_ = 0; fs_ = h_matching_proxy_connection; break; + case 't': pos_ = 0; fs_ = h_matching_transfer_encoding; break; + case 'u': pos_ = 0; fs_ = h_matching_upgrade; break; + default: + fs_ = h_general; + break; + } + if(cb(&self::call_on_field)) + return used(); + s_ = s_header_field; + break; + } + + case s_header_field: + { + for(; p != end; ch = *++p) + { + auto c = to_field_char(ch); + if(! c) + break; + switch(fs_) + { + case h_general: break; + case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; + case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; + case h_CON: + ++pos_; + switch(c) + { + case 'n': fs_ = h_matching_connection; break; + case 't': fs_ = h_matching_content_length; break; + default: + fs_ = h_general; + } + break; + + case h_matching_connection: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::connection)-1 || + c != detail::parser_str::connection[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::connection)-2) + fs_ = h_connection; + break; + + case h_matching_proxy_connection: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::proxy_connection)-1 || + c != detail::parser_str::proxy_connection[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) + fs_ = h_connection; + break; + + case h_matching_content_length: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::content_length)-1 || + c != detail::parser_str::content_length[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::content_length)-2) + { + if(flags_ & parse_flag::contentlength) + return err(parse_error::bad_content_length); + fs_ = h_content_length; + flags_ |= parse_flag::contentlength; + } + break; + + case h_matching_transfer_encoding: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::transfer_encoding)-1 || + c != detail::parser_str::transfer_encoding[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) + fs_ = h_transfer_encoding; + break; + + case h_matching_upgrade: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || + c != detail::parser_str::upgrade[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_upgrade; + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + // VFALCO Do we allow a space here? + fs_ = h_general; + break; + default: + break; + } + } + if(p == end) + { + --p; + break; + } + if(ch == ':') + { + if(cb(nullptr)) + return used(); + s_ = s_header_value_start; + break; + } + return err(parse_error::bad_field); + } + + // field-value = *( field-content | LWS ) + // field-content = *TEXT + // LWS = [CRLF] 1*( SP | HT ) + + case s_header_value_start: + if(ch == '\r') + { + s_ = s_header_value_discard_lWs0; + break; + } + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws0; + break; + } + s_ = s_header_value_text_start; + goto redo; + + case s_header_value_discard_ws0: + if(ch == ' ' || ch == '\t') + break; + if(ch == '\r') + { + s_ = s_header_value_discard_lWs0; + break; + } + s_ = s_header_value_text_start; + goto redo; + + case s_header_value_discard_lWs0: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value_almost_done0; + break; + + case s_header_value_almost_done0: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws0; + break; + } + call_on_value(ec, boost::string_ref{"", 0}); + if(ec) + return used(); + s_ = s_header_field_start; + goto redo; + + case s_header_value_text_start: + { + auto const c = to_value_char(ch); + if(! c) + return err(parse_error::bad_value); + switch(fs_) + { + case h_upgrade: + flags_ |= parse_flag::upgrade; + fs_ = h_general; + break; + + case h_transfer_encoding: + if(c == 'c') + fs_ = h_matching_transfer_encoding_chunked; + else + fs_ = h_general; + break; + + case h_content_length: + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + content_length_ = ch - '0'; + break; + + case h_connection: + switch(c) + { + case 'k': fs_ = h_matching_connection_keep_alive; break; + case 'c': fs_ = h_matching_connection_close; break; + case 'u': fs_ = h_matching_connection_upgrade; break; + default: + fs_ = h_matching_connection_token; + break; + } + break; + + case h_matching_connection_token_start: + break; + + default: + fs_ = h_general; + break; + } + pos_ = 0; + if(cb(&self::call_on_value)) + return used(); + s_ = s_header_value_text; + break; + } + + case s_header_value_text: + { + for(; p != end; ch = *++p) + { + if(ch == '\r') + { + if(cb(nullptr)) + return used(); + s_ = s_header_value_discard_lWs; + break; + } + auto const c = to_value_char(ch); + if(! c) + return err(parse_error::bad_value); + switch(fs_) + { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0); + break; + + case h_content_length: + if(ch == ' ' || ch == '\t') + break; + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + if(content_length_ > (no_content_length - 10) / 10) + return err(parse_error::bad_content_length); + content_length_ = + content_length_ * 10 + ch - '0'; + break; + + case h_matching_transfer_encoding_chunked: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::chunked)-1 || + c != detail::parser_str::chunked[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::chunked)-2) + fs_ = h_transfer_encoding_chunked; + break; + + case h_matching_connection_token_start: + switch(c) + { + case 'k': fs_ = h_matching_connection_keep_alive; break; + case 'c': fs_ = h_matching_connection_close; break; + case 'u': fs_ = h_matching_connection_upgrade; break; + default: + if(is_token(c)) + fs_ = h_matching_connection_token; + else if(ch == ' ' || ch == '\t') + { } + else + fs_ = h_general; + break; + } + break; + + case h_matching_connection_keep_alive: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::keep_alive)-1 || + c != detail::parser_str::keep_alive[pos_]) + fs_ = h_matching_connection_token; + else if (pos_ == sizeof(detail::parser_str::keep_alive)- 2) + fs_ = h_connection_keep_alive; + break; + + case h_matching_connection_close: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::close)-1 || + c != detail::parser_str::close[pos_]) + fs_ = h_matching_connection_token; + else if(pos_ == sizeof(detail::parser_str::close)-2) + fs_ = h_connection_close; + break; + + case h_matching_connection_upgrade: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || + c != detail::parser_str::upgrade[pos_]) + fs_ = h_matching_connection_token; + else if (pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_connection_upgrade; + break; + + case h_matching_connection_token: + if(ch == ',') + { + fs_ = h_matching_connection_token_start; + pos_ = 0; + } + break; + + case h_transfer_encoding_chunked: + if(ch != ' ' && ch != '\t') + fs_ = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if(ch ==',') + { + if(fs_ == h_connection_keep_alive) + flags_ |= parse_flag::connection_keep_alive; + else if(fs_ == h_connection_close) + flags_ |= parse_flag::connection_close; + else if(fs_ == h_connection_upgrade) + flags_ |= parse_flag::connection_upgrade; + fs_ = h_matching_connection_token_start; + pos_ = 0; + } + else if(ch != ' ' && ch != '\t') + { + fs_ = h_matching_connection_token; + } + break; + default: + break; + } + } + if(p == end) + --p; + break; + } + + case s_header_value_discard_ws: + if(ch == ' ' || ch == '\t') + break; + if(ch == '\r') + { + s_ = s_header_value_discard_lWs; + break; + } + if(! is_text(ch)) + return err(parse_error::bad_value); + call_on_value(ec, boost::string_ref(" ", 1)); + if(ec) + return used(); + if(cb(&self::call_on_value)) + return used(); + s_ = s_header_value_text; + break; + + case s_header_value_discard_lWs: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value_almost_done; + break; + + case s_header_value_almost_done: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws; + break; + } + switch(fs_) + { + case h_connection_keep_alive: flags_ |= parse_flag::connection_keep_alive; break; + case h_connection_close: flags_ |= parse_flag::connection_close; break; + case h_transfer_encoding_chunked: flags_ |= parse_flag::chunked; break; + case h_connection_upgrade: flags_ |= parse_flag::connection_upgrade; break; + default: + break; + } + s_ = s_header_field_start; + goto redo; + + case s_headers_almost_done: + { + if(ch != '\n') + return err(parse_error::bad_crlf); + if(flags_ & parse_flag::trailing) + { + //if(cb(&self::call_on_chunk_complete)) return used(); + s_ = s_complete; + goto redo; + } + if((flags_ & parse_flag::chunked) && (flags_ & parse_flag::contentlength)) + return err(parse_error::illegal_content_length); + upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) == + (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; + auto const maybe_skip = call_on_headers(ec); + if(ec) + return used(); + switch(maybe_skip) + { + case 0: break; + case 2: upgrade_ = true; // fall through + case 1: flags_ |= parse_flag::skipbody; break; + default: + return err(parse_error::bad_on_headers_rv); + } + s_ = s_headers_done; + goto redo; + } + + case s_headers_done: + { + assert(! cb_); + call_on_headers(ec); + if(ec) + return used(); + bool const hasBody = + (flags_ & parse_flag::chunked) || (content_length_ > 0 && + content_length_ != no_content_length); + if(upgrade_ && (/*method == "connect" ||*/ (flags_ & parse_flag::skipbody) || ! hasBody)) + { + s_ = s_complete; + } + else if((flags_ & parse_flag::skipbody) || content_length_ == 0) + { + s_ = s_complete; + } + else if(flags_ & parse_flag::chunked) + { + s_ = s_chunk_size_start; + break; + } + else if(content_length_ == 0) + { + s_ = s_complete; + } + else if(content_length_ != no_content_length) + { + s_ = s_body_identity0; + break; + } + else if(! needs_eof()) + { + s_ = s_complete; + } + else + { + s_ = s_body_identity_eof0; + break; + } + goto redo; + } + + case s_body_identity0: + if(cb(&self::call_on_body)) + return used(); + s_ = s_body_identity; + goto redo; // VFALCO fall through? + + case s_body_identity: + { + std::size_t n; + if(static_cast((end - p)) < content_length_) + n = end - p; + else + n = static_cast(content_length_); + assert(content_length_ != 0 && content_length_ != no_content_length); + content_length_ -= n; + if(content_length_ == 0) + { + p += n - 1; + s_ = s_complete; + goto redo; // ???? + } + p += n - 1; + break; + } + + case s_body_identity_eof0: + if(cb(&self::call_on_body)) + return used(); + s_ = s_body_identity_eof; + goto redo; // VFALCO fall through? + + case s_body_identity_eof: + p = end - 1; + break; + + case s_chunk_size_start: + { + auto v = unhex(ch); + if(v == -1) + return err(parse_error::invalid_chunk_size); + content_length_ = v; + s_ = s_chunk_size; + break; + } + case s_chunk_size: + { + if(ch == '\r') + { + s_ = s_chunk_size_almost_done; + break; + } + auto v = unhex(ch); + if(v == -1) + { + if(ch == ';' || ch == ' ') + { + s_ = s_chunk_parameters; + break; + } + return err(parse_error::invalid_chunk_size); + } + if(content_length_ > (no_content_length - 16) / 16) + return err(parse_error::bad_content_length); + content_length_ = + content_length_ * 16 + v; + break; + } + + case s_chunk_parameters: + if(ch == '\r') + { + s_ = s_chunk_size_almost_done; + break; + } + break; + + case s_chunk_size_almost_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + nread_ = 0; + if(content_length_ == 0) + { + flags_ |= parse_flag::trailing; + s_ = s_header_field_start; + break; + } + //call_chunk_header(ec); if(ec) return used(); + s_ = s_chunk_data_start; + break; + + case s_chunk_data_start: + if(cb(&self::call_on_body)) + return used(); + s_ = s_chunk_data; + goto redo; // VFALCO fall through? + + case s_chunk_data: + { + std::size_t n; + if(static_cast((end - p)) < content_length_) + n = end - p; + else + n = static_cast(content_length_); + content_length_ -= n; + p += n - 1; + if(content_length_ == 0) + s_ = s_chunk_data_almost_done; + break; + } + + case s_chunk_data_almost_done: + if(ch != '\r') + return err(parse_error::bad_crlf); + if(cb(nullptr)) + return used(); + s_ = s_chunk_data_done; + break; + + case s_chunk_data_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + nread_ = 0; + s_ = s_chunk_size_start; + break; + + case s_complete: + ++p; + if(cb(nullptr)) + return used(); + call_on_complete(ec); + if(ec) + return used(); + s_ = s_restart; + return used(); + + case s_restart: + if(keep_alive()) + init(std::integral_constant{}); + else + s_ = s_closed; + goto redo; + } + } + if(cb_) + { + (this->*cb_)(ec, piece()); + if(ec) + return used(); + } + return used(); +} + +template +template +std::size_t +basic_parser:: +write(ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_ConstBufferSequence::value, + "ConstBufferSequence requirements not met"); + std::size_t used = 0; + for(auto const& buffer : buffers) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + used += write(buffer_cast(buffer), + buffer_size(buffer), ec); + if(ec) + break; + } + return used; +} + +template +void +basic_parser:: +write_eof(error_code& ec) +{ + switch(s_) + { + case s_body_identity_eof: + cb_ = nullptr; + call_on_complete(ec); + if(ec) + return; + return; + default: + break; + } + ec = parse_error::short_read; + s_ = s_closed; +} + +template +bool +basic_parser:: +needs_eof(std::true_type) const +{ + return false; +} + +template +bool +basic_parser:: +needs_eof(std::false_type) const +{ + // See RFC 2616 section 4.4 + if (status_code_ / 100 == 1 || // 1xx e.g. Continue + status_code_ == 204 || // No Content + status_code_ == 304 || // Not Modified + flags_ & parse_flag::skipbody) // response to a HEAD request + { + return false; + } + + if((flags_ & parse_flag::chunked) || content_length_ != no_content_length) + { + return false; + } + + return true; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp new file mode 100644 index 00000000..0f89cc4a --- /dev/null +++ b/include/beast/http/impl/message.ipp @@ -0,0 +1,318 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_MESSAGE_IPP +#define BEAST_HTTP_IMPL_MESSAGE_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +message:: +message() +{ +} + +template +message:: +message(request_params params) +{ + static_assert(isRequest, "message is not a request"); + this->method = params.method; + this->url = std::move(params.url); + version = params.version; +} + +template +message:: +message(response_params params) +{ + static_assert(! isRequest, "message is not a response"); + this->status = params.status; + this->reason = std::move(params.reason); + version = params.version; +} + +template +template +void +message:: +write_firstline(Streambuf& streambuf, + std::true_type) const +{ + write(streambuf, to_string(this->method)); + write(streambuf, " "); + write(streambuf, this->url); + switch(version) + { + case 10: + write(streambuf, " HTTP/1.0\r\n"); + break; + case 11: + write(streambuf, " HTTP/1.1\r\n"); + break; + default: + write(streambuf, " HTTP/"); + write(streambuf, version / 10); + write(streambuf, "."); + write(streambuf, version % 10); + write(streambuf, "\r\n"); + break; + } +} + +template +template +void +message:: +write_firstline(Streambuf& streambuf, + std::false_type) const +{ + switch(version) + { + case 10: + write(streambuf, "HTTP/1.0 "); + break; + case 11: + write(streambuf, "HTTP/1.1 "); + break; + default: + write(streambuf, " HTTP/"); + write(streambuf, version / 10); + write(streambuf, "."); + write(streambuf, version % 10); + write(streambuf, " "); + break; + } + write(streambuf, this->status); + write(streambuf, " "); + write(streambuf, this->reason); + write(streambuf, "\r\n"); +} + +namespace detail { + +template +std::string +buffers_to_string(ConstBufferSequence const& buffers) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(buffers)); + for(auto const& b : buffers) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +class writef_ostream +{ + std::ostream& os_; + bool chunked_; + +public: + writef_ostream(std::ostream& os, bool chunked) + : os_(os) + , chunked_(chunked) + { + } + + template + void + operator()(ConstBufferSequence const& buffers) + { + if(chunked_) + os_ << buffers_to_string( + chunk_encode(buffers)); + else + os_ << buffers_to_string( + buffers); + } +}; + +} // detail + +// Diagnostic output only +template +std::ostream& +operator<<(std::ostream& os, + message const& msg) +{ + error_code ec; + detail::write_preparation wp(msg); + wp.init(ec); + if(ec) + return os; + std::mutex m; + std::condition_variable cv; + bool ready = false; + resume_context resume{ + [&] + { + std::lock_guard lock(m); + ready = true; + cv.notify_one(); + }}; + auto copy = resume; + os << detail::buffers_to_string(wp.sb.data()); + wp.sb.consume(wp.sb.size()); + detail::writef_ostream writef(os, wp.chunked); + for(;;) + { + { + auto result = wp.w(std::move(copy), ec, writef); + if(ec) + return os; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + wp.sb.consume(wp.sb.size()); + for(;;) + { + auto result = wp.w(std::move(copy), ec, writef); + if(ec) + return os; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + } + if(wp.chunked) + { + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from using coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + os << detail::buffers_to_string(chunk_encode_final()); + if(ec) + return os; + } + os << std::endl; + return os; +} + +//------------------------------------------------------------------------------ + +template +void +set_connection(bool keep_alive, + message& req) +{ + if(req.version >= 11) + { + if(! keep_alive) + req.headers.replace("Connection", "close"); + else + req.headers.erase("Connection"); + } + else + { + if(keep_alive) + req.headers.replace("Connection", "keep-alive"); + else + req.headers.erase("Connection"); + } +} + +template +void +set_connection(bool keep_alive, + message& resp, + message const& req) +{ + if(req.version >= 11) + { + if(rfc2616::token_in_list(req["Connection"], "close")) + keep_alive = false; + } + else + { + if(! rfc2616::token_in_list(req["Connection"], "keep-alive")) + keep_alive = false; + } + set_connection(keep_alive, resp); +} + +template +void +write_fields(Streambuf& streambuf, FieldSequence const& fields) +{ + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + //static_assert(is_FieldSequence::value, + // "FieldSequence requirements not met"); + for(auto const& field : fields) + { + write(streambuf, field.name()); + write(streambuf, ": "); + write(streambuf, field.value()); + write(streambuf, "\r\n"); + } +} + +template +bool +is_keep_alive(message const& msg) +{ + if(msg.version >= 11) + { + if(rfc2616::token_in_list( + msg.headers["Connection"], "close")) + return false; + return true; + } + if(rfc2616::token_in_list( + msg.headers["Connection"], "keep-alive")) + return true; + return false; +} + +template +bool +is_upgrade(message const& msg) +{ + if(msg.version < 11) + return false; + if(rfc2616::token_in_list( + msg.headers["Connection"], "upgrade")) + return true; + return false; +} + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp new file mode 100644 index 00000000..19191ac4 --- /dev/null +++ b/include/beast/http/impl/read.ipp @@ -0,0 +1,286 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_READ_IPP_HPP +#define BEAST_HTTP_IMPL_READ_IPP_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +class read_op +{ + using alloc_type = + handler_alloc; + + using parser_type = + parser; + + using message_type = + message; + + struct data + { + Stream& s; + Streambuf& sb; + message_type& m; + parser_type p; + Handler h; + bool started = false; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, Stream& s_, + Streambuf& sb_, message_type& m_) + : s(s_) + , sb(sb_) + , m(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, Stream&s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +read_op:: +operator()(error_code ec, std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(d.state != 99) + { + switch(d.state) + { + case 0: + { + auto const used = + d.p.write(d.sb.data(), ec); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + if(used > 0) + d.started = true; + d.sb.consume(used); + if(d.p.complete()) + { + // call handler + d.state = 99; + d.m = d.p.release(); + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + d.state = 1; + break; + } + + case 1: + // read + d.state = 2; + d.s.async_read_some(d.sb.prepare( + read_size_helper(d.sb, 65536)), + std::move(*this)); + return; + + // got data + case 2: + { + if(ec == boost::asio::error::eof) + { + if(! d.started) + { + // call handler + d.state = 99; + break; + } + // Caller will see eof on next read. + ec = {}; + d.p.write_eof(ec); + if(! ec) + { + assert(d.p.complete()); + d.m = d.p.release(); + } + // call handler + d.state = 99; + break; + } + if(ec) + { + // call handler + d.state = 99; + break; + } + d.sb.commit(bytes_transferred); + d.sb.consume(d.p.write(d.sb.data(), ec)); + if(ec) + { + // call handler + d.state = 99; + break; + } + if(d.p.complete()) + { + // call handler + d.state = 99; + d.m = d.p.release(); + break; + } + d.state = 1; + break; + } + } + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& m, + error_code& ec) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + parser p; + bool started = false; + for(;;) + { + auto used = + p.write(streambuf.data(), ec); + if(ec) + return; + streambuf.consume(used); + if(used > 0) + started = true; + if(p.complete()) + { + m = p.release(); + break; + } + streambuf.commit(stream.read_some( + streambuf.prepare(read_size_helper( + streambuf, 65536)), ec)); + if(ec && ec != boost::asio::error::eof) + return; + if(ec == boost::asio::error::eof) + { + if(! started) + return; + // Caller will see eof on next read. + ec = {}; + p.write_eof(ec); + if(ec) + return; + assert(p.complete()); + m = p.release(); + break; + } + } +} + +template +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read(AsyncReadStream& stream, Streambuf& streambuf, + message& m, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion completion(handler); + detail::read_op{completion.handler, + stream, streambuf, m}; + return completion.result.get(); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp new file mode 100644 index 00000000..f1640c02 --- /dev/null +++ b/include/beast/http/impl/write.ipp @@ -0,0 +1,456 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_WRITE_IPP +#define BEAST_HTTP_IMPL_WRITE_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +class write_op +{ + using alloc_type = + handler_alloc; + + struct data + { + Stream& s; + // VFALCO How do we use handler_alloc in write_preparation? + write_preparation< + isRequest, Body, Headers> wp; + Handler h; + resume_context resume; + resume_context copy; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, Stream& s_, + message const& m_) + : s(s_) + , wp(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + class writef0 + { + write_op& self_; + + public: + explicit + writef0(write_op& self) + : self_(self) + { + } + + template + void operator()(ConstBufferSequence const& buffers) + { + auto& d = *self_.d_; + // write headers and body + if(d.wp.chunked) + boost::asio::async_write(d.s, + buffer_cat(d.wp.sb.data(), + chunk_encode(buffers)), + std::move(self_)); + else + boost::asio::async_write(d.s, + buffer_cat(d.wp.sb.data(), + buffers), std::move(self_)); + } + }; + + class writef + { + write_op& self_; + + public: + explicit + writef(write_op& self) + : self_(self) + { + } + + template + void operator()(ConstBufferSequence const& buffers) + { + auto& d = *self_.d_; + // write body + if(d.wp.chunked) + boost::asio::async_write(d.s, + chunk_encode(buffers), + std::move(self_)); + else + boost::asio::async_write(d.s, + buffers, std::move(self_)); + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + auto& d = *d_; + auto sp = d_; + d.resume = { + [sp]() mutable + { + write_op self(std::move(sp)); + self.d_->cont = false; + auto& ios = self.d_->s.get_io_service(); + ios.dispatch(bind_handler(std::move(self), + error_code{}, 0, false)); + }}; + d.copy = d.resume; + (*this)(error_code{}, 0, false); + } + + explicit + write_op(std::shared_ptr d) + : d_(std::move(d)) + { + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +write_op:: +operator()(error_code ec, std::size_t, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + d.wp.init(ec); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post(bind_handler( + std::move(*this), ec, 0, false)); + return; + } + d.state = 1; + break; + } + + case 1: + { + auto const result = d.wp.w( + std::move(d.copy), ec, writef0{*this}); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post(bind_handler( + std::move(*this), ec, false)); + return; + } + if(boost::indeterminate(result)) + { + // suspend + d.copy = d.resume; + return; + } + if(result) + d.state = d.wp.chunked ? 4 : 5; + else + d.state = 2; + return; + } + + // sent headers and body + case 2: + d.wp.sb.consume(d.wp.sb.size()); + d.state = 3; + break; + + case 3: + { + auto const result = d.wp.w( + std::move(d.copy), ec, writef{*this}); + if(ec) + { + // call handler + d.state = 99; + break; + } + if(boost::indeterminate(result)) + { + // suspend + d.copy = d.resume; + return; + } + if(result) + d.state = d.wp.chunked ? 4 : 5; + else + d.state = 2; + return; + } + + case 4: + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + d.state = 5; + boost::asio::async_write(d.s, + chunk_encode_final(), std::move(*this)); + return; + + case 5: + if(d.wp.close) + { + // VFALCO TODO Decide on an error code + ec = boost::asio::error::eof; + } + d.state = 99; + break; + } + } + d.h(ec); + d.resume = {}; + d.copy = {}; +} + +template +class writef0_write +{ + Streambuf const& sb_; + SyncWriteStream& stream_; + bool chunked_; + error_code& ec_; + +public: + writef0_write(SyncWriteStream& stream, + Streambuf const& sb, bool chunked, error_code& ec) + : sb_(sb) + , stream_(stream) + , chunked_(chunked) + , ec_(ec) + { + } + + template + void operator()(ConstBufferSequence const& buffers) + { + // write headers and body + if(chunked_) + boost::asio::write(stream_, buffer_cat( + sb_.data(), chunk_encode(buffers)), ec_); + else + boost::asio::write(stream_, buffer_cat( + sb_.data(), buffers), ec_); + } +}; + +template +class writef_write +{ + SyncWriteStream& stream_; + bool chunked_; + error_code& ec_; + +public: + writef_write(SyncWriteStream& stream, + bool chunked, error_code& ec) + : stream_(stream) + , chunked_(chunked) + , ec_(ec) + { + } + + template + void operator()(ConstBufferSequence const& buffers) + { + // write body + if(chunked_) + boost::asio::write(stream_, + chunk_encode(buffers), ec_); + else + boost::asio::write(stream_, buffers, ec_); + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +template +void +write(SyncWriteStream& stream, + message const& msg, + boost::system::error_code& ec) +{ + detail::write_preparation wp(msg); + wp.init(ec); + if(ec) + return; + std::mutex m; + std::condition_variable cv; + bool ready = false; + resume_context resume{ + [&] + { + std::lock_guard lock(m); + ready = true; + cv.notify_one(); + }}; + auto copy = resume; + for(;;) + { + { + auto result = wp.w(std::move(copy), ec, + detail::writef0_write{ + stream, wp.sb, wp.chunked, ec}); + if(ec) + return; + if(result) + break; + if(boost::indeterminate(result)) + { + boost::asio::write(stream, wp.sb.data(), ec); + if(ec) + return; + wp.sb.consume(wp.sb.size()); + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + wp.sb.consume(wp.sb.size()); + for(;;) + { + auto result = wp.w(std::move(copy), ec, + detail::writef_write{ + stream, wp.chunked, ec}); + if(ec) + return; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + } + if(wp.chunked) + { + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from using coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + boost::asio::write(stream, chunk_encode_final(), ec); + if(ec) + return; + } + if(wp.close) + { + // VFALCO TODO Decide on an error code + ec = boost::asio::error::eof; + } +} + +template +typename async_completion< + WriteHandler, void(error_code)>::result_type +async_write(AsyncWriteStream& stream, + message const& msg, + WriteHandler&& handler) +{ + static_assert( + is_AsyncWriteStream::value, + "AsyncWriteStream requirements not met"); + beast::async_completion completion(handler); + detail::write_op{completion.handler, stream, msg}; + return completion.result.get(); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp new file mode 100644 index 00000000..33e7bb1c --- /dev/null +++ b/include/beast/http/message.hpp @@ -0,0 +1,174 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_MESSAGE_HPP +#define BEAST_HTTP_MESSAGE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +struct request_fields +{ + http::method_t method; + std::string url; +}; + +struct response_fields +{ + int status; + std::string reason; +}; + +} // detail + +#if ! GENERATING_DOCS + +struct request_params +{ + http::method_t method; + std::string url; + int version; +}; + +struct response_params +{ + int status; + std::string reason; + int version; +}; + +#endif + +/** A HTTP message. + + A message can be a request or response, depending on the `isRequest` + template argument value. Requests and responses have different types, + so functions may be overloaded on them if desired. + + The `Body` template argument type determines the model used + to read or write the content body of the message. + + @tparam isRequest `true` if this is a request. + + @tparam Body A type meeting the requirements of Body. + + @tparam Headers A type meeting the requirements of Headers. +*/ +template +struct message + : std::conditional::type +{ + /** The trait type characterizing the body. + + The body member will be of type body_type::value_type. + */ + using body_type = Body; + using headers_type = Headers; + + using is_request = + std::integral_constant; + + int version; // 10 or 11 + headers_type headers; + typename Body::value_type body; + + message(); + message(message&&) = default; + message(message const&) = default; + message& operator=(message&&) = default; + message& operator=(message const&) = default; + + /** Construct a HTTP request. + */ + explicit + message(request_params params); + + /** Construct a HTTP response. + */ + explicit + message(response_params params); + + /// Serialize the request or response line to a Streambuf. + template + void + write_firstline(Streambuf& streambuf) const + { + write_firstline(streambuf, + std::integral_constant{}); + } + + /// Diagnostics only + template + friend + std::ostream& + operator<<(std::ostream& os, + message const& m); + +private: + template + void + write_firstline(Streambuf& streambuf, + std::true_type) const; + + template + void + write_firstline(Streambuf& streambuf, + std::false_type) const; +}; + +#if ! GENERATING_DOCS + +/// A typical HTTP request +template>> +using request = message; + +/// A typical HTTP response +template>> +using response = message; + +#endif + +// For diagnostic output only +template +std::ostream& +operator<<(std::ostream& os, + message const& m); + +/// Write a FieldSequence to a Streambuf. +template +void +write_fields(Streambuf& streambuf, FieldSequence const& fields); + +/// Returns `true` if a message indicates a keep alive +template +bool +is_keep_alive(message const& msg); + +/// Returns `true` if a message indicates a HTTP Upgrade request or response +template +bool +is_upgrade(message const& msg); + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/method.hpp b/include/beast/http/method.hpp new file mode 100644 index 00000000..0c1acf04 --- /dev/null +++ b/include/beast/http/method.hpp @@ -0,0 +1,179 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_METHOD_HPP +#define BEAST_HTTP_METHOD_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class method_t +{ + http_delete, + http_get, + http_head, + http_post, + http_put, + + // pathological + http_connect, + http_options, + http_trace, + + // webdav + http_copy, + http_lock, + http_mkcol, + http_move, + http_propfind, + http_proppatch, + http_search, + http_unlock, + http_bind, + http_rebind, + http_unbind, + http_acl, + + // subversion + http_report, + http_mkactivity, + http_checkout, + http_merge, + + // upnp + http_msearch, + http_notify, + http_subscribe, + http_unsubscribe, + + // RFC-5789 + http_patch, + http_purge, + + // CalDav + http_mkcalendar, + + // RFC-2068, section 19.6.1.2 + http_link, + http_unlink +}; + +template +std::string +to_string(method_t m) +{ + switch(m) + { + case method_t::http_delete: return "DELETE"; + case method_t::http_get: return "GET"; + case method_t::http_head: return "HEAD"; + case method_t::http_post: return "POST"; + case method_t::http_put: return "PUT"; + + case method_t::http_connect: return "CONNECT"; + case method_t::http_options: return "OPTIONS"; + case method_t::http_trace: return "TRACE"; + + case method_t::http_copy: return "COPY"; + case method_t::http_lock: return "LOCK"; + case method_t::http_mkcol: return "MKCOL"; + case method_t::http_move: return "MOVE"; + case method_t::http_propfind: return "PROPFIND"; + case method_t::http_proppatch: return "PROPPATCH"; + case method_t::http_search: return "SEARCH"; + case method_t::http_unlock: return "UNLOCK"; + + case method_t::http_report: return "REPORT"; + case method_t::http_mkactivity: return "MKACTIVITY"; + case method_t::http_checkout: return "CHECKOUT"; + case method_t::http_merge: return "MERGE"; + + case method_t::http_msearch: return "MSEARCH"; + case method_t::http_notify: return "NOTIFY"; + case method_t::http_subscribe: return "SUBSCRIBE"; + case method_t::http_unsubscribe: return "UNSUBSCRIBE"; + + case method_t::http_patch: return "PATCH"; + case method_t::http_purge: return "PURGE"; + + default: + assert(false); + break; + }; + + return "GET"; +} + +template +Stream& +operator<< (Stream& s, method_t m) +{ + return s << to_string(m); +} + +/** Returns the string corresponding to the numeric HTTP status code. */ +template +std::string +status_text (int status) +{ + switch(status) + { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + //case 306: return ""; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + default: + break; + } + return "Unknown HTTP status"; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp new file mode 100644 index 00000000..29b051e3 --- /dev/null +++ b/include/beast/http/parse_error.hpp @@ -0,0 +1,157 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_PARSE_ERROR_HPP +#define BEAST_HTTP_PARSE_ERROR_HPP + +#include +#include + +namespace beast { +namespace http { + +enum class parse_error +{ + connection_closed, + + bad_method, + bad_uri, + bad_version, + bad_crlf, + bad_request, + + bad_status_code, + bad_status, + + bad_field, + bad_value, + bad_content_length, + illegal_content_length, + bad_on_headers_rv, + + invalid_chunk_size, + + short_read +}; + +class parse_error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case parse_error::connection_closed: + return "data after Connection close"; + + case parse_error::bad_method: + return "bad method"; + + case parse_error::bad_uri: + return "bad Request-URI"; + + case parse_error::bad_version: + return "bad HTTP-Version"; + + case parse_error::bad_crlf: + return "missing CRLF"; + + case parse_error::bad_request: + return "bad Request-Line"; + + case parse_error::bad_status_code: + return "bad Status-Code"; + + case parse_error::bad_status: + return "bad Status-Line"; + + case parse_error::bad_field: + return "bad field token"; + + case parse_error::bad_value: + return "bad field-value"; + + case parse_error::bad_content_length: + return "bad Content-Length"; + + case parse_error::illegal_content_length: + return "illegal Content-Length with chunked Transfer-Encoding"; + + case parse_error::bad_on_headers_rv: + return "on_headers returned an unknown value"; + + case parse_error::invalid_chunk_size: + return "invalid chunk size"; + + case parse_error::short_read: + return "unexpected end of data"; + + default: + return "beast::http::parser error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition(ev, *this); + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_parse_error_category() +{ + static parse_error_category const cat{}; + return cat; +} + +inline +boost::system::error_code +make_error_code(parse_error ev) +{ + return error_code(static_cast(ev), + get_parse_error_category()); +} + +} // http +} // beast + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +#endif diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp new file mode 100644 index 00000000..3dcc6d61 --- /dev/null +++ b/include/beast/http/parser.hpp @@ -0,0 +1,234 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_PARSER_HPP +#define BEAST_HTTP_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +struct parser_request +{ + std::string method_; + std::string uri_; +}; + +struct parser_response +{ + std::string reason_; +}; + +} // detail + +template +class parser + : public basic_parser> + , private std::conditional::type +{ + using message_type = + message; + + std::string field_; + std::string value_; + message_type m_; + typename message_type::body_type::reader r_; + +public: + parser(parser&&) = default; + + parser() + : r_(m_) + { + } + + message_type + release() + { + return std::move(m_); + } + +private: + friend class basic_parser; + + void flush() + { + if(! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + // VFALCO could std::move + m_.headers.insert(field_, value_); + field_.clear(); + value_.clear(); + } + } + + void on_method(boost::string_ref const& s, error_code&) + { + this->method_.append(s.data(), s.size()); + } + + void on_uri(boost::string_ref const& s, error_code&) + { + this->uri_.append(s.data(), s.size()); + } + + void on_reason(boost::string_ref const& s, error_code&) + { + this->reason_.append(s.data(), s.size()); + } + + void on_field(boost::string_ref const& s, error_code&) + { + flush(); + field_.append(s.data(), s.size()); + } + + void on_value(boost::string_ref const& s, error_code&) + { + value_.append(s.data(), s.size()); + } + + void set(std::true_type) + { + // VFALCO This is terrible for setting method + auto m = + [&](char const* s, method_t m) + { + if(this->method_ == s) + { + m_.method = m; + return true; + } + return false; + }; + do + { + if(m("DELETE", method_t::http_delete)) + break; + if(m("GET", method_t::http_get)) + break; + if(m("HEAD", method_t::http_head)) + break; + if(m("POST", method_t::http_post)) + break; + if(m("PUT", method_t::http_put)) + break; + if(m("CONNECT", method_t::http_connect)) + break; + if(m("OPTIONS", method_t::http_options)) + break; + if(m("TRACE", method_t::http_trace)) + break; + if(m("COPY", method_t::http_copy)) + break; + if(m("LOCK", method_t::http_lock)) + break; + if(m("MKCOL", method_t::http_mkcol)) + break; + if(m("MOVE", method_t::http_move)) + break; + if(m("PROPFIND", method_t::http_propfind)) + break; + if(m("PROPPATCH", method_t::http_proppatch)) + break; + if(m("SEARCH", method_t::http_search)) + break; + if(m("UNLOCK", method_t::http_unlock)) + break; + if(m("BIND", method_t::http_bind)) + break; + if(m("REBID", method_t::http_rebind)) + break; + if(m("UNBIND", method_t::http_unbind)) + break; + if(m("ACL", method_t::http_acl)) + break; + if(m("REPORT", method_t::http_report)) + break; + if(m("MKACTIVITY", method_t::http_mkactivity)) + break; + if(m("CHECKOUT", method_t::http_checkout)) + break; + if(m("MERGE", method_t::http_merge)) + break; + if(m("MSEARCH", method_t::http_msearch)) + break; + if(m("NOTIFY", method_t::http_notify)) + break; + if(m("SUBSCRIBE", method_t::http_subscribe)) + break; + if(m("UNSUBSCRIBE",method_t::http_unsubscribe)) + break; + if(m("PATCH", method_t::http_patch)) + break; + if(m("PURGE", method_t::http_purge)) + break; + if(m("MKCALENDAR", method_t::http_mkcalendar)) + break; + if(m("LINK", method_t::http_link)) + break; + if(m("UNLINK", method_t::http_unlink)) + break; + } + while(false); + + m_.url = std::move(this->uri_); + + } + + void set(std::false_type) + { + m_.status = this->status_code(); + m_.reason = this->reason_; + } + + int on_headers(error_code&) + { + flush(); + m_.version = 10 * this->http_major() + this->http_minor(); + return 0; + } + + void on_request(error_code& ec) + { + set(std::integral_constant< + bool, isRequest>{}); + } + + void on_response(error_code& ec) + { + set(std::integral_constant< + bool, isRequest>{}); + } + + void on_body(boost::string_ref const& s, error_code& ec) + { + r_.write(s.data(), s.size(), ec); + } + + void on_complete(error_code&) + { + } +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/read.hpp b/include/beast/http/read.hpp new file mode 100644 index 00000000..78cfa5a3 --- /dev/null +++ b/include/beast/http/read.hpp @@ -0,0 +1,108 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_READ_HPP +#define BEAST_HTTP_READ_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** Read a HTTP message from a stream. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @throws boost::system::system_error on failure. +*/ +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& msg) +{ + error_code ec; + read(stream, streambuf, msg, ec); + if(ec) + throw boost::system::system_error{ec}; +} + +/** Read a HTTP message from a stream. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @param ec Set to the error, if any occurred. +*/ +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& msg, + error_code& ec); + +/** Start reading a HTTP message from a stream asynchronously. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + async_read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). +*/ +template +#if GENERATING_DOCS +void_or_deduced +#else +typename async_completion< + ReadHandler, void(error_code)>::result_type +#endif +async_read(AsyncReadStream& stream, Streambuf& streambuf, + message& msg, + ReadHandler&& handler); + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/reason.hpp b/include/beast/http/reason.hpp new file mode 100644 index 00000000..ab3b51b7 --- /dev/null +++ b/include/beast/http/reason.hpp @@ -0,0 +1,72 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_REASON_HPP +#define BEAST_HTTP_REASON_HPP + +namespace beast { +namespace http { + +/** Returns the text for a known status code integer. */ +template +char const* +reason_string(int status) +{ + switch(status) + { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + + case 306: return ""; + default: + break; + } + return ""; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/resume_context.hpp b/include/beast/http/resume_context.hpp new file mode 100644 index 00000000..67c8d513 --- /dev/null +++ b/include/beast/http/resume_context.hpp @@ -0,0 +1,34 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_RESUME_CONTEXT_HPP +#define BEAST_HTTP_RESUME_CONTEXT_HPP + +#include + +namespace beast { +namespace http { + +/** A functor that resumes a write operation. + + An rvalue reference to an object of this type is provided by the + write implementation to the `writer` associated with the body of + a message being sent. + + If it is desired that the `writer` suspend the write operation (for + example, to wait until data is ready), it can take ownership of + the resume context using a move. Then, it returns `boost::indeterminate` + to indicate that the write operation should suspend. Later, the calling + code invokes the resume function and the write operation continues + from where it left off. +*/ +using resume_context = std::function; + +} // http +} // beast + +#endif diff --git a/include/beast/http/rfc2616.hpp b/include/beast/http/rfc2616.hpp new file mode 100644 index 00000000..e3c4b844 --- /dev/null +++ b/include/beast/http/rfc2616.hpp @@ -0,0 +1,464 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_RFC2616_HPP +#define BEAST_HTTP_RFC2616_HPP + +#include +#include +#include +#include +#include +#include +#include +#include // for std::tie, remove ASAP +#include +#include + +namespace beast { + +#if ! GENERATING_DOCS + +/** Routines for performing RFC2616 compliance. + RFC2616: + Hypertext Transfer Protocol -- HTTP/1.1 + http://www.w3.org/Protocols/rfc2616/rfc2616 +*/ +namespace rfc2616 { + +namespace detail { + +struct ci_equal_pred +{ + bool operator()(char c1, char c2) + { + // VFALCO TODO Use a table lookup here + return std::tolower(c1) == std::tolower(c2); + } +}; + +} // detail + +/** Returns `true` if `c` is linear white space. + + This excludes the CRLF sequence allowed for line continuations. +*/ +inline +bool +is_lws(char c) +{ + return c == ' ' || c == '\t'; +} + +/** Returns `true` if `c` is any whitespace character. */ +inline +bool +is_white(char c) +{ + switch (c) + { + case ' ': case '\f': case '\n': + case '\r': case '\t': case '\v': + return true; + }; + return false; +} + +/** Returns `true` if `c` is a control character. */ +inline +bool +is_control(char c) +{ + return c <= 31 || c >= 127; +} + +/** Returns `true` if `c` is a separator. */ +inline +bool +is_separator(char c) +{ + // VFALCO Could use a static table + switch (c) + { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '{': case '}': case ' ': case '\t': + return true; + }; + return false; +} + +/** Returns `true` if `c` is a character. */ +inline +bool +is_char(char c) +{ + return c >= 0 && c <= 127; +} + +template +FwdIter +trim_left (FwdIter first, FwdIter last) +{ + return std::find_if_not (first, last, + is_white); +} + +template +FwdIter +trim_right (FwdIter first, FwdIter last) +{ + if (first == last) + return last; + do + { + --last; + if (! is_white (*last)) + return ++last; + } + while (last != first); + return first; +} + +template +void +trim_right_in_place (std::basic_string < + CharT, Traits, Allocator>& s) +{ + s.resize (std::distance (s.begin(), + trim_right (s.begin(), s.end()))); +} + +template +std::pair +trim (FwdIter first, FwdIter last) +{ + first = trim_left (first, last); + last = trim_right (first, last); + return std::make_pair (first, last); +} + +template +String +trim (String const& s) +{ + using std::begin; + using std::end; + auto first = begin(s); + auto last = end(s); + std::tie (first, last) = trim (first, last); + return { first, last }; +} + +template +String +trim_right (String const& s) +{ + using std::begin; + using std::end; + auto first (begin(s)); + auto last (end(s)); + last = trim_right (first, last); + return { first, last }; +} + +inline +std::string +trim (std::string const& s) +{ + return trim (s); +} + +/** Parse a character sequence of values separated by commas. + Double quotes and escape sequences will be converted. Excess white + space, commas, double quotes, and empty elements are not copied. + Format: + #(token|quoted-string) + Reference: + http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 +*/ +template ::value_type>>, + class Char> +Result +split(FwdIt first, FwdIt last, Char delim) +{ + Result result; + using string = typename Result::value_type; + FwdIt iter = first; + string e; + while (iter != last) + { + if (*iter == '"') + { + // quoted-string + ++iter; + while (iter != last) + { + if (*iter == '"') + { + ++iter; + break; + } + + if (*iter == '\\') + { + // quoted-pair + ++iter; + if (iter != last) + e.append (1, *iter++); + } + else + { + // qdtext + e.append (1, *iter++); + } + } + if (! e.empty()) + { + result.emplace_back(std::move(e)); + e.clear(); + } + } + else if (*iter == delim) + { + e = trim_right (e); + if (! e.empty()) + { + result.emplace_back(std::move(e)); + e.clear(); + } + ++iter; + } + else if (is_lws (*iter)) + { + ++iter; + } + else + { + e.append (1, *iter++); + } + } + + if (! e.empty()) + { + e = trim_right (e); + if (! e.empty()) + result.emplace_back(std::move(e)); + } + return result; +} + +template ::value_type>>> +Result +split_commas(FwdIt first, FwdIt last) +{ + return split(first, last, ','); +} + +template > +Result +split_commas(boost::string_ref const& s) +{ + return split_commas(s.begin(), s.end()); +} + +//------------------------------------------------------------------------------ + +/** Iterates through a comma separated list. + + Meets the requirements of ForwardIterator. + + List defined in rfc2616 2.1. + + @note Values returned may contain backslash escapes. +*/ +class list_iterator +{ + using iter_type = boost::string_ref::const_iterator; + + iter_type it_; + iter_type end_; + boost::string_ref value_; + +public: + using value_type = boost::string_ref; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + list_iterator(iter_type begin, iter_type end) + : it_(begin) + , end_(end) + { + if(it_ != end_) + increment(); + } + + bool + operator==(list_iterator const& other) const + { + return other.it_ == it_ && other.end_ == end_ + && other.value_.size() == value_.size(); + } + + bool + operator!=(list_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return value_; + } + + pointer + operator->() const + { + return &*(*this); + } + + list_iterator& + operator++() + { + increment(); + return *this; + } + + list_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + template + void + increment(); +}; + +template +void +list_iterator::increment() +{ + value_.clear(); + while(it_ != end_) + { + if(*it_ == '"') + { + // quoted-string + ++it_; + if(it_ == end_) + return; + if(*it_ != '"') + { + auto start = it_; + for(;;) + { + ++it_; + if(it_ == end_) + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + return; + } + if(*it_ == '"') + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + ++it_; + return; + } + } + } + ++it_; + } + else if(*it_ == ',') + { + it_++; + continue; + } + else if(is_lws(*it_)) + { + ++it_; + continue; + } + else + { + auto start = it_; + for(;;) + { + ++it_; + if(it_ == end_ || + *it_ == ',' || + is_lws(*it_)) + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + return; + } + } + } + } +} + +/** Returns true if two strings are equal. + + A case-insensitive comparison is used. +*/ +inline +bool +ci_equal(boost::string_ref s1, boost::string_ref s2) +{ + return boost::range::equal(s1, s2, + detail::ci_equal_pred{}); +} + +/** Returns a range representing the list. */ +inline +boost::iterator_range +make_list(boost::string_ref const& field) +{ + return boost::iterator_range{ + list_iterator{field.begin(), field.end()}, + list_iterator{field.end(), field.end()}}; +} + +/** Returns true if the specified token exists in the list. + + A case-insensitive comparison is used. +*/ +template +bool +token_in_list(boost::string_ref const& value, + boost::string_ref const& token) +{ + for(auto const& item : make_list(value)) + if(ci_equal(item, token)) + return true; + return false; +} + +} // rfc2616 + +#endif + +} // beast + +#endif + diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp new file mode 100644 index 00000000..3f69354a --- /dev/null +++ b/include/beast/http/rfc7230.hpp @@ -0,0 +1,23 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_RFC7230_HPP +#define BEAST_HTTP_RFC7230_HPP + +#include +#include + +namespace beast { +namespace rfc7230 { + + + +} // rfc7230 +} // beast + +#endif + diff --git a/include/beast/http/streambuf_body.hpp b/include/beast/http/streambuf_body.hpp new file mode 100644 index 00000000..745935d1 --- /dev/null +++ b/include/beast/http/streambuf_body.hpp @@ -0,0 +1,96 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_STREAMBUF_BODY_HPP +#define BEAST_HTTP_STREAMBUF_BODY_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A Body represented by a Streambuf +*/ +template +struct basic_streambuf_body +{ + /// The type of the `message::body` member + using value_type = Streambuf; + +#if GENERATING_DOCS +private: +#endif + + class reader + { + value_type& sb_; + + public: + template + explicit + reader(message& m) noexcept + : sb_(m.body) + { + } + + void + write(void const* data, + std::size_t size, error_code&) noexcept + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + sb_.commit(buffer_copy( + sb_.prepare(size), buffer(data, size))); + } + }; + + class writer + { + Streambuf const& body_; + + public: + template + explicit + writer(message const& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return body_.size(); + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(body_.data()); + return true; + } + }; +}; + +using streambuf_body = basic_streambuf_body; + +} // http +} // beast + +#endif diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp new file mode 100644 index 00000000..2711f8c1 --- /dev/null +++ b/include/beast/http/string_body.hpp @@ -0,0 +1,92 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_STRING_BODY_HPP +#define BEAST_HTTP_STRING_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A Body represented by a std::string. +*/ +struct string_body +{ + /// The type of the `message::body` member + using value_type = std::string; + +#if GENERATING_DOCS +private: +#endif + + class reader + { + value_type& s_; + + public: + template + explicit + reader(message& m) noexcept + : s_(m.body) + { + } + + void + write(void const* data, + std::size_t size, error_code&) noexcept + { + auto const n = s_.size(); + s_.resize(n + size); + std::memcpy(&s_[n], data, size); + } + }; + + class writer + { + value_type const& body_; + + public: + template + explicit + writer(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return body_.size(); + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(boost::asio::buffer(body_)); + return true; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp new file mode 100644 index 00000000..0489dda6 --- /dev/null +++ b/include/beast/http/write.hpp @@ -0,0 +1,93 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_WRITE_HPP +#define BEAST_HTTP_WRITE_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** Write a HTTP message to a stream. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @throws boost::system::error code on failure. +*/ +template +void +write(SyncWriteStream& stream, + message const& msg) +{ + error_code ec; + write(stream, msg, ec); + if(ec) + throw boost::system::system_error{ec}; +} + +/** Write a HTTP message to a stream. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @param ec Set to the error, if any occurred. +*/ +template +void +write(SyncWriteStream& stream, + message const& msg, + error_code& ec); + +/** Start writing a HTTP message to a stream asynchronously. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @param token The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + + @note The message must remain valid at least until the + completion handler is called, no copies are made. +*/ +template +#if GENERATING_DOCS +void_or_deduced +#else +typename async_completion< + WriteHandler, void(error_code)>::result_type +#endif +async_write(AsyncWriteStream& stream, + message const& msg, + WriteHandler&& handler); + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/impl/basic_streambuf.ipp b/include/beast/impl/basic_streambuf.ipp new file mode 100644 index 00000000..7ba989ee --- /dev/null +++ b/include/beast/impl/basic_streambuf.ipp @@ -0,0 +1,892 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_BASIC_STREAMBUF_IPP +#define BEAST_IMPL_BASIC_STREAMBUF_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/* These diagrams illustrate the layout and state variables. + +1 Input and output contained entirely in one element: + + 0 out_ + |<-------------+------------------------------------------->| + in_pos_ out_pos_ out_end_ + + +2 Output contained in first and second elements: + + out_ + |<------+----------+------->| |<----------+-------------->| + in_pos_ out_pos_ out_end_ + + +3 Output contained in the second element: + + out_ + |<------------+------------>| |<----+-------------------->| + in_pos_ out_pos_ out_end_ + + +4 Output contained in second and third elements: + + out_ + |<-----+-------->| |<-------+------>| |<--------------->| + in_pos_ out_pos_ out_end_ + + +5 Input sequence is empty: + + out_ + |<------+------------------>| |<-----------+------------->| + out_pos_ out_end_ + in_pos_ + + +6 Output sequence is empty: + + out_ + |<------+------------------>| |<------+------------------>| + in_pos_ out_pos_ + out_end_ + + +7 The end of output can point to the end of an element. + But out_pos_ should never point to the end: + + out_ + |<------+------------------>| |<------+------------------>| + in_pos_ out_pos_ out_end_ + + +8 When the input sequence entirely fills the last element and + the output sequence is empty, out_ will point to the end of + the list of buffers, and out_pos_ and out_end_ will be 0: + + + |<------+------------------>| out_ == list_.end() + in_pos_ out_pos_ == 0 + out_end_ == 0 +*/ + +template +class basic_streambuf::element + : public boost::intrusive::list_base_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> +{ + using size_type = typename std::allocator_traits::size_type; + + size_type const size_; + +public: + element(element const&) = delete; + element& operator=(element const&) = delete; + + explicit + element(size_type n) + : size_(n) + { + } + + size_type + size() const + { + return size_; + } + + char* + data() const + { + return const_cast( + reinterpret_cast(this+1)); + } +}; + +template +class basic_streambuf::const_buffers_type +{ + basic_streambuf const* sb_ = nullptr; + + friend class basic_streambuf; + + explicit + const_buffers_type(basic_streambuf const& sb); + +public: + /// Why? + using value_type = boost::asio::const_buffer; + + class const_iterator; + + const_buffers_type() = default; + const_buffers_type(const_buffers_type const&) = default; + const_buffers_type& operator=(const_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; +}; + +template +class basic_streambuf::mutable_buffers_type +{ + basic_streambuf const* sb_; + + friend class basic_streambuf; + + explicit + mutable_buffers_type(basic_streambuf const& sb); + +public: + using value_type = mutable_buffer; + + class const_iterator; + + mutable_buffers_type() = default; + mutable_buffers_type(mutable_buffers_type const&) = default; + mutable_buffers_type& operator=(mutable_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; +}; + +//------------------------------------------------------------------------------ + +template +class basic_streambuf::const_buffers_type::const_iterator +{ + basic_streambuf const* sb_ = nullptr; + typename list_type::const_iterator it_; + +public: + using value_type = + typename const_buffers_type::value_type; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + const_iterator(basic_streambuf const& sb, + typename list_type::const_iterator const& it) + : sb_(&sb) + , it_(it) + { + } + + bool + operator==(const_iterator const& other) const + { + return sb_ == other.sb_ && it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + auto const& e = *it_; + return value_type{e.data(), + (sb_->out_ == sb_->list_.end() || + &e != &*sb_->out_) ? e.size() : sb_->out_pos_} + + (&e == &*sb_->list_.begin() ? sb_->in_pos_ : 0); + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } +}; + +template +basic_streambuf::const_buffers_type::const_buffers_type( + basic_streambuf const& sb) + : sb_(&sb) +{ +} + +template +auto +basic_streambuf::const_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{*sb_, sb_->list_.begin()}; +} + +template +auto +basic_streambuf::const_buffers_type::end() const -> + const_iterator +{ + return const_iterator{*sb_, sb_->out_ == + sb_->list_.end() ? sb_->list_.end() : + std::next(sb_->out_)}; +} + +//------------------------------------------------------------------------------ + +template +class basic_streambuf::mutable_buffers_type::const_iterator +{ + basic_streambuf const* sb_ = nullptr; + typename list_type::const_iterator it_; + +public: + using value_type = + typename mutable_buffers_type::value_type; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + const_iterator(basic_streambuf const& sb, + typename list_type::const_iterator const& it) + : sb_(&sb) + , it_(it) + { + } + + bool + operator==(const_iterator const& other) const + { + return sb_ == other.sb_ && it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + auto const& e = *it_; + return value_type{e.data(), + &e == &*std::prev(sb_->list_.end()) ? + sb_->out_end_ : e.size()} + + (&e == &*sb_->out_ ? sb_->out_pos_ : 0); + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } +}; + +template +basic_streambuf::mutable_buffers_type::mutable_buffers_type( + basic_streambuf const& sb) + : sb_(&sb) +{ +} + +template +auto +basic_streambuf::mutable_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{*sb_, sb_->out_}; +} + +template +auto +basic_streambuf::mutable_buffers_type::end() const -> + const_iterator +{ + return const_iterator{*sb_, sb_->list_.end()}; +} + +//------------------------------------------------------------------------------ + +template +basic_streambuf::~basic_streambuf() +{ + delete_list(); +} + +template +basic_streambuf:: +basic_streambuf(basic_streambuf&& other) + : detail::empty_base_optimization( + std::move(other.member())) + , alloc_size_(other.alloc_size_) + , in_size_(other.in_size_) + , in_pos_(other.in_pos_) + , out_pos_(other.out_pos_) + , out_end_(other.out_end_) +{ + auto const at_end = + other.out_ == other.list_.end(); + list_ = std::move(other.list_); + out_ = at_end ? list_.end() : other.out_; + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; +} + +template +basic_streambuf:: +basic_streambuf(basic_streambuf&& other, + allocator_type const& alloc) + : basic_streambuf(other.alloc_size_, alloc) +{ + using boost::asio::buffer_copy; + if(this->member() != other.member()) + commit(buffer_copy(prepare(other.size()), other.data())); + else + move_assign(other, std::true_type{}); +} + +template +auto +basic_streambuf::operator=( + basic_streambuf&& other) -> basic_streambuf& +{ + if(this == &other) + return *this; + // VFALCO If any memory allocated we could use it first? + clear(); + alloc_size_ = other.alloc_size_; + move_assign(other, std::integral_constant{}); + return *this; +} + +template +basic_streambuf:: +basic_streambuf(basic_streambuf const& other) + : basic_streambuf(other.alloc_size_, + alloc_traits::select_on_container_copy_construction(other.member())) +{ + commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); +} + +template +basic_streambuf:: +basic_streambuf(basic_streambuf const& other, + allocator_type const& alloc) + : basic_streambuf(other.alloc_size_, alloc) +{ + commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); +} + +template +auto +basic_streambuf::operator=( + basic_streambuf const& other) -> + basic_streambuf& +{ + if(this == &other) + return *this; + using boost::asio::buffer_copy; + clear(); + copy_assign(other, std::integral_constant{}); + commit(buffer_copy(prepare(other.size()), other.data())); + return *this; +} + +template +template +basic_streambuf::basic_streambuf( + basic_streambuf const& other) + : basic_streambuf(other.alloc_size_) +{ + using boost::asio::buffer_copy; + commit(buffer_copy(prepare(other.size()), other.data())); +} + +template +template +basic_streambuf::basic_streambuf( + basic_streambuf const& other, + allocator_type const& alloc) + : basic_streambuf(other.alloc_size_, alloc) +{ + using boost::asio::buffer_copy; + commit(buffer_copy(prepare(other.size()), other.data())); +} + +template +template +auto +basic_streambuf::operator=( + basic_streambuf const& other) -> + basic_streambuf& +{ + using boost::asio::buffer_copy; + clear(); + commit(buffer_copy(prepare(other.size()), other.data())); + return *this; +} + +template +basic_streambuf::basic_streambuf( + std::size_t alloc_size, Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , out_(list_.end()) + , alloc_size_(alloc_size) +{ + if(alloc_size <= 0) + throw std::invalid_argument( + "basic_streambuf: invalid alloc_size"); +} + +template +auto +basic_streambuf::prepare(size_type n) -> + mutable_buffers_type +{ + list_type reuse; + if(out_ != list_.end()) + { + if(out_ != list_.iterator_to(list_.back())) + { + out_end_ = out_->size(); + reuse.splice(reuse.end(), list_, + std::next(out_), list_.end()); + debug_check(); + } + auto const avail = out_->size() - out_pos_; + if(n > avail) + { + out_end_ = out_->size(); + n -= avail; + } + else + { + out_end_ = out_pos_ + n; + n = 0; + } + debug_check(); + } + while(n > 0 && ! reuse.empty()) + { + auto& e = reuse.front(); + reuse.erase(reuse.iterator_to(e)); + list_.push_back(e); + if(n > e.size()) + { + out_end_ = e.size(); + n -= e.size(); + } + else + { + out_end_ = n; + n = 0; + } + debug_check(); + } + while(n > 0) + { + auto const size = std::max(alloc_size_, n); + auto& e = *reinterpret_cast( + alloc_traits::allocate(this->member(), + sizeof(element) + size)); + alloc_traits::construct(this->member(), &e, size); + list_.push_back(e); + if(out_ == list_.end()) + out_ = list_.iterator_to(e); + if(n > e.size()) + { + out_end_ = e.size(); + n -= e.size(); + } + else + { + out_end_ = n; + n = 0; + } + debug_check(); + } + for(auto it = reuse.begin(); it != reuse.end();) + { + auto& e = *it++; + reuse.erase(list_.iterator_to(e)); + auto const len = e.size() + sizeof(e); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), len); + } + return mutable_buffers_type(*this); +} + +template +void +basic_streambuf::commit(size_type n) +{ + if(list_.empty()) + return; + if(out_ == list_.end()) + return; + auto const back = + list_.iterator_to(list_.back()); + while(out_ != back) + { + auto const avail = + out_->size() - out_pos_; + if(n < avail) + { + out_pos_ += n; + in_size_ += n; + debug_check(); + return; + } + ++out_; + n -= avail; + out_pos_ = 0; + in_size_ += avail; + debug_check(); + } + + n = std::min(n, out_end_ - out_pos_); + out_pos_ += n; + in_size_ += n; + if(out_pos_ == out_->size()) + { + ++out_; + out_pos_ = 0; + out_end_ = 0; + } + debug_check(); +} + +template +auto +basic_streambuf::data() const -> + const_buffers_type +{ + return const_buffers_type(*this); +} + +template +void +basic_streambuf::consume(size_type n) +{ + if(list_.empty()) + return; + + for(;;) + { + if(list_.begin() != out_) + { + auto const avail = list_.front().size() - in_pos_; + if(n < avail) + { + in_size_ -= n; + in_pos_ += n; + debug_check(); + break; + } + n -= avail; + in_size_ -= avail; + in_pos_ = 0; + auto& e = list_.front(); + list_.erase(list_.iterator_to(e)); + auto const len = e.size() + sizeof(e); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), len); + debug_check(); + } + else + { + auto const avail = out_pos_ - in_pos_; + if(n < avail) + { + in_size_ -= n; + in_pos_ += n; + } + else + { + in_size_ = 0; + if(out_ != list_.iterator_to(list_.back()) || + out_pos_ != out_end_) + { + in_pos_ = out_pos_; + } + else + { + // Input and output sequences are empty, reuse buffer. + // Alternatively we could deallocate it. + in_pos_ = 0; + out_pos_ = 0; + out_end_ = 0; + } + } + debug_check(); + break; + } + } +} + +template +void +basic_streambuf::clear() +{ + delete_list(); + list_.clear(); + out_ = list_.begin(); + in_size_ = 0; + in_pos_ = 0; + out_pos_ = 0; + out_end_ = 0; +} + +template +void +basic_streambuf:: +move_assign(basic_streambuf& other, std::false_type) +{ + using boost::asio::buffer_copy; + if(this->member() != other.member()) + { + commit(buffer_copy(prepare(other.size()), other.data())); + other.clear(); + } + else + move_assign(other, std::true_type{}); +} + +template +void +basic_streambuf:: +move_assign(basic_streambuf& other, std::true_type) +{ + this->member() = std::move(other.member()); + auto const at_end = + other.out_ == other.list_.end(); + list_ = std::move(other.list_); + out_ = at_end ? list_.end() : other.out_; + + in_size_ = other.in_size_; + in_pos_ = other.in_pos_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; +} + +template +void +basic_streambuf:: +copy_assign(basic_streambuf const& other, std::false_type) +{ +} + +template +void +basic_streambuf:: +copy_assign(basic_streambuf const& other, std::true_type) +{ + this->member() = other.member(); +} + +template +void +basic_streambuf::delete_list() +{ + for(auto iter = list_.begin(); iter != list_.end();) + { + auto& e = *iter++; + auto const n = e.size() + sizeof(e); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), n); + } +} + +// Returns the number of bytes which can be +// prepared without causing a memory allocation. +template +std::size_t +basic_streambuf::prepare_size() const +{ + auto pos = out_; + if(pos == list_.end()) + return 0; + auto n = pos->size() - out_pos_; + while(++pos != list_.end()) + n += pos->size(); + return n; +} + +template +void +basic_streambuf::debug_check() const +{ +#ifndef NDEBUG + using boost::asio::buffer_size; + assert(buffer_size(data()) == in_size_); + if(list_.empty()) + { + assert(in_pos_ == 0); + assert(in_size_ == 0); + assert(out_pos_ == 0); + assert(out_end_ == 0); + assert(out_ == list_.end()); + return; + } + + auto const& front = list_.front(); + + assert(in_pos_ < front.size()); + + if(out_ == list_.end()) + { + assert(out_pos_ == 0); + assert(out_end_ == 0); + } + else + { + auto const& out = *out_; + auto const& back = list_.back(); + + assert(out_end_ <= back.size()); + assert(out_pos_ < out.size()); + assert(&out != &front || out_pos_ >= in_pos_); + assert(&out != &front || out_pos_ - in_pos_ == in_size_); + assert(&out != &back || out_pos_ <= out_end_); + } +#endif +} + +template +basic_streambuf& +operator<<(basic_streambuf& streambuf, T const& t) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + std::stringstream ss; + ss << t; + auto const& s = ss.str(); + streambuf.commit(buffer_copy( + streambuf.prepare(s.size()), buffer(s))); + return streambuf; +} + +//------------------------------------------------------------------------------ + +template +std::size_t +read_size_helper(basic_streambuf< + Allocator> const& streambuf, std::size_t max_size) +{ + return std::min(max_size, + std::max(512, streambuf.prepare_size())); +} + +template +std::string +to_string(basic_streambuf const& streambuf) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + std::string s; + s.resize(streambuf.size()); + buffer_copy( + buffer(&s[0], s.size()), streambuf.data()); + return s; +} + +} // beast + +#endif diff --git a/include/beast/impl/buffers_adapter.ipp b/include/beast/impl/buffers_adapter.ipp new file mode 100644 index 00000000..0040fd5a --- /dev/null +++ b/include/beast/impl/buffers_adapter.ipp @@ -0,0 +1,520 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_BUFFERS_ADAPTER_IPP +#define BEAST_IMPL_BUFFERS_ADAPTER_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +template +class buffers_adapter::const_buffers_type +{ + buffers_adapter const* ba_; + +public: + using value_type = boost::asio::const_buffer; + + class const_iterator; + + const_buffers_type() = default; + const_buffers_type( + const_buffers_type const&) = default; + const_buffers_type& operator=( + const_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; + +private: + friend class buffers_adapter; + + const_buffers_type(buffers_adapter const& ba) + : ba_(&ba) + { + } +}; + +template +class buffers_adapter::const_buffers_type::const_iterator +{ + iter_type it_; + buffers_adapter const* ba_; + +public: + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return ba_ == other.ba_ && + it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return value_type{buffer_cast(*it_), + (ba_->out_ == ba_->bs_.end() || + it_ != ba_->out_) ? buffer_size(*it_) : ba_->out_pos_} + + (it_ == ba_->begin_ ? ba_->in_pos_ : 0); + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + friend class const_buffers_type; + + const_iterator(buffers_adapter const& ba, + iter_type iter) + : it_(iter) + , ba_(&ba) + { + } +}; + +template +inline +auto +buffers_adapter::const_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{*ba_, ba_->begin_}; +} + +template +inline +auto +buffers_adapter::const_buffers_type::end() const -> + const_iterator +{ + return const_iterator{*ba_, ba_->out_ == + ba_->end_ ? ba_->end_ : std::next(ba_->out_)}; +} + +//------------------------------------------------------------------------------ + +template +class buffers_adapter::mutable_buffers_type +{ + buffers_adapter const* ba_; + +public: + using value_type = boost::asio::mutable_buffer; + + class const_iterator; + + mutable_buffers_type() = default; + mutable_buffers_type( + mutable_buffers_type const&) = default; + mutable_buffers_type& operator=( + mutable_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; + +private: + friend class buffers_adapter; + + mutable_buffers_type( + buffers_adapter const& ba) + : ba_(&ba) + { + } +}; + +template +class buffers_adapter::mutable_buffers_type::const_iterator +{ + iter_type it_; + buffers_adapter const* ba_; + +public: + using value_type = boost::asio::mutable_buffer; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return ba_ == other.ba_ && + it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return value_type{buffer_cast(*it_), + it_ == std::prev(ba_->end_) ? + ba_->out_end_ : buffer_size(*it_)} + + (it_ == ba_->out_ ? ba_->out_pos_ : 0); + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + friend class mutable_buffers_type; + + const_iterator(buffers_adapter const& ba, + iter_type iter) + : it_(iter) + , ba_(&ba) + { + } +}; + +template +inline +auto +buffers_adapter::mutable_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{*ba_, ba_->out_}; +} + +template +inline +auto +buffers_adapter::mutable_buffers_type::end() const -> + const_iterator +{ + return const_iterator{*ba_, ba_->end_}; +} + +//------------------------------------------------------------------------------ + +template +buffers_adapter::buffers_adapter( + buffers_adapter&& other) + : buffers_adapter(std::move(other), + std::distance(other.bs_.begin(), other.begin_), + std::distance(other.bs_.begin(), other.out_), + std::distance(other.bs_.begin(), other.end_)) +{ +} + +template +buffers_adapter::buffers_adapter( + buffers_adapter const& other) + : buffers_adapter(other, + std::distance(other.bs_.begin(), other.begin_), + std::distance(other.bs_.begin(), other.out_), + std::distance(other.bs_.begin(), other.end_)) +{ +} + +template +auto +buffers_adapter::operator=( + buffers_adapter&& other) -> buffers_adapter& +{ + auto const nbegin = std::distance( + other.bs_.begin(), other.begin_); + auto const nout = std::distance( + other.bs_.begin(), other.out_); + auto const nend = std::distance( + other.bs_.begin(), other.end_); + bs_ = std::move(other.bs_); + begin_ = std::next(bs_.begin(), nbegin); + out_ = std::next(bs_.begin(), nout); + end_ = std::next(bs_.begin(), nend); + max_size_ = other.max_size_; + in_pos_ = other.in_pos_; + in_size_ = other.in_size_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + return *this; +} + +template +auto +buffers_adapter::operator=( + buffers_adapter const& other) -> buffers_adapter& +{ + auto const nbegin = std::distance( + other.bs_.begin(), other.begin_); + auto const nout = std::distance( + other.bs_.begin(), other.out_); + auto const nend = std::distance( + other.bs_.begin(), other.end_); + bs_ = other.bs_; + begin_ = std::next(bs_.begin(), nbegin); + out_ = std::next(bs_.begin(), nout); + end_ = std::next(bs_.begin(), nend); + max_size_ = other.max_size_; + in_pos_ = other.in_pos_; + in_size_ = other.in_size_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + return *this; +} + +template +buffers_adapter::buffers_adapter( + Buffers const& bs) + : bs_(bs) + , begin_(bs_.begin()) + , out_(bs_.begin()) + , end_(bs_.begin()) + , max_size_(boost::asio::buffer_size(bs_)) +{ +} + +template +auto +buffers_adapter::prepare(std::size_t n) -> + mutable_buffers_type +{ + using boost::asio::buffer_size; + static_assert(is_mutable, + "Operation not valid for ConstBufferSequence"); + end_ = out_; + if(end_ != bs_.end()) + { + auto size = buffer_size(*end_) - out_pos_; + if(n > size) + { + n -= size; + while(++end_ != bs_.end()) + { + size = buffer_size(*end_); + if(n < size) + { + out_end_ = n; + n = 0; + ++end_; + break; + } + n -= size; + out_end_ = size; + } + } + else + { + ++end_; + out_end_ = out_pos_ + n; + n = 0; + } + } + if(n > 0) + throw std::length_error( + "no space in buffers_adapter"); + return mutable_buffers_type{*this}; +} + +template +void +buffers_adapter::commit(std::size_t n) +{ + using boost::asio::buffer_size; + static_assert(is_mutable, + "Operation not valid for ConstBufferSequence"); + if(out_ == end_) + return; + auto const last = std::prev(end_); + while(out_ != last) + { + auto const avail = + buffer_size(*out_) - out_pos_; + if(n < avail) + { + out_pos_ += n; + in_size_ += n; + max_size_ -= n; + return; + } + ++out_; + n -= avail; + out_pos_ = 0; + in_size_ += avail; + max_size_ -= avail; + } + + n = std::min(n, out_end_ - out_pos_); + out_pos_ += n; + in_size_ += n; + max_size_ -= n; + if(out_pos_ == buffer_size(*out_)) + { + ++out_; + out_pos_ = 0; + out_end_ = 0; + } +} + +template +inline +auto +buffers_adapter::data() const -> + const_buffers_type +{ + return const_buffers_type{*this}; +} + +template +void +buffers_adapter::consume(std::size_t n) +{ + for(;;) + { + if(begin_ != out_) + { + auto const avail = + buffer_size(*begin_) - in_pos_; + if(n < avail) + { + in_size_ -= n; + in_pos_ += n; + break; + } + n -= avail; + in_size_ -= avail; + in_pos_ = 0; + ++begin_; + } + else + { + auto const avail = out_pos_ - in_pos_; + if(n < avail) + { + in_size_ -= n; + in_pos_ += n; + } + else + { + in_size_ -= avail; + if(out_pos_ != out_end_|| + out_ != std::prev(bs_.end())) + { + in_pos_ = out_pos_; + } + else + { + // Use the whole buffer now. + in_pos_ = 0; + out_pos_ = 0; + out_end_ = 0; + } + } + break; + } + } +} + +} // beast + +#endif diff --git a/include/beast/impl/consuming_buffers.ipp b/include/beast/impl/consuming_buffers.ipp new file mode 100644 index 00000000..4aa203f5 --- /dev/null +++ b/include/beast/impl/consuming_buffers.ipp @@ -0,0 +1,211 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_CONSUMING_BUFFERS_IPP +#define BEAST_IMPL_CONSUMING_BUFFERS_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +template +class consuming_buffers::const_iterator +{ + friend class consuming_buffers; + + using iter_type = + typename Buffers::const_iterator; + + iter_type it_; + consuming_buffers const* b_; + +public: + using value_type = + typename std::iterator_traits::value_type; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return b_ == other.b_ && it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + if(it_ == b_->begin_) + return *it_ + b_->skip_; + return *it_; + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + const_iterator(consuming_buffers const& b, + iter_type it) + : it_(it) + , b_(&b) + { + } +}; + +template +consuming_buffers:: +consuming_buffers(consuming_buffers&& other) + : consuming_buffers(std::move(other), + std::distance( + other.bs_.begin(), other.begin_)) +{ +} + +template +consuming_buffers:: +consuming_buffers(consuming_buffers const& other) + : consuming_buffers(other, + std::distance( + other.bs_.begin(), other.begin_)) +{ +} + +template +auto +consuming_buffers:: +operator=(consuming_buffers&& other) -> + consuming_buffers& +{ + auto const nbegin = std::distance( + other.bs_.begin(), other.begin_); + bs_ = std::move(other.bs_); + begin_ = std::next(bs_.begin(), nbegin); + skip_ = other.skip_; + return *this; +} + +template +auto +consuming_buffers:: +operator=(consuming_buffers const& other) -> + consuming_buffers& +{ + auto const nbegin = std::distance( + other.bs_.begin(), other.begin_); + bs_ = other.bs_; + begin_ = std::next(bs_.begin(), nbegin); + skip_ = other.skip_; + return *this; +} + +template +consuming_buffers:: +consuming_buffers(Buffers const& bs) + : bs_(bs) + , begin_(bs_.begin()) +{ + static_assert(is_BufferSequence::value, + "BufferSequence requirements not met"); +} + +template +auto +consuming_buffers::begin() const -> + const_iterator +{ + return const_iterator{*this, begin_}; +} + +template +auto +consuming_buffers::end() const -> + const_iterator +{ + return const_iterator{*this, bs_.end()}; +} + +template +void +consuming_buffers::consume(std::size_t n) +{ + using boost::asio::buffer_size; + for(;n > 0 && begin_ != bs_.end(); ++begin_) + { + auto const len = + buffer_size(*begin_) - skip_; + if(n < len) + { + skip_ += n; + break; + } + n -= len; + skip_ = 0; + } +} + +template +consuming_buffers +consumed_buffers(Buffers const& bs, std::size_t n) +{ + consuming_buffers cb(bs); + cb.consume(n); + return cb; +} + +} // beast + +#endif diff --git a/include/beast/impl/prepare_buffers.ipp b/include/beast/impl/prepare_buffers.ipp new file mode 100644 index 00000000..65de4a3b --- /dev/null +++ b/include/beast/impl/prepare_buffers.ipp @@ -0,0 +1,215 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_PREPARE_BUFFERS_IPP +#define BEAST_IMPL_PREPARE_BUFFERS_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +template +void +prepared_buffers:: +setup(std::size_t n) +{ + for(end_ = bs_.begin(); end_ != bs_.end(); ++end_) + { + auto const len = + boost::asio::buffer_size(*end_); + if(n <= len) + { + size_ = n; + back_ = end_++; + return; + } + n -= len; + } + size_ = 0; + back_ = end_; +} + +template +class prepared_buffers::const_iterator +{ + friend class prepared_buffers; + + using iter_type = + typename BufferSequence::const_iterator; + + prepared_buffers const* b_; + typename BufferSequence::const_iterator it_; + +public: + using value_type = + typename std::iterator_traits::value_type; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return b_ == other.b_ && it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + if(it_ == b_->back_) + return prepare_buffer(b_->size_, *it_); + return *it_; + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + const_iterator(prepared_buffers const& b, + bool at_end) + : b_(&b) + , it_(at_end ? b.end_ : b.bs_.begin()) + { + } +}; + +template +prepared_buffers:: +prepared_buffers(prepared_buffers&& other) + : prepared_buffers(std::move(other), + std::distance(other.bs_.begin(), other.back_), + std::distance(other.bs_.begin(), other.end_)) +{ +} + +template +prepared_buffers:: +prepared_buffers(prepared_buffers const& other) + : prepared_buffers(other, + std::distance(other.bs_.begin(), other.back_), + std::distance(other.bs_.begin(), other.end_)) +{ +} + +template +auto +prepared_buffers:: +operator=(prepared_buffers&& other) -> + prepared_buffers& +{ + auto const nback = std::distance( + other.bs_.begin(), other.back_); + auto const nend = std::distance( + other.bs_.begin(), other.end_); + bs_ = std::move(other.bs_); + back_ = std::next(bs_.begin(), nback); + end_ = std::next(bs_.begin(), nend); + size_ = other.size_; + return *this; +} + +template +auto +prepared_buffers:: +operator=(prepared_buffers const& other) -> + prepared_buffers& +{ + auto const nback = std::distance( + other.bs_.begin(), other.back_); + auto const nend = std::distance( + other.bs_.begin(), other.end_); + bs_ = other.bs_; + back_ = std::next(bs_.begin(), nback); + end_ = std::next(bs_.begin(), nend); + size_ = other.size_; + return *this; +} + +template +prepared_buffers:: +prepared_buffers(std::size_t n, BufferSequence const& bs) + : bs_(bs) +{ + setup(n); +} + +template +auto +prepared_buffers::begin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +auto +prepared_buffers::end() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +template +inline +prepared_buffers +prepare_buffers(std::size_t n, BufferSequence const& buffers) +{ + return prepared_buffers(n, buffers); +} + +} // beast + +#endif diff --git a/include/beast/impl/static_streambuf.ipp b/include/beast/impl/static_streambuf.ipp new file mode 100644 index 00000000..0d75a820 --- /dev/null +++ b/include/beast/impl/static_streambuf.ipp @@ -0,0 +1,304 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STATIC_STREAMBUF_IPP +#define BEAST_IMPL_STATIC_STREAMBUF_IPP + +#include +#include +#include +#include +#include + +namespace beast { + +class static_streambuf::const_buffers_type +{ + std::size_t n_; + std::uint8_t const* p_; + +public: + using value_type = boost::asio::const_buffer; + + class const_iterator; + + const_buffers_type() = default; + const_buffers_type( + const_buffers_type const&) = default; + const_buffers_type& operator=( + const_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; + +private: + friend class static_streambuf; + + const_buffers_type( + std::uint8_t const* p, std::size_t n) + : n_(n) + , p_(p) + { + } +}; + +class static_streambuf::const_buffers_type::const_iterator +{ + std::size_t n_; + std::uint8_t const* p_; + +public: + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return p_ == other.p_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return value_type{p_, n_}; + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + p_ += n_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + p_ -= n_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + friend class const_buffers_type; + + const_iterator( + std::uint8_t const* p, std::size_t n) + : n_(n) + , p_(p) + { + } +}; + +inline +auto +static_streambuf::const_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{p_, n_}; +} + +inline +auto +static_streambuf::const_buffers_type::end() const -> + const_iterator +{ + return const_iterator{p_ + n_, n_}; +} + +//------------------------------------------------------------------------------ + +class static_streambuf::mutable_buffers_type +{ + std::size_t n_; + std::uint8_t* p_; + +public: + using value_type = boost::asio::mutable_buffer; + + class const_iterator; + + mutable_buffers_type() = default; + mutable_buffers_type( + mutable_buffers_type const&) = default; + mutable_buffers_type& operator=( + mutable_buffers_type const&) = default; + + const_iterator + begin() const; + + const_iterator + end() const; + +private: + friend class static_streambuf; + + mutable_buffers_type( + std::uint8_t* p, std::size_t n) + : n_(n) + , p_(p) + { + } +}; + +class static_streambuf::mutable_buffers_type::const_iterator +{ + std::size_t n_; + std::uint8_t* p_; + +public: + using value_type = boost::asio::mutable_buffer; + using pointer = value_type const*; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return p_ == other.p_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return value_type{p_, n_}; + } + + pointer + operator->() const = delete; + + const_iterator& + operator++() + { + p_ += n_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + p_ -= n_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + +private: + friend class mutable_buffers_type; + + const_iterator(std::uint8_t* p, std::size_t n) + : n_(n) + , p_(p) + { + } +}; + +inline +auto +static_streambuf::mutable_buffers_type::begin() const -> + const_iterator +{ + return const_iterator{p_, n_}; +} + +inline +auto +static_streambuf::mutable_buffers_type::end() const -> + const_iterator +{ + return const_iterator{p_ + n_, n_}; +} + +//------------------------------------------------------------------------------ + +inline +auto +static_streambuf::prepare(std::size_t n) -> + mutable_buffers_type +{ + if(n > static_cast(end_ - out_)) + throw std::length_error("no space in streambuf"); + last_ = out_ + n; + return mutable_buffers_type{out_, n}; +} + +inline +auto +static_streambuf::data() const -> + const_buffers_type +{ + return const_buffers_type{in_, + static_cast(out_ - in_)}; +} + +} // beast + +#endif diff --git a/include/beast/impl/streambuf_readstream.ipp b/include/beast/impl/streambuf_readstream.ipp new file mode 100644 index 00000000..e35e9313 --- /dev/null +++ b/include/beast/impl/streambuf_readstream.ipp @@ -0,0 +1,256 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STREAMBUF_READSTREAM_IPP +#define BEAST_IMPL_STREAMBUF_READSTREAM_IPP + +#include +#include +#include + +namespace beast { + +template +template +class streambuf_readstream< + Stream, Streambuf>::read_some_op +{ + using alloc_type = + handler_alloc; + + struct data + { + streambuf_readstream& brs; + MutableBufferSequence bs; + Handler h; + int state = 0; + + template + data(DeducedHandler&& h_, + streambuf_readstream& brs_, + MutableBufferSequence const& bs_) + : brs(brs_) + , bs(bs_) + , h(std::forward(h_)) + { + } + }; + + std::shared_ptr d_; + +public: + read_some_op(read_some_op&&) = default; + read_some_op(read_some_op const&) = default; + + template + read_some_op(DeducedHandler&& h, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), + std::forward(args)...)) + { + (*this)(error_code{}, 0); + } + + void + operator()(error_code const& ec, + std::size_t bytes_transferred); + + friend + void* asio_handler_allocate( + std::size_t size, read_some_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_some_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_some_op* op) + { + return boost_asio_handler_cont_helpers:: + is_continuation(op->d_->h); + } + + template + friend + void asio_handler_invoke(Function&& f, read_some_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +streambuf_readstream:: +read_some_op::operator()( + error_code const& ec, std::size_t bytes_transferred) +{ + auto& d = *d_; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.brs.sb_.size() == 0) + { + d.state = + d.brs.size_ > 0 ? 2 : 1; + break; + } + d.state = 4; + d.brs.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + + case 1: + // read (unbuffered) + d.state = 99; + d.brs.next_layer_.async_read_some( + d.bs, std::move(*this)); + return; + + case 2: + // read + d.state = 3; + d.brs.next_layer_.async_read_some( + d.brs.sb_.prepare(d.brs.size_), + std::move(*this)); + return; + + // got data + case 3: + d.state = 4; + d.brs.sb_.commit(bytes_transferred); + break; + + // copy + case 4: + bytes_transferred = + boost::asio::buffer_copy( + d.bs, d.brs.sb_.data()); + d.brs.sb_.consume(bytes_transferred); + // call handler + d.state = 99; + break; + } + } + d.h(ec, bytes_transferred); +} + +//------------------------------------------------------------------------------ + +template +template +streambuf_readstream:: +streambuf_readstream(Args&&... args) + : next_layer_(std::forward(args)...) +{ + static_assert(is_Stream::value, + "Stream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); +} + +template +template +auto +streambuf_readstream:: +async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler) -> + typename async_completion< + WriteHandler, void(error_code)>::result_type +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(is_Handler::value, + "WriteHandler requirements not met"); + return next_layer_.async_write_some(buffers, + std::forward(handler)); +} + +template +template +std::size_t +streambuf_readstream:: +read_some( + MutableBufferSequence const& buffers) +{ + static_assert(is_MutableBufferSequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + error_code ec; + auto n = read_some(buffers, ec); + if(ec) + throw boost::system::system_error{ec}; + return n; +} + +template +template +std::size_t +streambuf_readstream:: +read_some(MutableBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_MutableBufferSequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + if(buffer_size(buffers) == 0) + return 0; + if(size_ == 0) + return next_layer_.read_some(buffers, ec); + if(sb_.size() == 0) + { + sb_.commit(next_layer_.read_some( + sb_.prepare(size_), ec)); + if(ec) + return 0; + } + auto bytes_transferred = + buffer_copy(buffers, sb_.data()); + sb_.consume(bytes_transferred); + return bytes_transferred; +} + +template +template +auto +streambuf_readstream:: +async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler) -> + typename async_completion< + ReadHandler, void(error_code)>::result_type +{ + static_assert(is_MutableBufferSequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + beast::async_completion< + ReadHandler, void(error_code, std::size_t) + > completion(handler); + read_some_op{ + completion.handler, *this, buffers}; + return completion.result.get(); +} + +} // beast + +#endif diff --git a/include/beast/placeholders.hpp b/include/beast/placeholders.hpp new file mode 100644 index 00000000..2ba1a7af --- /dev/null +++ b/include/beast/placeholders.hpp @@ -0,0 +1,29 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_PLACEHOLDERS_HPP +#define BEAST_PLACEHOLDERS_HPP + +#include + +namespace beast { +namespace asio { + +namespace placeholders { +// asio placeholders that work with std::bind +namespace { +static auto const error (std::placeholders::_1); +static auto const bytes_transferred (std::placeholders::_2); +static auto const iterator (std::placeholders::_2); +static auto const signal_number (std::placeholders::_2); +} +} + +} +} + +#endif diff --git a/include/beast/prepare_buffers.hpp b/include/beast/prepare_buffers.hpp new file mode 100644 index 00000000..cb3a0099 --- /dev/null +++ b/include/beast/prepare_buffers.hpp @@ -0,0 +1,157 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_PREPARE_BUFFERS_HPP +#define BEAST_PREPARE_BUFFERS_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** Get a trimmed const buffer. + + The new buffer starts at the beginning of the passed + buffer. Ownership of the underlying memory is not + transferred. +*/ +inline +boost::asio::const_buffer +prepare_buffer(std::size_t n, + boost::asio::const_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + std::min(n, buffer_size(buffer)) }; +} + +/** Get a trimmed mutable buffer. + + The new buffer starts at the beginning of the passed + buffer. Ownership of the underlying memory is not + transferred. +*/ +inline +boost::asio::mutable_buffer +prepare_buffer(std::size_t n, + boost::asio::mutable_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + std::min(n, buffer_size(buffer)) }; +} + +/** Wrapper to produce a trimmed buffer sequence. + + This wraps a buffer sequence to efficiently present a shorter + subset of the original list of buffers starting with the first + byte of the original sequence. + + @tparam BufferSequence The buffer sequence to wrap. +*/ +template +class prepared_buffers +{ + using iter_type = + typename BufferSequence::const_iterator; + + BufferSequence bs_; + iter_type back_; + iter_type end_; + std::size_t size_; + + template + prepared_buffers(Deduced&& other, + std::size_t nback, std::size_t nend) + : bs_(std::forward(other).bs_) + , back_(std::next(bs_.begin(), nback)) + , end_(std::next(bs_.begin(), nend)) + , size_(other.size_) + { + } + +public: + /// The type for each element in the list of buffers. + using value_type = + typename std::iterator_traits::value_type; + +#if GENERATING_DOCS + /// A bidirectional iterator type that may be used to read elements. + using const_iterator = implementation_defined; + +#else + class const_iterator; + +#endif + + /// Move constructor. + prepared_buffers(prepared_buffers&&); + + /// Copy constructor. + prepared_buffers(prepared_buffers const&); + + /// Move assignment. + prepared_buffers& operator=(prepared_buffers&&); + + /// Copy assignment. + prepared_buffers& operator=(prepared_buffers const&); + + /** Construct a wrapped buffer sequence. + + @param n The maximum number of bytes in the wrapped sequence. + If this is larger than the size of buffers, the wrapped + sequence will represent the entire input sequence. + + @param buffers The buffer sequence to wrap. A copy of the sequence + will be made, but ownership of the underlying memory is not transferred. + */ + prepared_buffers(std::size_t n, BufferSequence const& buffers); + + /// Get a bidirectional iterator to the first element. + const_iterator + begin() const; + + /// Get a bidirectional iterator for one past the last element. + const_iterator + end() const; + +private: + void + setup(std::size_t n); +}; + +//------------------------------------------------------------------------------ + +/** Return a trimmed, wrapped buffer sequence. + + This function returns a new buffer sequence which wraps the provided + buffer sequence and efficiently presents a shorter subset of the + original list of buffers starting with the first byte of the original + sequence. + + @param n The maximum number of bytes in the wrapped sequence. If this + is larger than the size of buffers, the wrapped sequence will represent + the entire input sequence. + + @param buffers The buffer sequence to wrap. A copy of the sequence + will be made, but ownership of the underlying memory is not transferred. +*/ +template +prepared_buffers +prepare_buffers(std::size_t n, BufferSequence const& buffers); + +} // beast + +#include + +#endif diff --git a/include/beast/static_streambuf.hpp b/include/beast/static_streambuf.hpp new file mode 100644 index 00000000..36c298d6 --- /dev/null +++ b/include/beast/static_streambuf.hpp @@ -0,0 +1,177 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STATIC_STREAMBUF_HPP +#define BEAST_STATIC_STREAMBUF_HPP + +#include +#include +#include +#include + +namespace beast { + +/** A `Streambuf` with a fixed size internal buffer. + + Ownership of the underlying storage belongs to the derived class. + + @note Variables are usually declared using the template class + `static_streambuf_n`; however, to reduce the number of instantiations + of template functions receiving static stream buffer arguments in a + deduced context, the signature of the receiving function should use + `static_streambuf`. +*/ +class static_streambuf +{ +#if GENERATING_DOCS +private: +#else +protected: +#endif + std::uint8_t* in_; + std::uint8_t* out_; + std::uint8_t* last_; + std::uint8_t* end_; + +public: +#if GENERATING_DOCS + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = implementation_defined; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = implementation_defined; + +#else + class const_buffers_type; + class mutable_buffers_type; + + static_streambuf( + static_streambuf const& other) noexcept = delete; + + static_streambuf& operator=( + static_streambuf const&) noexcept = delete; + +#endif + + /// Returns the largest size output sequence possible. + std::size_t + max_size() const + { + return end_ - in_; + } + + /// Get the size of the input sequence. + std::size_t + size() const + { + return out_ - in_; + } + + /** Get a list of buffers that represents the output sequence, with the given size. + + @throws std::length_error if the size would exceed the limit + imposed by the underlying mutable buffer sequence. + */ + mutable_buffers_type + prepare(std::size_t n); + + /// Move bytes from the output sequence to the input sequence. + void + commit(std::size_t n) + { + out_ += std::min(n, last_ - out_); + } + + /// Get a list of buffers that represents the input sequence. + const_buffers_type + data() const; + + /// Remove bytes from the input sequence. + void + consume(std::size_t n) + { + in_ += std::min(n, out_ - in_); + } + +#if GENERATING_DOCS +private: +#else +protected: +#endif + static_streambuf(std::uint8_t* p, std::size_t n) + { + reset(p, n); + } + + void + reset(std::uint8_t* p, std::size_t n) + { + in_ = p; + out_ = p; + last_ = p; + end_ = p + n; + } +}; + +//------------------------------------------------------------------------------ + +/** A `Streambuf` with a fixed size internal buffer. + + @tparam N The number of bytes in the internal buffer. + + @note To reduce the number of template instantiations when passing + objects of this type in a deduced context, the signature of the + receiving function should use `static_streambuf` instead. +*/ +template +class static_streambuf_n + : private boost::base_from_member< + std::array> + , public static_streambuf +{ + using member_type = boost::base_from_member< + std::array>; +public: +#if GENERATING_DOCS +private: +#endif + static_streambuf_n( + static_streambuf_n const&) = delete; + static_streambuf_n& operator=( + static_streambuf_n const&) = delete; +#if GENERATING_DOCS +public: +#endif + + /// Construct a static stream buffer. + static_streambuf_n() + : static_streambuf( + member_type::member.data(), + member_type::member.size()) + { + } + + /** Reset the stream buffer. + + Postconditions: + The input sequence and output sequence are empty, + `max_size()` returns `N`. + */ + void + reset() + { + static_streambuf::reset( + member_type::member.data(), + member_type::member.size()); + } +}; + +} // beast + +#include + +#endif diff --git a/include/beast/streambuf.hpp b/include/beast/streambuf.hpp new file mode 100644 index 00000000..04ab4b3e --- /dev/null +++ b/include/beast/streambuf.hpp @@ -0,0 +1,19 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STREAMBUF_HPP +#define BEAST_STREAMBUF_HPP + +#include + +namespace beast { + +using streambuf = basic_streambuf>; + +} // beast + +#endif diff --git a/include/beast/streambuf_readstream.hpp b/include/beast/streambuf_readstream.hpp new file mode 100644 index 00000000..7d88ee81 --- /dev/null +++ b/include/beast/streambuf_readstream.hpp @@ -0,0 +1,250 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STREAMBUF_READSTREAM_HPP +#define BEAST_STREAMBUF_READSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A `Stream` with attached `Streambuf` to buffer reads. + + This wraps a `Stream` implementation so that calls to write are + passed through to the underlying stream, while calls to read will + first consume the input sequence stored in a `Streambuf` which + is part of the object. + + The use-case for this class is different than that of the + `boost::asio::buffered_readstream`. It is designed to facilitate + the use of `boost::asio::read_until`, and to allow buffers + acquired during detection of handshakes to be made transparently + available to callers. A hypothetical implementation of the + buffered version of `boost::asio::ssl::stream::async_handshake` + could make use of this wrapper. + + Uses: + + * Transparently leave untouched input acquired in calls + to `boost::asio::read_until` behind for subsequent callers. + + * "Preload" a stream with handshake input data acquired + from other sources. + + Example: + @code + // Process the next HTTP headers on the stream, + // leaving excess bytes behind for the next call. + // + template + void process_http_message( + streambuf_readstream& stream) + { + // Read up to and including the end of the HTTP + // headers, leaving the sequence in the stream's + // buffer. read_until may read past the end of the + // headers; the return value will include only the + // part up to the end of the delimiter. + // + std::size_t bytes_transferred = + boost::asio::read_until( + stream.next_layer(), stream.buffer(), "\r\n\r\n"); + + // Use prepare_buffers() to limit the input + // sequence to only the data up to and including + // the trailing "\r\n\r\n". + // + auto header_buffers = prepare_buffers( + bytes_transferred, stream.buffer().data()); + + ... + + // Discard the portion of the input corresponding + // to the HTTP headers. + // + stream.buffer().consume(bytes_transferred); + + // Everything we read from the stream + // is part of the content-body. + } + @endcode + + @tparam Stream The type of stream to wrap. + + @tparam Streambuf The type of stream buffer to use. +*/ +template +class streambuf_readstream +{ + using error_code = boost::system::error_code; + + template + class read_some_op; + + Streambuf sb_; + std::size_t size_ = 0; + Stream next_layer_; + +public: + /// The type of the internal buffer + using streambuf_type = Streambuf; + + /// The type of the next layer. + using next_layer_type = + typename std::remove_reference::type; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// Move constructor. + streambuf_readstream(streambuf_readstream&&) = default; + + /** Construct the wrapping stream. + + @param args Parameters forwarded to the `Stream` constructor. + */ + template + explicit + streambuf_readstream(Args&&... args); + + /// Get a reference to the next layer. + next_layer_type& + next_layer() + { + return next_layer_; + } + + /// Get a reference to the lowest layer. + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /// Get a const reference to the lowest layer. + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /// Get the io_service associated with the object. + boost::asio::io_service& + get_io_service() + { + return next_layer_.get_io_service(); + } + + /** Access the internal buffer. + + The internal buffer is returned. It is possible for the + caller to break invariants with this function. For example, + by causing the internal buffer size to increase beyond + the caller defined maximum. + */ + Streambuf& + buffer() + { + return sb_; + } + + /** Access the internal buffer. + + The internal buffer is returned. It is possible for the + caller to break invariants with this function. For example, + by causing the internal buffer size to increase beyond + the caller defined maximum. + */ + Streambuf const& + buffer() const + { + return sb_; + } + + /** Set the maximum buffer size. + + This changes the maximum size of the internal buffer used + to hold read data. No bytes are discarded by this call. If + the buffer size is set to zero, no more data will be buffered. + + Thread safety: + The caller is responsible for making sure the call is + made from the same implicit or explicit strand. + + @param size The number of bytes in the read buffer. + + @note This is a soft limit. If the new maximum size is smaller + than the amount of data in the buffer, no bytes are discarded. + */ + void + reserve(std::size_t size) + { + size_ = size; + } + + /// Write the given data to the stream. Returns the number of bytes written. + /// Throws an exception on failure. + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return next_layer_.write_some(buffers); + } + + /// Write the given data to the stream. Returns the number of bytes written, + /// or 0 if an error occurred. + template + std::size_t + write_some(ConstBufferSequence const& buffers, + error_code& ec) + { + return next_layer_.write_some(buffers, ec); + } + + /// Start an asynchronous write. The data being written must be valid for the + /// lifetime of the asynchronous operation. + template + typename async_completion< + WriteHandler, void(error_code)>::result_type + async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + /// Read some data from the stream. Returns the number of bytes read. + /// Throws an exception on failure. + template + std::size_t + read_some(MutableBufferSequence const& buffers); + + /// Read some data from the stream. Returns the number of bytes read + /// or 0 if an error occurred. + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec); + + /// Start an asynchronous read. The buffer into which the data will be read + /// must be valid for the lifetime of the asynchronous operation. + template + typename async_completion< + ReadHandler, void(error_code)>::result_type + async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler); +}; + +} // beast + +#include + +#endif diff --git a/include/beast/type_check.hpp b/include/beast/type_check.hpp new file mode 100644 index 00000000..43c49f9b --- /dev/null +++ b/include/beast/type_check.hpp @@ -0,0 +1,348 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TYPE_CHECK_HPP +#define BEAST_TYPE_CHECK_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +//------------------------------------------------------------------------------ + +// Types that meet the requirements, +// for use with std::declval only. +// + +#if GENERATING_DOCS +namespace detail { +#else +namespace concept { +#endif + +template +struct BufferSequence +{ + using value_type = BufferType; + using const_iterator = BufferType const*; + ~BufferSequence(); + BufferSequence(BufferSequence const&) = default; + const_iterator + begin() const noexcept; + const_iterator + end() const noexcept; +}; + +using ConstBufferSequence = + BufferSequence; + +using MutableBufferSequence = + BufferSequence; + +struct StreamHandler +{ + StreamHandler(StreamHandler const&) = default; + void + operator()(boost::system::error_code ec, + std::size_t); +}; + +using ReadHandler = StreamHandler; +using WriteHandler = StreamHandler; + +} // concept + +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html +// +/// Determine if `T` meets the requirements of `BufferSequence`. +template +class is_BufferSequence +{ + template > + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template::iterator_category>> + #else + // workaround: + // boost::asio::detail::consuming_buffers::const_iterator + // is not bidirectional + std::forward_iterator_tag, + typename std::iterator_traits< + typename U::const_iterator>::iterator_category>> + #endif + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + + template().begin()), + typename U::const_iterator>::type> + static R check3(int); + template + static std::false_type check3(...); + using type3 = decltype(check3(0)); + + template().end()), + typename U::const_iterator>::type> + static R check4(int); + template + static std::false_type check4(...); + using type4 = decltype(check4(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + std::is_copy_constructible::value && + std::is_destructible::value && + type1::value && type2::value && + type3::value && type4::value; +}; + +#if ! GENERATING_DOCS + +/// Determine if `T` meets the requirements of `ConstBufferSequence`. +template +using is_ConstBufferSequence = + is_BufferSequence; +static_assert(is_ConstBufferSequence::value, ""); +static_assert(! is_ConstBufferSequence::value, ""); + +/// Determine if `T` meets the requirements of `MutableBufferSequence`. +template +using is_MutableBufferSequence = + is_BufferSequence; +static_assert(is_MutableBufferSequence::value, ""); +static_assert(! is_MutableBufferSequence::value, ""); + +#endif + +//------------------------------------------------------------------------------ + +/// Determine if `T` has the `get_io_service` member. +template +class has_get_io_service +{ + template().get_io_service()), + boost::asio::io_service&>> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = type::value; +}; +static_assert(! has_get_io_service::value, ""); + +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html +// +/// Determine if `T` meets the requirements of `AsyncReadStream`. +template +class is_AsyncReadStream +{ + template().async_read_some( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + has_get_io_service::value && type::value; +}; +static_assert(! is_AsyncReadStream::value, ""); + +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncWriteStream.html +// +/// Determine if `T` meets the requirements of `AsyncWriteStream`. +template +class is_AsyncWriteStream +{ + template().async_write_some( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + has_get_io_service::value && type::value; +}; +static_assert(! is_AsyncWriteStream::value, ""); + +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html +// +/// Determine if `T` meets the requirements of `SyncReadStream`. +template +class is_SyncReadStream +{ + using error_code = + boost::system::error_code; + + template().read_some( + std::declval())), + std::size_t>> + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template().read_some( + std::declval(), + std::declval())), std::size_t>> + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + type1::value && type2::value; +}; +static_assert(! is_SyncReadStream::value, ""); + +// http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncWriteStream.html +// +/// Determine if `T` meets the requirements of `SyncWriterStream`. +template +class is_SyncWriteStream +{ + using error_code = + boost::system::error_code; + + template().write_some( + std::declval())), + std::size_t>> + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template().write_some( + std::declval(), + std::declval())), std::size_t>> + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + type1::value && type2::value; +}; +static_assert(! is_SyncWriteStream::value, ""); + +/// Determine if `T` meets the requirements of `Stream`. +template +struct is_Stream +{ +/// `true` if `T` meets the requirements. + static bool const value = + is_AsyncReadStream::value && + is_AsyncWriteStream::value && + is_SyncReadStream::value && + is_SyncWriteStream::value; +}; + +/// Determine if `T` meets the requirements of `Streambuf`. +template +class is_Streambuf +{ + template().prepare(1))>::value>> + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template().data())>::value>> + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + + template().commit(1), std::true_type{})> + static R check3(int); + template + static std::false_type check3(...); + using type3 = decltype(check3(0)); + + template().consume(1), std::true_type{})> + static R check4(int); + template + static std::false_type check4(...); + using type4 = decltype(check4(0)); + + template().size()), std::size_t>> + static R check5(int); + template + static std::false_type check5(...); + using type5 = decltype(check5(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + type1::value && type2::value && + type3::value && type4::value && + type5::value; +}; + +#if ! GENERATING_DOCS + +/// Determine if `T` meets the requirements of `CompletionHandler`. +template +using is_Handler = std::integral_constant::type>::value && + detail::is_call_possible::value>; + +#endif + +} // beast + +#endif diff --git a/include/beast/version.hpp b/include/beast/version.hpp new file mode 100644 index 00000000..76039b1c --- /dev/null +++ b/include/beast/version.hpp @@ -0,0 +1,21 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_VERSION_HPP +#define BEAST_VERSION_HPP + +// follows http://semver.org + +// BEAST_VERSION % 100 is the patch level +// BEAST_VERSION / 100 % 1000 is the minor version +// BEAST_VERSION / 100000 is the major version +// +#define BEAST_VERSION 100000 + +#define BEAST_VERSION_STRING "1.0.0-b2" + +#endif diff --git a/include/beast/websocket.hpp b/include/beast/websocket.hpp new file mode 100644 index 00000000..2baebdfc --- /dev/null +++ b/include/beast/websocket.hpp @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEBSOCKET_HPP +#define BEAST_WEBSOCKET_HPP + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/beast/websocket/detail/debug.hpp b/include/beast/websocket/detail/debug.hpp new file mode 100644 index 00000000..91de367b --- /dev/null +++ b/include/beast/websocket/detail/debug.hpp @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DEBUG_H_INCLUDED +#define BEAST_WSPROTO_DEBUG_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +std::string +to_hex(boost::asio::const_buffer b) +{ + using namespace boost::asio; + std::stringstream ss; + auto p = buffer_cast(b); + auto n = buffer_size(b); + while(n--) + { + ss << + std::setfill('0') << + std::setw(2) << + std::hex << int(*p++) << " "; + } + return ss.str(); +} + +template +std::string +to_hex(Buffers const& bs) +{ + std::string s; + for(auto const& b : bs) + s.append(to_hex(boost::asio::const_buffer(b))); + return s; +} + +template +std::string +buffers_to_string(Buffers const& bs) +{ + using namespace boost::asio; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +template +std::string +format(std::string s) +{ + auto const w = 84; + for(int n = w*(s.size()/w); n>0; n-=w) + s.insert(n, 1, '\n'); + return s; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp new file mode 100644 index 00000000..c0492477 --- /dev/null +++ b/include/beast/websocket/detail/decorator.hpp @@ -0,0 +1,100 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP +#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP + +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +using request_type = http::request; + +using response_type = http::response; + +struct abstract_decorator +{ + virtual + ~abstract_decorator() = default; + + virtual + void + operator()(request_type& req) = 0; + + virtual + void + operator()(response_type& resp) = 0; +}; + +template +class decorator : public abstract_decorator +{ + T t_; + +public: + decorator() = default; + + decorator(T&& t) + : t_(std::move(t)) + { + } + + decorator(T const& t) + : t_(t) + { + } + + void + operator()(request_type& req) override + { + t_(req); + } + + void + operator()(response_type& resp) override + { + t_(resp); + } +}; + +struct default_decorator +{ + static + char const* + version() + { + return "Beast.WSProto/1.0"; + } + + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", version()); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", version()); + } +}; + +using decorator_type = + std::unique_ptr; + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/endian.hpp b/include/beast/websocket/detail/endian.hpp new file mode 100644 index 00000000..a92b062a --- /dev/null +++ b/include/beast/websocket/detail/endian.hpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_ENDIAN_HPP +#define BEAST_WEBSOCKET_DETAIL_ENDIAN_HPP + +#include + +namespace beast { +namespace websocket { +namespace detail { + +inline +std::uint16_t +big_uint16_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return (p[0]<<8) + p[1]; +} + +inline +std::uint64_t +big_uint64_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + (static_cast(p[0])<<56) + + (static_cast(p[1])<<48) + + (static_cast(p[2])<<40) + + (static_cast(p[3])<<32) + + (static_cast(p[4])<<24) + + (static_cast(p[5])<<16) + + (static_cast(p[6])<< 8) + + p[7]; +} + +inline +std::uint32_t +little_uint32_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + p[0] + + (static_cast(p[1])<< 8) + + (static_cast(p[2])<<16) + + (static_cast(p[3])<<24); +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/error.hpp b/include/beast/websocket/detail/error.hpp new file mode 100644 index 00000000..0495a9ae --- /dev/null +++ b/include/beast/websocket/detail/error.hpp @@ -0,0 +1,92 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_ERROR_HPP +#define BEAST_WEBSOCKET_DETAIL_ERROR_HPP + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace websocket { +namespace detail { + +class error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "wsproto"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case error::closed: return "WebSocket connection closed normally"; + case error::failed: return "WebSocket connection failed due to a protocol violation"; + case error::handshake_failed: return "WebSocket Upgrade handshake failed"; + case error::keep_alive: return "WebSocket Upgrade handshake failed but connection is still open"; + + case error::response_malformed: return "malformed HTTP response"; + case error::response_failed: return "upgrade request failed"; + case error::response_denied: return "upgrade request denied"; + case error::request_malformed: return "malformed HTTP request"; + case error::request_invalid: return "upgrade request invalid"; + case error::request_denied: return "upgrade request denied"; + default: + return "wsproto.error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition(ev, *this); + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_error_category() +{ + static detail::error_category const cat{}; + return cat; +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/frame.hpp b/include/beast/websocket/detail/frame.hpp new file mode 100644 index 00000000..6ddbf1ac --- /dev/null +++ b/include/beast/websocket/detail/frame.hpp @@ -0,0 +1,385 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_FRAME_HPP +#define BEAST_WEBSOCKET_DETAIL_FRAME_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +// Contents of a WebSocket frame header +struct frame_header +{ + opcode op; + bool fin; + bool mask; + bool rsv1; + bool rsv2; + bool rsv3; + std::uint64_t len; + std::uint32_t key; +}; + +// holds the largest possible frame header +using fh_streambuf = + static_streambuf_n<14>; + +// holds the largest possible control frame +using frame_streambuf = + static_streambuf_n< 2 + 8 + 4 + 125 >; + +inline +bool constexpr +is_reserved(opcode op) +{ + return + (op >= opcode::rsv3 && op <= opcode::rsv7) || + (op >= opcode::crsvb && op <= opcode::crsvf); +} + +inline +bool constexpr +is_valid(opcode op) +{ + return op <= opcode::crsvf; +} + +inline +bool constexpr +is_control(opcode op) +{ + return op >= opcode::close; +} + +// Returns `true` if a close code is valid +inline +bool +is_valid(close_code code) +{ + auto const v = static_cast< + std::uint16_t>(code); + switch(v) + { + case 1000: + case 1001: + case 1002: + case 1003: + case 1007: + case 1008: + case 1009: + case 1010: + case 1011: + case 1012: + case 1013: + return true; + + // explicitly reserved + case 1004: + case 1005: + case 1006: + case 1014: + case 1015: + return false; + } + // reserved + if(v >= 1016 && v <= 2999) + return false; + // not used + if(v >= 0 && v <= 999) + return false; + return true; +} + +//------------------------------------------------------------------------------ + +// Write frame header to streambuf +// +template +void +write(Streambuf& sb, frame_header const& fh) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using namespace boost::endian; + std::size_t n; + std::uint8_t b[14]; + b[0] = (fh.fin ? 0x80 : 0x00) | static_cast(fh.op); + b[1] = fh.mask ? 0x80 : 0x00; + if (fh.len <= 125) + { + b[1] |= fh.len; + n = 2; + } + else if (fh.len <= 65535) + { + b[1] |= 126; + ::new(&b[2]) big_uint16_buf_t{ + (std::uint16_t)fh.len}; + n = 4; + } + else + { + b[1] |= 127; + ::new(&b[2]) big_uint64_buf_t{fh.len}; + n = 10; + } + if(fh.mask) + { + little_uint32_buf_t key(fh.key); + std::copy(key.data(), + key.data() + 4, &b[n]); + n += 4; + } + sb.commit(buffer_copy( + sb.prepare(n), buffer(b))); +} + +// Read fixed frame header +// Requires at least 2 bytes +// +template +std::size_t +read_fh1(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + std::size_t need; + fh.len = b[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + if((fh.mask = (b[1] & 0x80) != 0)) + need += 4; + fh.op = static_cast(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + // invalid length for control message + if(is_control(fh.op) && fh.len > 125) + { + code = close_code::protocol_error; + return 0; + } + // reserved bits not cleared + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + code = close_code::protocol_error; + return 0; + } + // reserved opcode + if(is_reserved(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // invalid opcode + // (only in locally generated headers) + if(! is_valid(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // fragmented control message + if(is_control(fh.op) && ! fh.fin) + { + code = close_code::protocol_error; + return 0; + } + // unmasked frame from client + if(role == role_type::server && ! fh.mask) + { + code = close_code::protocol_error; + return 0; + } + // masked frame from server + if(role == role_type::client && fh.mask) + { + code = close_code::protocol_error; + return 0; + } + code = close_code::none; + return need; +} + +// Decode variable frame header from stream +// +template +void +read_fh2(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + switch(fh.len) + { + case 126: + { + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + #if 0 + // Causes strict-aliasing warning in gcc + fh.len = reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value(); + #else + fh.len = big_uint16_to_native(&b[0]); + #endif + // length not canonical + if(fh.len < 126) + { + code = close_code::protocol_error; + return; + } + break; + } + case 127: + { + std::uint8_t b[8]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + #if 0 + // Causes strict-aliasing warning in gcc + fh.len = reinterpret_cast< + big_uint64_buf_t const*>(&b[0])->value(); + #else + fh.len = big_uint64_to_native(&b[0]); + #endif + // length not canonical + if(fh.len < 65536) + { + code = close_code::protocol_error; + return; + } + break; + } + } + if(fh.mask) + { + std::uint8_t b[4]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + #if 0 + // Causes strict-aliasing warning in gcc + fh.key = reinterpret_cast< + little_uint32_buf_t const*>(&b[0])->value(); + #else + fh.key = little_uint32_to_native(&b[0]); + #endif + } + code = close_code::none; +} + +// Read data from buffers +// This is for ping and pong payloads +// +template +void +read(ping_payload_type& data, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + assert(buffer_size(bs) <= data.max_size()); + data.resize(buffer_size(bs)); + buffer_copy(mutable_buffers_1{ + data.data(), data.size()}, bs); +} + +// Read close_reason, return true on success +// This is for the close payload +// +template +void +read(close_reason& cr, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + auto n = buffer_size(bs); + assert(n <= 125); + if(n == 0) + { + cr = close_reason{}; + code = close_code::none; + return; + } + if(n == 1) + { + code = close_code::protocol_error; + return; + } + consuming_buffers cb(bs); + { + std::uint8_t b[2]; + buffer_copy(buffer(b), cb); + #if 0 + // Causes strict-aliasing warning in gcc + cr.code = static_cast( + reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value()); + #else + cr.code = static_cast( + big_uint16_to_native(&b[0])); + #endif + cb.consume(2); + n -= 2; + if(! is_valid(cr.code)) + { + code = close_code::protocol_error; + return; + } + } + if(n > 0) + { + cr.reason.resize(n); + buffer_copy(buffer(&cr.reason[0], n), cb); + if(! detail::check_utf8( + cr.reason.data(), cr.reason.size())) + { + code = close_code::protocol_error; + return; + } + } + else + { + cr.reason = ""; + } + code = close_code::none; +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/hybi13.hpp b/include/beast/websocket/detail/hybi13.hpp new file mode 100644 index 00000000..831469c8 --- /dev/null +++ b/include/beast/websocket/detail/hybi13.hpp @@ -0,0 +1,57 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_HYBI13_HPP +#define BEAST_WEBSOCKET_DETAIL_HYBI13_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +std::string +make_sec_ws_key(Gen& g) +{ + union U + { + std::array a4; + std::array a16; + }; + U u; + for(int i = 0; i < 4; ++i) + u.a4[i] = g(); + return beast::detail::base64_encode(u.a16.data(), u.a16.size()); +} + +template +std::string +make_sec_ws_accept(boost::string_ref const& key) +{ + std::string s(key.data(), key.size()); + s += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + beast::detail::sha1_context ctx; + beast::detail::init(ctx); + beast::detail::update(ctx, s.data(), s.size()); + std::array digest; + beast::detail::finish(ctx, digest.data()); + return beast::detail::base64_encode( + digest.data(), digest.size()); +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/invokable.hpp b/include/beast/websocket/detail/invokable.hpp new file mode 100644 index 00000000..6fd24d29 --- /dev/null +++ b/include/beast/websocket/detail/invokable.hpp @@ -0,0 +1,149 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP +#define BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +// "Parks" a composed operation, to invoke later +// +class invokable +{ + struct base + { + base() = default; + base(base &&) = default; + virtual ~base() = default; + virtual void move(void* p) = 0; + virtual void operator()() = 0; + }; + + template + struct holder : base + { + F f; + + holder(holder&&) = default; + + template + explicit + holder(U&& u) + : f(std::forward(u)) + { + } + + void + move(void* p) override + { + ::new(p) holder(std::move(*this)); + } + + void + operator()() override + { + F f_(std::move(f)); + this->~holder(); + // invocation of f_() can + // assign a new invokable. + f_(); + } + }; + + struct exemplar + { + std::shared_ptr _; + void operator()(){} + }; + + using buf_type = char[sizeof(holder)]; + + base* base_ = nullptr; + alignas(holder) buf_type buf_; + +public: +#ifndef NDEBUG + ~invokable() + { + // Engaged invokables must be invoked before + // destruction otherwise the io_service + // invariants are broken w.r.t completions. + assert(! base_); + } +#endif + + invokable() = default; + invokable(invokable const&) = delete; + invokable& operator=(invokable const&) = delete; + + invokable(invokable&& other) + { + if(other.base_) + { + base_ = reinterpret_cast(&buf_[0]); + other.base_->move(buf_); + other.base_ = nullptr; + } + } + + invokable& + operator=(invokable&& other) + { + // Engaged invokables must be invoked before + // assignment otherwise the io_service + // invariants are broken w.r.t completions. + assert(! base_); + + if(other.base_) + { + base_ = reinterpret_cast(&buf_[0]); + other.base_->move(buf_); + other.base_ = nullptr; + } + return *this; + } + + template + void + emplace(F&& f); + + void + maybe_invoke() + { + if(base_) + { + auto const basep = base_; + base_ = nullptr; + (*basep)(); + } + } +}; + +template +void +invokable::emplace(F&& f) +{ + static_assert(sizeof(buf_type) >= sizeof(holder), + "buffer too small"); + assert(! base_); + ::new(buf_) holder(std::forward(f)); + base_ = reinterpret_cast(&buf_[0]); +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/mask.hpp b/include/beast/websocket/detail/mask.hpp new file mode 100644 index 00000000..e37fdca5 --- /dev/null +++ b/include/beast/websocket/detail/mask.hpp @@ -0,0 +1,389 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEBSOCKETDETAIL_MASKGEN_HPP +#define BEAST_WEBSOCKETDETAIL_MASKGEN_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +// Pseudo-random source of mask keys +// +template +class maskgen_t +{ + std::mt19937 g_; + +public: + using result_type = typename std::mt19937::result_type; + + maskgen_t(maskgen_t const&) = delete; + maskgen_t& operator=(maskgen_t const&) = delete; + + maskgen_t(); + + result_type + operator()() noexcept; + + void + rekey(); +}; + +template +maskgen_t<_>::maskgen_t() +{ + rekey(); +} + +template +auto +maskgen_t<_>::operator()() noexcept -> + result_type +{ + for(;;) + if(auto key = g_()) + return key; +} + +template +void +maskgen_t<_>::rekey() +{ + std::random_device rng; + std::array e; + for(auto& i : e) + i = rng(); + std::seed_seq ss(e.begin(), e.end()); + g_.seed(ss); +} + +using maskgen = maskgen_t<>; + +//------------------------------------------------------------------------------ + +//using prepared_key_type = std::size_t; +using prepared_key_type = std::uint32_t; +//using prepared_key_type = std::uint64_t; + +inline +void +prepare_key(std::uint32_t& prepared, std::uint32_t key) +{ + prepared = key; +} + +inline +void +prepare_key(std::uint64_t& prepared, std::uint32_t key) +{ + prepared = + (static_cast(key) << 32) | key; +} + +template +inline +typename std::enable_if::value, T>::type +rol(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast((t << n) | ( + static_cast::type>(t) >> (bits - n))); +} + +template +inline +typename std::enable_if::value, T>::type +ror(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast((t << (bits - n)) | ( + static_cast::type>(t) >> n)); +} + +// 32-bit Uuoptimized +// +template +void +mask_inplace_safe( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit unoptimized +// +template +void +mask_inplace_safe( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + *p ^= (key >>32); ++p; + *p ^= (key >>40); ++p; + *p ^= (key >>48); ++p; + *p ^= (key >>56); ++p; + } + n %= sizeof(key); + switch(n) + { + case 7: p[6] ^= (key >>16); + case 6: p[5] ^= (key >> 8); + case 5: p[4] ^= key; + case 4: p[3] ^= (key >>24); + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 32-bit optimized +template +void +mask_inplace_32( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + auto m = reinterpret_cast< + uintptr_t>(p) % sizeof(key); + switch(m) + { + case 1: *p ^= key ; ++p; --n; + case 2: *p ^= (key >> 8); ++p; --n; + case 3: *p ^= (key >>16); ++p; --n; + key = ror(key, m * 8); + case 0: + break; + } + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint32_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit optimized +// +template +void +mask_inplace_64( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + auto m = reinterpret_cast< + uintptr_t>(p) % sizeof(key); + switch(m) + { + case 1: *p ^= key ; ++p; --n; + case 2: *p ^= (key >> 8); ++p; --n; + case 3: *p ^= (key >>16); ++p; --n; + case 4: *p ^= (key >>24); ++p; --n; + case 5: *p ^= (key >>32); ++p; --n; + case 6: *p ^= (key >>40); ++p; --n; + case 7: *p ^= (key >>48); ++p; --n; + key = ror(key, m * 8); + case 0: + break; + } + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint64_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 32-bit x86 optimized +// +template +void +mask_inplace_x86( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint32_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit amd64 optimized +// +template +void +mask_inplace_amd( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint64_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 7: p[6] ^= (key >>16); + case 6: p[5] ^= (key >> 8); + case 5: p[4] ^= key; + case 4: p[3] ^= (key >>24); + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + mask_inplace_safe(b, key); + //mask_inplace_32(b, key); + //mask_inplace_x86(b, key); +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + mask_inplace_safe(b, key); + //mask_inplace_64(b, key); + //mask_inplace_amd(b, key); +} + +// Apply mask in place +// +template +void +mask_inplace( + MutableBuffers const& bs, KeyType& key) +{ + for(auto const& b : bs) + mask_inplace(b, key); +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp new file mode 100644 index 00000000..743951ec --- /dev/null +++ b/include/beast/websocket/detail/stream_base.hpp @@ -0,0 +1,128 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP +#define BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +inline +void +maybe_throw(error_code const& ec, String const&) +{ + if(ec) + throw boost::system::system_error{ec}; +} + +template +static +std::size_t +clamp(UInt x) +{ + if(x >= std::numeric_limits::max()) + return std::numeric_limits::max(); + return static_cast(x); +} + +template +static +std::size_t +clamp(UInt x, std::size_t limit) +{ + if(x >= limit) + return limit; + return static_cast(x); +} + +//------------------------------------------------------------------------------ + +struct stream_base +{ +protected: + struct op {}; + + detail::maskgen maskgen_; // source of mask keys + decorator_type d_; // adorns http messages + bool keep_alive_ = false; // close on failed upgrade + role_type role_; // server or client + bool error_ = false; // non-zero ec was delivered + + std::size_t rd_msg_max_ = + 16 * 1024 * 1024; // max message size + detail::frame_header rd_fh_; // current frame header + detail::prepared_key_type rd_key_; // prepared masking key + detail::utf8_checker rd_utf8_check_;// for current text msg + std::uint64_t rd_size_; // size of the current message so far + std::uint64_t rd_need_ = 0; // bytes left in msg frame payload + opcode rd_opcode_; // opcode of current msg + bool rd_cont_ = false; // expecting a continuation frame + bool rd_close_ = false; // got close frame + op* rd_block_ = nullptr; // op currently reading + + std::size_t + wr_frag_size_ = 16 * 1024; // size of auto-fragments + std::size_t wr_buf_size_ = 4096; // write buffer size + opcode wr_opcode_ = opcode::text; // outgoing message type + bool wr_close_ = false; // sent close frame + bool wr_cont_ = false; // next write is continuation frame + op* wr_block_ = nullptr; // op currenly writing + + invokable rd_op_; // invoked after write completes + invokable wr_op_; // invoked after read completes + close_reason cr_; // set from received close frame + + stream_base() + : d_(new decorator{}) + { + } + + stream_base(stream_base&&) = default; + stream_base(stream_base const&) = delete; + stream_base& operator=(stream_base&&) = default; + stream_base& operator=(stream_base const&) = delete; + + template + void + prepare_fh(close_code& code); + + template + void + write_close(Streambuf& sb, + close_reason const& rc); + + template + void + write_ping(Streambuf& sb, opcode op, + ping_payload_type const& data); +}; + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/utf8_checker.hpp b/include/beast/websocket/detail/utf8_checker.hpp new file mode 100644 index 00000000..e9a673bb --- /dev/null +++ b/include/beast/websocket/detail/utf8_checker.hpp @@ -0,0 +1,172 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP +#define BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP + +#include +#include +#include // DEPRECATED + +namespace beast { +namespace websocket { +namespace detail { + +// Code adapted from +// http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +/* + Copyright (c) 2008-2009 Bjoern Hoehrmann + + 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. * +*/ +template +class utf8_checker_t +{ + // Table for the UTF8 decode state machine + using lut_type = std::uint8_t[400]; + static + lut_type const& + lut() + { + // 400 elements + static std::uint8_t constexpr tab[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8 + }; + return tab; + } + + std::uint32_t state_ = 0; + std::uint32_t codepoint_ = 0; + +public: + utf8_checker_t() = default; + utf8_checker_t(utf8_checker_t&&) = default; + utf8_checker_t(utf8_checker_t const&) = default; + utf8_checker_t& operator=(utf8_checker_t&&) = default; + utf8_checker_t& operator=(utf8_checker_t const&) = default; + + void + reset(); + + // Returns `true` on success + bool + write(void const* buffer, std::size_t size); + + // Returns `true` on success + template + bool + write(BufferSequence const& bs); + + // Returns `true` on success + bool + finish(); +}; + +template +void +utf8_checker_t<_>::reset() +{ + state_ = 0; + codepoint_ = 0; +} + +template +bool +utf8_checker_t<_>::write(void const* buffer, std::size_t size) +{ + auto p = static_cast(buffer); + auto plut = &lut()[0]; + while(size) + { + auto const byte = *p; + auto const type = plut[byte]; + if(state_) + codepoint_ = (byte & 0x3fu) | (codepoint_ << 6); + else + codepoint_ = (0xff >> type) & byte; + state_ = plut[256 + state_ * 16 + type]; + if(state_ == 1) + { + reset(); + return false; + } + ++p; + --size; + } + return true; +} + +template +template +bool +utf8_checker_t<_>::write(BufferSequence const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for (auto const& b : bs) + if(! write(buffer_cast(b), + buffer_size(b))) + return false; + return true; +} + +template +bool +utf8_checker_t<_>::finish() +{ + auto const success = state_ == 0; + reset(); + return success; +} + +using utf8_checker = utf8_checker_t<>; + +template +bool +check_utf8(char const* p, std::size_t n) +{ + utf8_checker c; + if(! c.write(p, n)) + return false; + return c.finish(); +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/error.hpp b/include/beast/websocket/error.hpp new file mode 100644 index 00000000..09cbdcc1 --- /dev/null +++ b/include/beast/websocket/error.hpp @@ -0,0 +1,61 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_ERROR_HPP +#define BEAST_WEBSOCKET_ERROR_HPP + +#include + +namespace beast { +namespace websocket { + +/// The type of error used by functions and completion handlers. +using error_code = boost::system::error_code; + +/// Error values +enum class error +{ + /// Both sides performed a WebSocket close + closed = 1, + + /// WebSocket connection failed, protocol violation + failed, + + /// Upgrade request failed, connection is closed + handshake_failed, + + /// Upgrade request failed, but connection is still open + keep_alive, + + /// HTTP response is malformed + response_malformed, + + /// HTTP response failed the upgrade + response_failed, + + /// Upgrade request denied for invalid fields. + response_denied, + + /// Upgrade request is malformed + request_malformed, + + /// Upgrade request fields incorrect + request_invalid, + + /// Upgrade request denied + request_denied +}; + +error_code +make_error_code(error e); + +} // websocket +} // beast + +#include + +#endif diff --git a/include/beast/websocket/impl/accept_op.ipp b/include/beast/websocket/impl/accept_op.ipp new file mode 100644 index 00000000..c4e6dc97 --- /dev/null +++ b/include/beast/websocket/impl/accept_op.ipp @@ -0,0 +1,146 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_OP_HPP +#define BEAST_WEBSOCKET_IMPL_ACCEPT_OP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// read and respond to an upgrade request +// +template +template +class stream::accept_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& ws; + http::request req; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + Buffers const& buffers) + : ws(ws_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + ws.stream_.buffer().commit(buffer_copy( + ws.stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + } + }; + + std::shared_ptr d_; + +public: + accept_op(accept_op&&) = default; + accept_op(accept_op const&) = default; + + template + accept_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code const& ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(accept_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, accept_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::accept_op:: +operator()(error_code const& ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read message + d.state = 1; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.req, + std::move(*this)); + return; + + // got message + case 1: + // respond to request + response_op{ + std::move(d.h), d.ws, d.req, true}; + return; + } + } + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/close_op.ipp b/include/beast/websocket/impl/close_op.ipp new file mode 100644 index 00000000..c3201903 --- /dev/null +++ b/include/beast/websocket/impl/close_op.ipp @@ -0,0 +1,186 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_CLOSE_OP_HPP +#define BEAST_WEBSOCKET_IMPL_CLOSE_OP_HPP + +#include +#include +#include + +namespace beast { +namespace websocket { + +// send the close message and wait for the response +// +template +template +class stream::close_op +{ + using alloc_type = + handler_alloc; + using fb_type = + detail::frame_streambuf; + using fmb_type = + typename fb_type::mutable_buffers_type; + + struct data : op + { + stream& ws; + close_reason cr; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + close_reason const& cr_) + : ws(ws_) + , cr(cr_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + ws.template write_close< + static_streambuf>(fb, cr); + } + }; + + std::shared_ptr d_; + +public: + close_op(close_op&&) = default; + close_op(close_op const&) = default; + + template + close_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(close_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, close_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::close_op::operator()( + error_code ec, std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.rd_op_.template emplace< + close_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + d.state = 2; + break; + + // resume + case 1: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + d.state = 2; + break; + + case 2: + // send close + d.state = 99; + assert(! d.ws.wr_close_); + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/error.ipp b/include/beast/websocket/impl/error.ipp new file mode 100644 index 00000000..b4c29102 --- /dev/null +++ b/include/beast/websocket/impl/error.ipp @@ -0,0 +1,27 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_ERROR_IPP_HPP +#define BEAST_WEBSOCKET_IMPL_ERROR_IPP_HPP + +#include + +namespace beast { +namespace websocket { + +inline +error_code +make_error_code(error e) +{ + return error_code( + static_cast(e), detail::get_error_category()); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/handshake_op.ipp b/include/beast/websocket/impl/handshake_op.ipp new file mode 100644 index 00000000..a2179f36 --- /dev/null +++ b/include/beast/websocket/impl/handshake_op.ipp @@ -0,0 +1,158 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_OP_HPP +#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_OP_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// send the upgrade request and process the response +// +template +template +class stream::handshake_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& ws; + Handler h; + std::string key; + http::request req; + http::response resp; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + boost::string_ref const& host, + boost::string_ref const& resource) + : ws(ws_) + , h(std::forward(h_)) + , req(ws.build_request(host, resource, key)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + handshake_op(handshake_op&&) = default; + handshake_op(handshake_op const&) = default; + + template + handshake_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(handshake_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, handshake_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::handshake_op< + Handler>::operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + // send http upgrade + d.state = 1; + // VFALCO Do we need the ability to move + // a message on the async_write? + http::async_write(d.ws.stream_, + d.req, std::move(*this)); + return; + } + + // sent upgrade + case 1: + // read http response + d.state = 2; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.resp, + std::move(*this)); + return; + + // got response + case 2: + { + d.ws.do_response(d.resp, d.key, ec); + // call handler + d.state = 99; + break; + } + } + } + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/read_frame_op.ipp b/include/beast/websocket/impl/read_frame_op.ipp new file mode 100644 index 00000000..82e2da98 --- /dev/null +++ b/include/beast/websocket/impl/read_frame_op.ipp @@ -0,0 +1,507 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_READ_FRAME_OP_HPP +#define BEAST_WEBSOCKET_IMPL_READ_FRAME_OP_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// Reads a single message frame, +// processes any received control frames. +// +template +template +class stream::read_frame_op +{ + using alloc_type = + handler_alloc; + + using fb_type = + detail::frame_streambuf; + + using fmb_type = + typename fb_type::mutable_buffers_type; + + using smb_type = + typename Streambuf::mutable_buffers_type; + + struct data : op + { + stream& ws; + frame_info& fi; + Streambuf& sb; + smb_type smb; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + frame_info& fi_, Streambuf& sb_) + : ws(ws_) + , fi(fi_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_frame_op(read_frame_op&&) = default; + read_frame_op(read_frame_op const&) = default; + + template + read_frame_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_frame_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::read_frame_op:: +operator()(error_code ec,std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + close_code code; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + if(d.ws.rd_need_ > 0) + { + d.state = 1; + break; + } + d.state = 2; + break; + + case 1: + // read payload + d.state = 3; + d.smb = d.sb.prepare( + detail::clamp(d.ws.rd_need_)); + d.ws.stream_.async_read_some( + d.smb, std::move(*this)); + return; + + case 2: + // read fixed header + d.state = 5; + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(2), std::move(*this)); + return; + + // got payload + case 3: + { + d.ws.rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, d.smb); + if(d.ws.rd_fh_.mask) + detail::mask_inplace(pb, d.ws.rd_key_); + if(d.ws.rd_opcode_ == opcode::text) + { + if(! d.ws.rd_utf8_check_.write(pb) || + (d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin && + ! d.ws.rd_utf8_check_.finish())) + { + // invalid utf8 + d.state = 16; + code = close_code::bad_payload; + break; + } + } + d.sb.commit(bytes_transferred); + d.state = 4; + break; + } + + // call handler + case 4: + d.state = 99; + d.fi.op = d.ws.rd_opcode_; + d.fi.fin = d.ws.rd_fh_.fin && + d.ws.rd_need_ == 0; + break; + + // got fixed header + case 5: + { + d.fb.commit(bytes_transferred); + code = close_code::none; + auto const n = detail::read_fh1( + d.ws.rd_fh_, d.fb, d.ws.role_, code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.state = 6; + if (n == 0) + { + bytes_transferred = 0; + break; + } + // read variable header + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(n), std::move(*this)); + return; + } + + // got variable header + case 6: + d.fb.commit(bytes_transferred); + code = close_code::none; + detail::read_fh2(d.ws.rd_fh_, + d.fb, d.ws.role_, code); + if(code == close_code::none) + d.ws.prepare_fh(code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + if(detail::is_control(d.ws.rd_fh_.op)) + { + if(d.ws.rd_fh_.len > 0) + { + // read control payload + d.state = 7; + d.fmb = d.fb.prepare(static_cast< + std::size_t>(d.ws.rd_fh_.len)); + boost::asio::async_read(d.ws.stream_, + d.fmb, std::move(*this)); + return; + } + d.state = 8; + break; + } + if(d.ws.rd_need_ > 0) + { + d.state = 1; + break; + } + if(! d.ws.rd_fh_.fin) + { + d.state = 2; + break; + } + // empty frame with fin + d.state = 4; + break; + + // got control payload + case 7: + if(d.ws.rd_fh_.mask) + detail::mask_inplace( + d.fmb, d.ws.rd_key_); + d.fb.commit(bytes_transferred); + d.state = 8; + break; + + // do control + case 8: + if(d.ws.rd_fh_.op == opcode::ping) + { + code = close_code::none; + ping_payload_type data; + detail::read(data, d.fb.data(), code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.fb.reset(); + if(d.ws.wr_close_) + { + d.state = 2; + break; + } + d.ws.template write_ping( + d.fb, opcode::pong, data); + if(d.ws.wr_block_) + { + assert(d.ws.wr_block_ != &d); + // suspend + d.state = 13; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 14; + break; + } + else if(d.ws.rd_fh_.op == opcode::pong) + { + code = close_code::none; + ping_payload_type data; + detail::read(data, d.fb.data(), code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.fb.reset(); + // VFALCO TODO maybe_invoke an async pong handler + // For now just ignore the pong. + d.state = 2; + break; + } + assert(d.ws.rd_fh_.op == opcode::close); + { + detail::read(d.ws.cr_, d.fb.data(), code); + if(code != close_code::none) + { + d.state = 16; + break; + } + if(! d.ws.wr_close_) + { + auto cr = d.ws.cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, cr); + if(d.ws.wr_block_) + { + // suspend + d.state = 9; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 10; + break; + } + // call handler; + d.state = 99; + ec = error::closed; + break; + } + + // resume + case 9: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + if(d.ws.wr_close_) + { + // call handler + d.state = 99; + ec = error::closed; + break; + } + d.state = 10; + break; + + // send close + case 10: + d.state = 11; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return;; + + // teardown + case 11: + d.state = 12; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + case 12: + // call handler + d.state = 99; + ec = error::closed; + break; + + // resume + case 13: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + if(d.ws.wr_close_) + { + d.fb.reset(); + d.state = 2; + break; + } + d.state = 14; + break; + + case 14: + // write ping/pong + d.state = 15; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + // sent ping/pong + case 15: + d.fb.reset(); + d.state = 2; + d.ws.wr_block_ = nullptr; + break; + + // fail the connection + case 16: + if(! d.ws.wr_close_) + { + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, code); + if(d.ws.wr_block_) + { + // suspend + d.state = 17; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 18; + break; + } + + // resume + case 17: + if(d.ws.wr_close_) + { + d.state = 19; + break; + } + d.state = 18; + break; + + case 18: + // send close + d.state = 19; + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + // teardown + case 19: + d.state = 20; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + case 20: + // call handler + d.state = 99; + ec = error::failed; + break; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.wr_op_.maybe_invoke(); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/read_op.ipp b/include/beast/websocket/impl/read_op.ipp new file mode 100644 index 00000000..8ea86893 --- /dev/null +++ b/include/beast/websocket/impl/read_op.ipp @@ -0,0 +1,132 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_READ_OP_HPP +#define BEAST_WEBSOCKET_IMPL_READ_OP_HPP + +#include +#include + +namespace beast { +namespace websocket { + +// read an entire message +// +template +template +class stream::read_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& ws; + opcode& op; + Streambuf& sb; + Handler h; + frame_info fi; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + stream& ws_, opcode& op_, + Streambuf& sb_) + : ws(ws_) + , op(op_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code const& ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::read_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read payload + d.state = 1; + d.ws.async_read_frame( + d.fi, d.sb, std::move(*this)); + return; + + // got payload + case 1: + d.op = d.fi.op; + d.state = d.fi.fin ? 99 : 0; + break; + } + } + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/response_op.ipp b/include/beast/websocket/impl/response_op.ipp new file mode 100644 index 00000000..dd7343cd --- /dev/null +++ b/include/beast/websocket/impl/response_op.ipp @@ -0,0 +1,135 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_RESPONSE_OP_HPP +#define BEAST_WEBSOCKET_IMPL_RESPONSE_OP_HPP + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// Respond to an upgrade HTTP request +template +template +class stream::response_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& ws; + http::response resp; + Handler h; + error_code final_ec; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + http::message const& req, + bool cont_) + : ws(ws_) + , resp(ws_.build_response(req)) + , h(std::forward(h_)) + , cont(cont_) + { + if(resp.status != 101) + final_ec = error::handshake_failed; + } + }; + + std::shared_ptr d_; + +public: + response_op(response_op&&) = default; + response_op(response_op const&) = default; + + template + response_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(response_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, response_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::response_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // send response + d.state = 1; + http::async_write(d.ws.next_layer_, + d.resp, std::move(*this)); + return; + + // sent response + case 1: + d.state = 99; + ec = d.final_ec; + if(! ec) + d.ws.role_ = role_type::server; + break; + } + } + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/ssl.ipp b/include/beast/websocket/impl/ssl.ipp new file mode 100644 index 00000000..5f054f28 --- /dev/null +++ b/include/beast/websocket/impl/ssl.ipp @@ -0,0 +1,158 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED +#define BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED + +#include +#include + +namespace beast { +namespace websocket { + +namespace detail { + +/* + +See +http://stackoverflow.com/questions/32046034/what-is-the-proper-way-to-securely-disconnect-an-asio-ssl-socket/32054476#32054476 + +Behavior of ssl::stream regarding close_ + + If the remote host calls async_shutdown then the + local host's async_read will complete with eof. + + If both hosts call async_shutdown then the calls + to async_shutdown will complete with eof. + +*/ +template +class teardown_ssl_op +{ + using stream_type = + boost::asio::ssl::stream; + + struct data + { + stream_type& stream; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + stream_type& stream_) + : stream(stream_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + explicit + teardown_ssl_op( + DeducedHandler&& h, + stream_type& stream) + : d_(std::make_shared( + std::forward(h), + stream)) + { + (*this)(error_code{}, false); + } + + void + operator()(error_code ec, bool again = true); + + friend + void* asio_handler_allocate(std::size_t size, + teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate(void* p, + std::size_t size, teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation( + teardown_ssl_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, + teardown_ssl_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_ssl_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(!ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + d.stream.async_shutdown(*this); + return; + } + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec) +{ + stream.shutdown(ec); +} + +template +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_ssl_op::type>{std::forward( + handler), stream}; +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp new file mode 100644 index 00000000..033c7a8b --- /dev/null +++ b/include/beast/websocket/impl/stream.ipp @@ -0,0 +1,819 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_STREAM_IPP +#define BEAST_WEBSOCKET_IMPL_STREAM_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +namespace detail { + +template +void +stream_base::prepare_fh(close_code& code) +{ + // continuation without an active message + if(! rd_cont_ && rd_fh_.op == opcode::cont) + { + code = close_code::protocol_error; + return; + } + // new data frame when continuation expected + if(rd_cont_ && ! is_control(rd_fh_.op) && + rd_fh_.op != opcode::cont) + { + code = close_code::protocol_error; + return; + } + if(rd_fh_.mask) + prepare_key(rd_key_, rd_fh_.key); + if(! is_control(rd_fh_.op)) + { + if(rd_fh_.op != opcode::cont) + { + rd_size_ = rd_fh_.len; + rd_opcode_ = rd_fh_.op; + } + else + { + if(rd_size_ > std::numeric_limits< + std::uint64_t>::max() - rd_fh_.len) + { + code = close_code::too_big; + return; + } + rd_size_ += rd_fh_.len; + } + if(rd_size_ > rd_msg_max_) + { + code = close_code::too_big; + return; + } + rd_need_ = rd_fh_.len; + rd_cont_ = ! rd_fh_.fin; + } +} + +template +void +stream_base::write_close( + Streambuf& sb, close_reason const& cr) +{ + using namespace boost::endian; + frame_header fh; + fh.op = opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(cr.code != close_code::none) + { + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t b[2]; + ::new(&b[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto d = sb.prepare(2); + boost::asio::buffer_copy(d, + boost::asio::buffer(b)); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(2); + } + if(! cr.reason.empty()) + { + auto d = sb.prepare(cr.reason.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(cr.reason.size()); + } + } +} + +template +void +stream_base::write_ping(Streambuf& sb, + opcode op, ping_payload_type const& data) +{ + frame_header fh; + fh.op = op; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(data.empty()) + return; + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto d = sb.prepare(data.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffers_1( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(data.size()); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +template +stream:: +stream(Args&&... args) + : next_layer_(std::forward(args)...) + , stream_(next_layer_) +{ + static_assert(is_Stream::value, + "Stream requirements not met"); +} + +template +void +stream:: +accept(error_code& ec) +{ + accept(boost::asio::null_buffers{}, ec); +} + +template +template +typename async_completion< + AcceptHandler, void(error_code)>::result_type +stream:: +async_accept(AcceptHandler&& handler) +{ + return async_accept(boost::asio::null_buffers{}, + std::forward(handler)); +} + +template +template +void +stream:: +accept(ConstBufferSequence const& buffers) +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(buffers, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +stream:: +accept(ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + http::request m; + http::read(next_layer_, stream_.buffer(), m, ec); + if(ec) + return; + accept(m, ec); +} + +template +template +typename async_completion< + AcceptHandler, void(error_code)>::result_type +stream:: +async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + accept_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +stream:: +accept(http::message const& request) +{ + error_code ec; + accept(request, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +stream:: +accept(http::message const& req, + error_code& ec) +{ + auto resp = build_response(req); + http::write(stream_, resp, ec); + if(resp.status != 101) + { + ec = error::handshake_failed; + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + role_ = role_type::server; +} + +template +template +typename async_completion< + AcceptHandler, void(error_code)>::result_type +stream:: +async_accept(http::message const& req, + AcceptHandler&& handler) +{ + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + response_op{ + completion.handler, *this, req, + boost_asio_handler_cont_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +void +stream:: +handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec) +{ + std::string key; + http::write(stream_, + build_request(host, resource, key), ec); + if(ec) + return; + http::response resp; + http::read(next_layer_, stream_.buffer(), resp, ec); + if(ec) + return; + do_response(resp, key, ec); +} + +template +template +typename async_completion< + HandshakeHandler, void(error_code)>::result_type +stream:: +async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& handler) +{ + beast::async_completion< + HandshakeHandler, void(error_code) + > completion(handler); + handshake_op{ + completion.handler, *this, host, resource}; + return completion.result.get(); +} + +template +void +stream:: +close(close_reason const& cr, error_code& ec) +{ + assert(! wr_close_); + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + error_ = ec != 0; +} + +template +template +typename async_completion< + CloseHandler, void(error_code)>::result_type +stream:: +async_close(close_reason const& cr, CloseHandler&& handler) +{ + beast::async_completion< + CloseHandler, void(error_code) + > completion(handler); + close_op{ + completion.handler, *this, cr}; + return completion.result.get(); +} + +template +template +void +stream:: +read(opcode& op, Streambuf& streambuf, error_code& ec) +{ + frame_info fi; + for(;;) + { + read_frame(fi, streambuf, ec); + if(ec) + break; + op = fi.op; + if(fi.fin) + break; + } +} + +template +template +typename async_completion< + ReadHandler, void(error_code)>::result_type +stream:: +async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code) + > completion(handler); + read_op{ + completion.handler, *this, op, streambuf}; + return completion.result.get(); +} + +template +template +void +stream:: +read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) +{ + close_code code{}; + for(;;) + { + if(rd_need_ == 0) + { + // read header + detail::frame_streambuf fb; + do_read_fh(fb, code, ec); + if((error_ = ec != 0)) + return; + if(code != close_code::none) + break; + if(detail::is_control(rd_fh_.op)) + { + // read control payload + if(rd_fh_.len > 0) + { + auto const mb = fb.prepare( + static_cast(rd_fh_.len)); + fb.commit(boost::asio::read(stream_, mb, ec)); + if((error_ = ec != 0)) + return; + if(rd_fh_.mask) + detail::mask_inplace(mb, rd_key_); + fb.commit(static_cast(rd_fh_.len)); + } + if(rd_fh_.op == opcode::ping) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if(code != close_code::none) + break; + fb.reset(); + write_ping( + fb, opcode::pong, data); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + continue; + } + else if(rd_fh_.op == opcode::pong) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if((error_ = ec != 0)) + break; + // VFALCO How to notify callers using + // the synchronous interface? + continue; + } + assert(rd_fh_.op == opcode::close); + { + detail::read(cr_, fb.data(), code); + if(code != close_code::none) + break; + if(! wr_close_) + { + auto cr = cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + fb.reset(); + wr_close_ = true; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + break; + } + } + if(rd_need_ == 0 && ! rd_fh_.fin) + { + // empty frame + continue; + } + } + // read payload + auto smb = streambuf.prepare( + detail::clamp(rd_need_)); + auto const bytes_transferred = + stream_.read_some(smb, ec); + if((error_ = ec != 0)) + return; + rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, smb); + if(rd_fh_.mask) + detail::mask_inplace(pb, rd_key_); + if(rd_opcode_ == opcode::text) + { + if(! rd_utf8_check_.write(pb) || + (rd_need_ == 0 && rd_fh_.fin && + ! rd_utf8_check_.finish())) + { + code = close_code::bad_payload; + break; + } + } + streambuf.commit(bytes_transferred); + fi.op = rd_opcode_; + fi.fin = rd_fh_.fin && rd_need_ == 0; + return; + } + if(code != close_code::none) + { + // Fail the connection (per rfc6455) + if(! wr_close_) + { + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, code); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + wsproto_helpers::call_teardown(next_layer_, ec); + if((error_ = ec != 0)) + return; + ec = error::failed; + error_ = true; + return; + } + if(! ec) + wsproto_helpers::call_teardown(next_layer_, ec); + if(! ec) + ec = error::closed; + error_ = ec != 0; +} + +template +template +typename async_completion< + ReadHandler, void(error_code)>::result_type +stream:: +async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code)> completion(handler); + read_frame_op{ + completion.handler, *this, fi, streambuf}; + return completion.result.get(); +} + +template +template +void +stream:: +write(ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_size; + consuming_buffers cb(bs); + auto remain = buffer_size(cb); + for(;;) + { + auto const n = + detail::clamp(remain, wr_frag_size_); + remain -= n; + auto const fin = remain <= 0; + write_frame(fin, prepare_buffers(n, cb), ec); + cb.consume(n); + if(ec) + return; + if(fin) + break; + } +} + +template +template +typename async_completion< + WriteHandler, void(error_code)>::result_type +stream:: +async_write(ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code)> completion(handler); + write_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +stream:: +write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + detail::frame_header fh; + fh.op = wr_cont_ ? opcode::cont : wr_opcode_; + wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = buffer_size(bs); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + if(! fh.mask) + { + // send header and payload + boost::asio::write(stream_, + buffer_cat(fh_buf.data(), bs), ec); + error_ = ec != 0; + return; + } + detail::prepared_key_type key; + detail::prepare_key(key, fh.key); + auto const tmp_size = detail::clamp( + fh.len, wr_buf_size_); + std::unique_ptr up( + new std::uint8_t[tmp_size]); + auto const tmp = up.get(); + std::uint64_t remain = fh.len; + consuming_buffers cb(bs); + { + auto const n = + detail::clamp(remain, tmp_size); + mutable_buffers_1 mb{tmp, n}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send header and payload + boost::asio::write(stream_, + buffer_cat(fh_buf.data(), mb), ec); + if(ec) + { + error_ = ec != 0; + return; + } + } + while(remain > 0) + { + auto const n = + detail::clamp(remain, tmp_size); + mutable_buffers_1 mb{tmp, n}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send payload + boost::asio::write(stream_, mb, ec); + if(ec) + { + error_ = ec != 0; + return; + } + } +} + +template +template +typename async_completion< + WriteHandler, void(error_code)>::result_type +stream:: +async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code) + > completion(handler); + write_frame_op{completion.handler, + *this, fin, bs}; + return completion.result.get(); +} + +//------------------------------------------------------------------------------ + +template +http::request +stream:: +build_request(boost::string_ref const& host, + boost::string_ref const& resource, std::string& key) +{ + http::request req; + req.url = "/"; + req.version = 11; + req.method = http::method_t::http_get; + req.headers.insert("Host", host); + req.headers.insert("Connection", "upgrade"); + req.headers.insert("Upgrade", "websocket"); + key = detail::make_sec_ws_key(maskgen_); + req.headers.insert("Sec-WebSocket-Key", key); + req.headers.insert("Sec-WebSocket-Version", "13"); + (*d_)(req); + return req; +} + +template +template +http::response +stream:: +build_response(http::message const& req) +{ + auto err = + [&](std::string const& text) + { + http::response resp( + {400, http::reason_string(400), req.version}); + resp.body = text; + // VFALCO TODO respect keep-alive here + return resp; + }; + if(req.version < 11) + return err("HTTP version 1.1 required"); + if(req.method != http::method_t::http_get) + return err("Wrong method"); + if(! is_upgrade(req)) + return err("Expected Upgrade request"); + if(! req.headers.exists("Host")) + return err("Missing Host"); + if(! req.headers.exists("Sec-WebSocket-Key")) + return err("Missing Sec-WebSocket-Key"); + { + auto const version = + req.headers["Sec-WebSocket-Version"]; + if(version.empty()) + return err("Missing Sec-WebSocket-Version"); + if(version != "13") + return err("Unsupported Sec-WebSocket-Version"); + } + if(! rfc2616::token_in_list( + req.headers["Upgrade"], "websocket")) + return err("Missing websocket Upgrade token"); + http::response resp( + {101, http::reason_string(101), req.version}); + resp.headers.insert("Upgrade", "websocket"); + resp.headers.insert("Connection", "upgrade"); + { + auto const key = + req.headers["Sec-WebSocket-Key"]; + resp.headers.insert("Sec-WebSocket-Key", key); + resp.headers.insert("Sec-WebSocket-Accept", + detail::make_sec_ws_accept(key)); + } + resp.headers.replace("Server", "Beast.WSProto"); + (*d_)(resp); + return resp; +} + +template +template +void +stream:: +do_response(http::message const& resp, + boost::string_ref const& key, error_code& ec) +{ + // VFALCO Review these error codes + auto fail = [&]{ ec = error::response_failed; }; + if(resp.status != 101) + return fail(); + if(! is_upgrade(resp)) + return fail(); + if(! rfc2616::ci_equal( + resp.headers["Upgrade"], "websocket")) + return fail(); + if(! resp.headers.exists("Sec-WebSocket-Accept")) + return fail(); + if(resp.headers["Sec-WebSocket-Accept"] != + detail::make_sec_ws_accept(key)) + return fail(); + role_ = role_type::client; +} + +template +void +stream:: +do_read_fh(detail::frame_streambuf& fb, + close_code& code, error_code& ec) +{ + fb.commit(boost::asio::read( + stream_, fb.prepare(2), ec)); + if(ec) + return; + auto const n = detail::read_fh1( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + if(n > 0) + { + fb.commit(boost::asio::read( + stream_, fb.prepare(n), ec)); + if(ec) + return; + } + detail::read_fh2( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + prepare_fh(code); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/teardown.ipp b/include/beast/websocket/impl/teardown.ipp new file mode 100644 index 00000000..689f647e --- /dev/null +++ b/include/beast/websocket/impl/teardown.ipp @@ -0,0 +1,171 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP +#define BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP + +#include +#include +#include + +namespace beast { +namespace websocket { + +namespace detail { + +template +class teardown_tcp_op +{ + using socket_type = + boost::asio::ip::tcp::socket; + + struct data + { + socket_type& socket; + Handler h; + char buf[8192]; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket_type& socket_) + : socket(socket_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + teardown_tcp_op( + DeducedHandler&& h, + socket_type& socket) + : d_(std::make_shared( + std::forward(h), + socket)) + { + (*this)(error_code{}, 0, false); + } + + void + operator()( + error_code ec, std::size_t, bool again = true); + + friend + void* asio_handler_allocate(std::size_t size, + teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate(void* p, + std::size_t size, teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(teardown_tcp_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, + teardown_tcp_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_tcp_op:: +operator()(error_code ec, std::size_t, bool again) +{ + using boost::asio::buffer; + auto& d = *d_; + d.cont = d.cont || again; + while(! ec) + { + switch(d.state) + { + case 0: + d.state = 1; + d.socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + break; + + case 1: + d.socket.async_read_some( + buffer(d.buf), std::move(*this)); + return; + } + } + if(ec == boost::asio::error::eof) + { + d.socket.close(ec); + ec = error_code{}; + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +inline +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec) +{ + using boost::asio::buffer; + socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + while(! ec) + { + char buf[8192]; + auto const n = socket.read_some( + buffer(buf), ec); + if(! n) + break; + } + if(ec == boost::asio::error::eof) + ec = error_code{}; + socket.close(ec); +} + +template +inline +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_tcp_op::type>{std::forward< + TeardownHandler>(handler), socket}; +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/write_frame_op.ipp b/include/beast/websocket/impl/write_frame_op.ipp new file mode 100644 index 00000000..41f669a0 --- /dev/null +++ b/include/beast/websocket/impl/write_frame_op.ipp @@ -0,0 +1,265 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_WRITE_FRAME_OP_HPP +#define BEAST_WEBSOCKET_IMPL_WRITE_FRAME_OP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// write a frame +// +template +template +class stream::write_frame_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + stream& ws; + consuming_buffers cb; + Handler h; + detail::frame_header fh; + detail::fh_streambuf fh_buf; + detail::prepared_key_type key; + void* tmp; + std::size_t tmp_size; + std::uint64_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + bool fin, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + fh.op = ws.wr_cont_ ? + opcode::cont : ws.wr_opcode_; + ws.wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = 0; + fh.rsv2 = 0; + fh.rsv3 = 0; + fh.len = boost::asio::buffer_size(cb); + if((fh.mask = (ws.role_ == role_type::client))) + { + fh.key = ws.maskgen_(); + detail::prepare_key(key, fh.key); + tmp_size = detail::clamp( + fh.len, ws.wr_buf_size_); + tmp = boost_asio_handler_alloc_helpers:: + allocate(tmp_size, h); + remain = fh.len; + } + else + { + tmp = nullptr; + } + detail::write(fh_buf, fh); + } + + ~data() + { + if(tmp) + boost_asio_handler_alloc_helpers:: + deallocate(tmp, tmp_size, h); + } + }; + + std::shared_ptr d_; + +public: + write_frame_op(write_frame_op&&) = default; + write_frame_op(write_frame_op const&) = default; + + template + write_frame_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::make_shared( + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(write_frame_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, write_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +write_frame_op:: +operator()( + error_code ec, std::size_t bytes_transferred, bool again) +{ + using boost::asio::buffer_copy; + using boost::asio::mutable_buffers_1; + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.wr_op_.template emplace< + write_frame_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + assert(! d.ws.wr_close_); + d.state = 2; + break; + + // resume + case 1: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + d.state = 2; + break; + + case 2: + { + if(! d.fh.mask) + { + // send header and payload + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), d.cb), + std::move(*this)); + return; + } + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, n}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send header and payload + d.state = d.remain > 0 ? 3 : 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), + mb), std::move(*this)); + return; + } + + // sent masked payload + case 3: + { + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, + static_cast(n)}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send payload + if(d.remain == 0) + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write( + d.ws.stream_, mb, std::move(*this)); + return; + } + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + if(d.tmp) + { + boost_asio_handler_alloc_helpers:: + deallocate(d.tmp, d.tmp_size, d.h); + d.tmp = nullptr; + } + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/write_op.ipp b/include/beast/websocket/impl/write_op.ipp new file mode 100644 index 00000000..0a4b3e0d --- /dev/null +++ b/include/beast/websocket/impl/write_op.ipp @@ -0,0 +1,137 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_WRITE_OP_HPP +#define BEAST_WEBSOCKET_IMPL_WRITE_OP_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// write a message +// +template +template +class stream::write_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + stream& ws; + consuming_buffers cb; + Handler h; + std::size_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + stream& ws_, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , remain(boost::asio::buffer_size(cb)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()(error_code ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +write_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + auto const n = std::min( + d.remain, d.ws.wr_frag_size_); + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.state = 99; + d.ws.async_write_frame(fin, + prepare_buffers(n, d.cb), std::move(*this)); + d.cb.consume(n); + return; + } + } + } + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp new file mode 100644 index 00000000..9dda1f6c --- /dev/null +++ b/include/beast/websocket/option.hpp @@ -0,0 +1,293 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_OPTION_HPP +#define BEAST_WEBSOCKET_OPTION_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +/** Automatic fragmentation size option. + + Sets the maximum size of fragments generated when sending + messages on a WebSocket socket. + + When the automatic fragmentation size is non-zero, messages + exceeding the size will be split into multiple frames no + larger than the size. This setting does not affect frames + send explicitly using `write_frame` or `async_write_frame`. + + The default setting is to fragment messages into 16KB frames. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the automatic fragmentation size option: + @code + ... + websocket::stream stream(ios); + stream.set_option(auto_fragment_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using auto_fragment_size = implementation_defined; +#else +struct auto_fragment_size +{ + std::size_t value; + + auto_fragment_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** HTTP decorator option. + + The decorator transforms the HTTP requests and responses used + when requesting or responding to the WebSocket Upgrade. This may + be used to set or change header fields. For example to set the + Server or User-Agent fields. The default setting applies no + transformation to the HTTP message. + + For synchronous operations, the implementation will call the + decorator before the function call to perform the operation + returns. + + For asynchronous operations, the implementation guarantees that + calls to the decorator will be made from the same implicit or + explicit strand used to call the asynchronous initiation + function. + + The default setting is no decorator. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the decorator. + @code + struct identity + { + template + void + operator()(http::message& m) + { + if(isRequest) + m.headers.replace("User-Agent", "MyClient"); + else + m.headers.replace("Server", "MyServer"); + } + }; + ... + websocket::stream ws(ios); + ws.set_option(decorate(identity{})); + @endcode +*/ +#if GENERATING_DOCS +using decorate = implementation_defined; +#else +template +inline +detail::decorator_type +decorate(Decorator&& d) +{ + return detail::decorator_type{new + detail::decorator::type>{ + std::forward(d)}}; +} +#endif + +/** Keep-alive option. + + Determines if the connection is closed after a failed upgrade + request. + + This setting only affects the behavior of HTTP requests that + implicitly or explicitly ask for a keepalive. For HTTP requests + that indicate the connection should be closed, the connection is + closed as per rfc2616. + + The default setting is to close connections after a failed + upgrade request. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the keep alive option. + @code + ... + websocket::stream ws(ios); + ws.set_option(keep_alive{8192}); + @endcode +*/ +#if GENERATING_DOCS +using keep_alive = implementation_defined; +#else +struct keep_alive +{ + bool value; + + keep_alive(bool v) + : value(v) + { + } +}; +#endif + +/** Message type option. + + This controls the opcode set for outgoing messages. Valid + choices are opcode::binary or opcode::text. The setting is + only applied at the start when a caller begins a new message. + Changing the opcode after a message is started will only + take effect after the current message being sent is complete. + + The default setting is opcode::text. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the message type to binary. + @code + ... + websocket::stream ws(ios); + ws.set_option(message_type{opcode::binary}); + @endcode +*/ +#if GENERATING_DOCS +using message_type = implementation_defined; +#else +struct message_type +{ + opcode value; + + explicit + message_type(opcode op) + { + if(op != opcode::binary && op != opcode::text) + throw std::domain_error("bad opcode"); + value = op; + } +}; +#endif + +/** Read buffer size option. + + Sets the number of bytes allocated to the socket's read buffer. + If this is zero, then reads are not buffered. Setting this + higher can improve performance when expecting to receive + many small frames. + + The default is no buffering. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the read buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_buffer_size{16 * 1024}); + @endcode +*/ +#if GENERATING_DOCS +using read_buffer_size = implementation_defined; +#else +struct read_buffer_size +{ + std::size_t value; + + explicit + read_buffer_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** Maximum incoming message size option. + + Sets the largest permissible incoming message size. Message + frame headers indicating a size that would bring the total + message size over this limit will cause a protocol failure. + + The default setting is 16 megabytes. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the maximum read message size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_message_max{65536}); + @endcode +*/ +#if GENERATING_DOCS +using read_message_max = implementation_defined; +#else +struct read_message_max +{ + std::size_t value; + + explicit + read_message_max(std::size_t n) + : value(n) + { + } +}; +#endif + +/** Write buffer size option. + + Sets the number of bytes allocated to the socket's write buffer. + This buffer is used to hold masked frame payload data. Lowering + the size of the buffer can decrease the memory requirements for + each connection, at the cost of an increased number of calls to + perform socket writes. + + This setting does not affect connections operating in the server + role, since servers do not apply a masking key to frame payloads. + + The default setting is 4096. The minimum value is 1024. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the write buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(write_buffer_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using write_buffer_size = implementation_defined; +#else +struct write_buffer_size +{ + std::size_t value; + + explicit + write_buffer_size(std::size_t n) + : value(n) + { + } +}; +#endif + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/rfc6455.hpp b/include/beast/websocket/rfc6455.hpp new file mode 100644 index 00000000..3290b369 --- /dev/null +++ b/include/beast/websocket/rfc6455.hpp @@ -0,0 +1,151 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_RFC6455_HPP +#define BEAST_WEBSOCKET_RFC6455_HPP + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + +/** Close status codes. + + These codes accompany close frames. + + @see RFC 6455 7.4.1 Defined Status Codes + https://tools.ietf.org/html/rfc6455#section-7.4.1 +*/ +enum class close_code : std::uint16_t +{ + // used internally to mean "no error" + none = 0, + + normal = 1000, + going_away = 1001, + protocol_error = 1002, + unknown_data = 1003, + bad_payload = 1007, + policy_error = 1008, + too_big = 1009, + needs_extension = 1010, + internal_error = 1011, + + service_restart = 1012, + try_again_later = 1013, + + reserved1 = 1004, + no_status = 1005, // illegal on wire + abnormal = 1006, // illegal on wire + reserved2 = 1015, + + last = 5000 // satisfy warnings +}; + +#if ! GENERATING_DOCS + +using reason_string_type = + static_string<123, char>; + +/// Payload type for pings and pongs +using ping_payload_type = + static_string<125, char>; + +#endif + +/** Description of the close reason. + + This object stores the close code (if any) and the optional + utf-8 encoded implementation defined reason string. +*/ +struct close_reason +{ + /// The close code. + close_code code = close_code::none; + + /// The optional utf8-encoded reason string. + reason_string_type reason; + + /** Default constructor. + + The code will be none. Default constructed objects + will explicitly convert to bool as `false`. + */ + close_reason() = default; + + /// Construct from a code. + close_reason(close_code code_) + : code(code_) + { + } + + /// Construct from a reason. code is close_code::normal. + template + close_reason(CharT const* reason_) + : code(close_code::normal) + , reason(reason_) + { + } + + /// Construct from a code and reason. + template + close_reason(close_code code_, + CharT const* reason_) + : code(code_) + , reason(reason_) + { + } + + /// Returns `true` if a code was specified + operator bool() const + { + return code != close_code::none; + } +}; + +#if ! GENERATING_DOCS + +/// Identifies the role of a WebSockets stream. +enum class role_type +{ + /// Stream is operating as a client. + client, + + /// Stream is operating as a server. + server +}; + +#endif + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/ssl.hpp b/include/beast/websocket/ssl.hpp new file mode 100644 index 00000000..8d7606b3 --- /dev/null +++ b/include/beast/websocket/ssl.hpp @@ -0,0 +1,74 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_SSL_HPP +#define BEAST_WEBSOCKET_SSL_HPP + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +/** Tear down a `boost::asio::ssl::stream`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The stream to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec); + +/** Start tearing down a `boost::asio::ssl::stream`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param stream The stream to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + +*/ +template +inline +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler); + +} // websocket +} // beast + +#include + +#endif diff --git a/include/beast/websocket/static_string.hpp b/include/beast/websocket/static_string.hpp new file mode 100644 index 00000000..6cddcdc5 --- /dev/null +++ b/include/beast/websocket/static_string.hpp @@ -0,0 +1,337 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEBSOCKET_STATIC_STRING_HPP +#define BEAST_WEBSOCKET_STATIC_STRING_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +/** A string with a fixed-size storage area. + + `static_string` objects behave like `std::string` except that + the storage is not dynamically allocated but rather fixed in + size. + + These strings offer performance advantages when a protocol + imposes a natural small upper limit on the size of a value. +*/ +template> +class static_string +{ + std::size_t n_; + std::array s_; + +public: + using traits_type = Traits; + using value_type = typename Traits::char_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using const_pointer = value_type const*; + using const_reference = value_type const&; + using iterator = value_type*; + using const_iterator = value_type const*; + using reverse_iterator = + std::reverse_iterator; + using const_reverse_iterator = + std::reverse_iterator; + + static_string() + { + resize(0); + } + + static_string(static_string const& s) + : n_(s.n_) + { + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + } + + static_string& + operator=(static_string const& s) + { + n_ = s.n_; + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + return *this; + } + + static_string(CharT const* s) + { + assign(s); + } + + static_string& operator=(CharT const* s) + { + assign(s); + return *this; + } + + reference + at(size_type pos) + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + const_reference + at(size_type pos) const + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + reference + operator[](size_type pos); + + const_reference + operator[](size_type pos) const; + + CharT& + front() + { + return s_[0]; + } + + CharT const& + front() const + { + return s_[0]; + } + + CharT& + back() + { + return s_[n_-1]; + } + + CharT const& + back() const + { + return s_[n_-1]; + } + + CharT* + data() + { + return &s_[0]; + } + + CharT const* + data() const + { + return &s_[0]; + } + + CharT const* + c_str() const + { + s_[n_] = 0; + return &s_[0]; + } + + iterator + begin() + { + return &s_[0]; + } + + const_iterator + begin() const + { + return &s_[0]; + } + + const_iterator + cbegin() const + { + return &s_[0]; + } + + iterator + end() + { + return &s_[n_]; + } + + const_iterator + end() const + { + return &s_[n_]; + } + + const_iterator + cend() const + { + return &s_[n_]; + } + + reverse_iterator + rbegin() + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + rbegin() const + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + crbegin() const + { + return reverse_iterator{cend()}; + } + + reverse_iterator + rend() + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + rend() const + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + crend() const + { + return reverse_iterator{cbegin()}; + } + + bool + empty() const + { + return n_ == 0; + } + + size_type + size() const + { + return n_; + } + + size_type constexpr + max_size() const + { + return N; + } + + size_type + capacity() const + { + return N; + } + + void + shrink_to_fit() + { + // no-op + } + + void + clear() + { + resize(0); + } + + // Does not perform value-initialization + void + resize(std::size_t n); + + std::basic_string + to_string() const + { + return std::basic_string< + CharT, Traits>{&s_[0], n_}; + } + +private: + void + assign(CharT const* s); +}; + +template +auto +static_string:: +operator[](size_type pos) -> + reference +{ + static CharT null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +auto +static_string:: +operator[](size_type pos) const -> + const_reference +{ + static CharT constexpr null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +void +static_string:: +resize(std::size_t n) +{ + if(n > N) + throw std::length_error( + "static_string overflow"); + n_ = n; + s_[n_] = 0; +} + +template +void +static_string:: +assign(CharT const* s) +{ + size_type n = 0; + for(auto p = s; *p; ++p) + ++n; + if(n > N) + throw std::out_of_range( + "too large"); + std::copy(s, s+n, s_.begin()); +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp new file mode 100644 index 00000000..c5a7bc82 --- /dev/null +++ b/include/beast/websocket/stream.hpp @@ -0,0 +1,1191 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_STREAM_HPP +#define BEAST_WEBSOCKET_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +/** Information about a WebSocket frame. + + This information is provided to callers during frame + read operations. +*/ +struct frame_info +{ + /// Indicates the type of message (binary or text). + opcode op; + + /// `true` if this is the last frame in the current message. + bool fin; +}; + +//-------------------------------------------------------------------- + +/** Provides message-oriented functionality using WebSocket. + + The stream class template provides asynchronous and blocking + message-oriented functionality necessary for clients and servers + to utilize the WebSocket protocol. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. The application must ensure that + all asynchronous operations are performed within the same + implicit or explicit strand. + + @par Example + + To use the WebSocket stream template with an + ip::tcp::socket, you would write: + + @code + websocket::stream ws(io_service); + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock(io_service); + websocket::stream ws(sock); + @endcode + + @tparam NextLayer An object meeting the requirements of ReadStream, + WriteStream, AsyncReadStream, and AsyncWriteStream. + + @note A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + + @par Concepts + AsyncReadStream, AsyncWriteStream, + Decorator, Streambuf, SyncReadStream, SyncWriteStream. +*/ +template +class stream : public detail::stream_base +{ + friend class ws_test; + + NextLayer next_layer_; + streambuf_readstream stream_; + +public: + /// The type of the next layer. + using next_layer_type = + typename std::remove_reference::type; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// The type of endpoint of the lowest layer. + using endpoint_type = + typename lowest_layer_type::endpoint_type; + + /// The protocol of the next layer. + using protocol_type = + typename lowest_layer_type::protocol_type; + + /// The type of resolver of the next layer. + using resolver_type = + typename protocol_type::resolver; + + /// Move constructor. + stream(stream&&) = default; + + /** Move assignment. + + Undefined behavior if operations are active or pending. + */ + stream& operator=(stream&&) = default; + + /** Construct a WebSocket stream. + + This constructor creates a websocket stream and initialises + the next layer object. + + @throws Any exceptions thrown by the NextLayer constructor. + + @param args The arguments to be passed to initialise the + next layer object. The arguments are forwarded to the next + layer's constructor. + */ + template + explicit + stream(Args&&... args); + + /** Destructor. + + A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + */ + ~stream() = default; + + /** Set options on the stream. + + The application must ensure that calls to set options + are performed within the same implicit or explicit strand. + + @param args One or more stream options to set. + */ +#if GENERATING_DOCS + template + void + set_option(Args&&... args) +#else + template + void + set_option(A1&& a1, A2&& a2, An&&... an) +#endif + { + set_option(std::forward(a1)); + set_option(std::forward(a2), + std::forward(an)...); + } + + void + set_option(auto_fragment_size const& o) + { + if(o.value <= 0) + wr_frag_size_ = + std::numeric_limits::max(); + else + wr_frag_size_ = o.value; + } + + void + set_option(detail::decorator_type o) + { + d_ = std::move(o); + } + + void + set_option(keep_alive const& o) + { + keep_alive_ = o.value; + } + + void + set_option(message_type const& o) + { + wr_opcode_ = o.value; + } + + void + set_option(read_buffer_size const& o) + { + stream_.reserve(o.value); + } + + void + set_option(read_message_max const& o) + { + rd_msg_max_ = o.value; + } + + void + set_option(write_buffer_size const& o) + { + wr_buf_size_ = std::max(o.value, 1024); + stream_.reserve(o.value); + } + + /** Get the io_service associated with the stream. + + This function may be used to obtain the io_service object + that the stream uses to dispatch handlers for asynchronous + operations. + + @return A reference to the io_service object that the stream + will use to dispatch handlers. Ownership is not transferred + to the caller. + */ + boost::asio::io_service& + get_io_service() + { + return next_layer_.lowest_layer().get_io_service(); + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type& + next_layer() + { + return next_layer_; + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type const& + next_layer() const + { + return next_layer_; + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /** Returns the close reason received from the peer. + + This is only valid after a read completes with error::closed. + */ + close_reason const& + reason() const + { + return cr_; + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"), and an appropriate + exception will be thrown. + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @throws boost::system::system_error Thrown on failure. + */ + void + accept() + { + error_code ec; + accept(boost::asio::null_buffers{}, ec); + detail::maybe_throw(ec, "accept"); + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @param ec Set to indicate what error occurred, if any. + */ + void + accept(error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + AcceptHandler, void(error_code)>::result_type + async_accept(AcceptHandler&& handler); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + accept(ConstBufferSequence const& buffers); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(ConstBufferSequence const& buffers, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param buffers Caller provide data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + AcceptHandler, void(error_code)>::result_type + async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @throws boost::system::system_error Thrown on failure. + */ + // VFALCO TODO This should also take a streambuf with any leftover bytes. + template + void + accept(http::message const& request); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the stream. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::message const& request, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the stream is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + AcceptHandler, void(error_code)>::result_type + async_accept(http::message const& request, AcceptHandler&& handler); + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the stream + + @li A complete HTTP response with the result of the upgrade + request is received. + + @throws boost::system::system_error Thrown on failure. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @par Example + @code + websocket::stream ws(io_service); + ... + try + { + ws.upgrade("localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource) + { + error_code ec; + handshake(host, resource, ec); + detail::maybe_throw(ec, "upgrade"); + } + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the stream. + + @li A complete HTTP response with the result of the upgrade + request is received. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws(io_service); + ... + error_code ec; + ws.upgrade(host, resource, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec); + + /** Asynchronously send a WebSocket Upgrade request. + + This function is used to asynchronously send the WebSocket + upgrade HTTP request. This function call always returns + immediately. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @param h The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + HandshakeHandler, void(error_code)>::result_type + async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& h); + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + */ + void + close(close_reason const& cr) + { + error_code ec; + close(cr, ec); + detail::maybe_throw(ec, "close"); + } + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param ec Set to indicate what error occurred, if any. + */ + void + close(close_reason const& cr, error_code& ec); + + /** Start an asychronous WebSocket close operation. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param handler The handler to be called when the close operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + CloseHandler, void(error_code)>::result_type + async_close(close_reason const& cr, CloseHandler&& handler); + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read(opcode& op, Streambuf& streambuf) + { + error_code ec; + read(op, streambuf, ec); + detail::maybe_throw(ec, "read"); + } + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read(opcode& op, + Streambuf& streambuf, error_code& ec); + + /** Start reading a message asynchronously. + + This function is used to asychronously read a message from + the websocket. The function call always returns immediately. + + Upon a successful completion, op is set to either binary or + text depending on the message type, and the input area of the + streambuf will hold all the message payload bytes (which may + be zero in length). + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @param handler The handler to be called when the read operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + ReadHandler, void(error_code)>::result_type + #endif + async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler); + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf) + { + error_code ec; + read_frame(fi, streambuf, ec); + detail::maybe_throw(ec, "read_some"); + } + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec); + + /** Start reading a message frame asynchronously. + + This function is used to asychronously read a single message + frame from the websocket. The function call always returns + immediately. + + Upon a successful completion, fi is filled out to reflect the + message payload contents. op is set to binary or text, and the + fin flag indicates if all the message data has been read in. + To read the entire message, callers should repeat the + read_frame operation until fi.fin is true. A message with no + payload will have fi.fin == true, and zero bytes placed into + the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data after + any masking or decompression has been applied. This object must + remain valid until the handler is called. + + @param handler The handler to be called when the read operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + typename async_completion< + ReadHandler, void(error_code)>::result_type + async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler); + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @throws boost::system::system_error Thrown on failure. + + @note This function always sends an entire message. To + send a message in fragments, use `write_frame`. + */ + template + void + write(ConstBufferSequence const& buffers) + { + error_code ec; + write(buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param ec Set to indicate what error occurred, if any. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @note This function always sends an entire message. To + send a message in fragments, use `write_frame`. + */ + template + void + write(ConstBufferSequence const& buffers, error_code& ec); + + /** Start writing a complete message asynchronously. + + This function is used to asychronously write a message to + the websocket. The function call always returns immediately. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @param handler The handler to be called when the write operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + WriteHandler, void(error_code)>::result_type + #endif + async_write(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @throws boost::system::system_error Thrown on failure. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + */ + template + void + write_frame(bool fin, ConstBufferSequence const& buffers) + { + error_code ec; + write_frame(fin, buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers, error_code& ec); + + /** Start sending a frame asynchronously. + + This function is used to asynchronously write a WebSocket + frame on the stream. This function call always returns + immediately. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + This operation is implemented in terms of one or more calls + to the stream's async_write_some function. The actual payload + sent may be transformed as per the WebSocket protocol settings. + + @param fin A bool indicating whether or not the frame is the + last frame in the corresponding WebSockets message. + + @param buffers A object meeting the requirements of + ConstBufferSequence which holds the payload data before any + masking or compression. Although the buffers object may be copied + as necessary, ownership of the underlying buffers is retained by + the caller, which must guarantee that they remain valid until + the handler is called. + + @param handler The handler to be called when the write completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + boost::system::error_code const& error // result of operation + ); @endcode + */ + template + typename async_completion< + WriteHandler, void(error_code)>::result_type + async_write_frame(bool fin, + ConstBufferSequence const& buffers, WriteHandler&& handler); + +private: + template class accept_op; + template class close_op; + template class handshake_op; + template class response_op; + template class read_op; + template class read_frame_op; + template class write_op; + template class write_frame_op; + + http::request + build_request(boost::string_ref const& host, + boost::string_ref const& resource, + std::string& key); + + template + http::response + build_response(http::message const& req); + + template + void + do_response(http::message const& resp, + boost::string_ref const& key, error_code& ec); + + void + do_read_fh(detail::frame_streambuf& fb, + close_code& code, error_code& ec); +}; + +} // websocket +} // beast + +#include + +#endif diff --git a/include/beast/websocket/teardown.hpp b/include/beast/websocket/teardown.hpp new file mode 100644 index 00000000..2bbc6e24 --- /dev/null +++ b/include/beast/websocket/teardown.hpp @@ -0,0 +1,161 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEBSOCKET_TEARDOWN_HPP +#define BEAST_WEBSOCKET_TEARDOWN_HPP + +#include +#include +#include + +namespace beast { +namespace websocket { + +/** Tear down a connection. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown(Socket& socket, error_code& ec) = delete; + +/** Start tearing down a connection. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + +*/ +template +void +async_teardown(AsyncSocket& socket, TeardownHandler&& handler) = delete; + +//------------------------------------------------------------------------------ + +/** Tear down a `boost::asio::ip::tcp::socket`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec); + +/** Start tearing down a `boost::asio::ip::tcp::socket`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + +*/ +template +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler); + +} // websocket + +//------------------------------------------------------------------------------ + +namespace wsproto_helpers { + +// Calls to teardown and async_teardown must be made from +// a namespace that does not contain any overloads of these +// functions. The wsproto_helpers namespace is defined here +// for that purpose. + +template +inline +void +call_teardown(Socket& socket, websocket::error_code& ec) +{ + using websocket::teardown; + teardown(socket, ec); +} + +template +inline +void +call_async_teardown(Socket& socket, TeardownHandler&& handler) +{ + using websocket::async_teardown; + async_teardown(socket, + std::forward(handler)); +} + +} // websocket_helpers + +} // beast + +#include + +#endif diff --git a/include/beast/write_streambuf.hpp b/include/beast/write_streambuf.hpp new file mode 100644 index 00000000..f4e92485 --- /dev/null +++ b/include/beast/write_streambuf.hpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WRITE_STREAMBUF_HPP +#define BEAST_WRITE_STREAMBUF_HPP + +#include +#include +#include +#include + +namespace beast { + +/** Write to a Streambuf. + + This function appends the serialized representation of each provided + argument into the stream buffer. It is capable of converting the + following types of arguments: + + @li `boost::asio::const_buffer` + + @li `boost::asio::mutable_buffer` + + @li A type for which the call to `boost::asio::buffer()` is defined + + @li A type meeting the requirements of `ConstBufferSequence` + + @li A type meeting the requirements of `MutableBufferSequence` + + For all types not listed above, the function will invoke + `boost::lexical_cast` on the argument in an attempt to convert to + a string, which is then appended to the stream buffer. + + When this function serializes numbers, it converts them to + their text representation as if by a call to `std::to_string`. + + @param streambuf The stream buffer to write to. + + @param args A list of one or more arguments to write. + + @throws unspecified Any exceptions thrown by `boost::lexical_cast`. + + @note This function participates in overload resolution only if + the `streambuf` parameter meets the requirements of Streambuf. +*/ +template +#if GENERATING_DOCS +void +#else +typename std::enable_if::value>::type +#endif +write(Streambuf& streambuf, Args&&... args) +{ + detail::write_streambuf(streambuf, std::forward(args)...); +} + +} // beast + +#endif diff --git a/scripts/build-and-test.sh b/scripts/build-and-test.sh new file mode 100755 index 00000000..00eb6a89 --- /dev/null +++ b/scripts/build-and-test.sh @@ -0,0 +1,11 @@ +#!/bin/bash -u +# We use set -e and bash with -u to bail on first non zero exit code of any +# processes launched or upon any unbound variable +set -e +__dirname=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +echo "using toolset: $CC" +echo "using variant: $VARIANT" + +$BOOST_ROOT/bjam toolset=$CC variant=$VARIANT +`find . -name "core_tests"` +`find . -name "http_tests"` diff --git a/scripts/install-boost.sh b/scripts/install-boost.sh new file mode 100755 index 00000000..aa18b37d --- /dev/null +++ b/scripts/install-boost.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Assumptions: +# 1) BOOST_ROOT and BOOST_URL are already defined, +# and contain valid values. +# 2) The last namepart of BOOST_ROOT matches the +# folder name internal to boost's .tar.gz +# When testing you can force a boost build by clearing travis caches: +# https://travis-ci.org/ripple/rippled/caches +set -e +if [ ! -d "$BOOST_ROOT/lib" ] +then + wget $BOOST_URL -O /tmp/boost.tar.gz + cd `dirname $BOOST_ROOT` + rm -fr ${BOOST_ROOT} + tar xzf /tmp/boost.tar.gz + params="define=_GLIBCXX_USE_CXX11_ABI=0 --with-program_options --with-system" + cd $BOOST_ROOT && \ + ./bootstrap.sh --prefix=$BOOST_ROOT && \ + ./b2 -d1 $params && \ + ./b2 -d0 $params install +else + echo "Using cached boost at $BOOST_ROOT" +fi + diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh new file mode 100755 index 00000000..14cf6bbf --- /dev/null +++ b/scripts/install-dependencies.sh @@ -0,0 +1,34 @@ +#!/bin/bash -u +# Exit if anything fails. +set -e +# Override gcc version to $GCC_VER. +# Put an appropriate symlink at the front of the path. +mkdir -v $HOME/bin +for g in gcc g++ gcov gcc-ar gcc-nm gcc-ranlib +do + test -x $( type -p ${g}-$GCC_VER ) + ln -sv $(type -p ${g}-$GCC_VER) $HOME/bin/${g} +done +for c in clang clang++ +do + test -x $( type -p ${c}-$CLANG_VER ) + ln -sv $(type -p ${c}-$CLANG_VER) $HOME/bin/${c} +done +export PATH=$PWD/bin:$PATH + +# What versions are we ACTUALLY running? +if [ -x $HOME/bin/g++ ]; then + $HOME/bin/g++ -v +fi +if [ -x $HOME/bin/clang ]; then + $HOME/bin/clang -v +fi +# Avoid `spurious errors` caused by ~/.npm permission issues +# Does it already exist? Who owns? What permissions? +ls -lah ~/.npm || mkdir ~/.npm +# Make sure we own it +chown -Rc $USER ~/.npm +# We use this so we can filter the subtrees from our coverage report +#pip install --user https://github.com/vinniefalco/codecov-python/zipball/source-match + +bash scripts/install-boost.sh diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..ab83bdb7 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,82 @@ +# Part of Beast + +GroupSources(include/beast) +GroupSources(test) + +set(CORE_TESTS_SRCS + main.cpp + async_completion.cpp + basic_streambuf.cpp + bind_handler.cpp + buffer_cat.cpp + buffers_adapter.cpp + buffers_debug.cpp + consuming_buffers.cpp + handler_alloc.cpp + placeholders.cpp + prepare_buffers.cpp + static_streambuf.cpp + streambuf.cpp + streambuf_readstream.cpp + type_check.cpp + detail/base64.cpp + detail/empty_base_optimization.cpp +) + +add_executable (core-tests + ${BEAST_INCLUDES} + ${CORE_TESTS_SRCS} +) + +set(HTTP_TESTS_SRCS + main.cpp + http/basic_headers.cpp + http/basic_parser.cpp + http/chunk_encode.cpp + http/empty_body.cpp + http/error.cpp + http/headers.cpp + http/message.cpp + http/method.cpp + http/parse_error.cpp + http/parser.cpp + http/read.cpp + http/reason.cpp + http/resume_context.cpp + http/rfc2616.cpp + http/rfc7230.cpp + http/streambuf_body.cpp + http/string_body.cpp + http/write.cpp +) + +add_executable (http-tests + ${BEAST_INCLUDES} + ${HTTP_TESTS_SRCS} +) + +set(WEBSOCKET_TESTS_SRCS + main.cpp + websocket/error.cpp + websocket/option.cpp + websocket/rfc6455.cpp + websocket/static_string.cpp + websocket/teardown.cpp + websocket/utf8_checker.cpp +) + +add_executable (websocket-tests + ${BEAST_INCLUDES} + ${WEBSOCKET_TESTS_SRCS} +) + +set(PARSER_BENCH_SRCS + main.cpp + http/nodejs_parser.cpp + http/parser_bench.cpp +) + +add_executable (parser-bench + ${BEAST_INCLUDES} + ${PARSER_BENCH_SRCS} +) diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 00000000..953e79a3 --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,66 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +unit-test core-tests : + main.cpp + async_completion.cpp + basic_streambuf.cpp + bind_handler.cpp + buffer_cat.cpp + buffers_adapter.cpp + buffers_debug.cpp + consuming_buffers.cpp + handler_alloc.cpp + placeholders.cpp + prepare_buffers.cpp + static_streambuf.cpp + streambuf.cpp + streambuf_readstream.cpp + type_check.cpp + detail/base64.cpp + detail/empty_base_optimization.cpp + ; + +unit-test http-tests : + main.cpp + http/basic_headers.cpp + http/basic_parser.cpp + http/chunk_encode.cpp + http/empty_body.cpp + http/error.cpp + http/headers.cpp + http/message.cpp + http/method.cpp + http/parse_error.cpp + http/parser.cpp + http/read.cpp + http/reason.cpp + http/resume_context.cpp + http/rfc2616.cpp + http/rfc7230.cpp + http/streambuf_body.cpp + http/string_body.cpp + http/write.cpp + ; + +unit-test websocket-tests : + main.cpp + websocket/error.cpp + websocket/option.cpp + websocket/rfc6455.cpp + websocket/static_string.cpp + websocket/teardown.cpp + websocket/utf8_checker.cpp + ; + +unit-test parser-bench : + main.cpp + http/nodejs_parser.cpp + http/parser_bench.cpp + ; diff --git a/test/async_completion.cpp b/test/async_completion.cpp new file mode 100644 index 00000000..6dd0636f --- /dev/null +++ b/test/async_completion.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/basic_streambuf.cpp b/test/basic_streambuf.cpp new file mode 100644 index 00000000..80f8ef92 --- /dev/null +++ b/test/basic_streambuf.cpp @@ -0,0 +1,301 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +struct test_allocator_info +{ + std::size_t ncopy = 0; + std::size_t nmove = 0; + std::size_t nselect = 0; +}; + +template +class test_allocator; + +template +struct test_allocator_base +{ +}; + +template +struct test_allocator_base +{ + static + test_allocator + select_on_container_copy_construction( + test_allocator const& a) + { + return test_allocator{}; + } +}; + +template +class test_allocator : public test_allocator_base< + T, Assign, Move, Swap, Select> +{ + std::size_t id_; + std::shared_ptr info_; + + template + friend class test_allocator; + +public: + using value_type = T; + using propagate_on_container_copy_assignment = + std::integral_constant; + using propagate_on_container_move_assignment = + std::integral_constant; + using propagate_on_container_swap = + std::integral_constant; + + template + struct rebind + { + using other = test_allocator< + U, Assign, Move, Swap, Select>; + }; + + test_allocator() + : id_([] + { + static std::atomic< + std::size_t> sid(0); + return ++sid; + }()) + , info_(std::make_shared()) + { + } + + test_allocator(test_allocator const& u) noexcept + : id_(u.id_) + , info_(u.info_) + { + ++info_->ncopy; + } + + template + test_allocator(test_allocator< + U, Assign, Move, Swap, Select> const& u) noexcept + : id_(u.id_) + , info_(u.info_) + { + ++info_->ncopy; + } + + test_allocator(test_allocator&& t) + : id_(t.id_) + , info_(t.info_) + { + ++info_->nmove; + } + + value_type* + allocate(std::size_t n) + { + return static_cast( + ::operator new (n*sizeof(value_type))); + } + + void + deallocate(value_type* p, std::size_t) noexcept + { + ::operator delete(p); + } + + std::size_t + id() const + { + return id_; + } + + test_allocator_info const* + operator->() const + { + return info_.get(); + } +}; + +class basic_streambuf_test : public beast::detail::unit_test::suite +{ +public: + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + void + testPrepare() + { + using boost::asio::buffer_size; + streambuf sb(2); + expect(buffer_size(sb.prepare(5)) == 5); + expect(buffer_size(sb.prepare(8)) == 8); + expect(buffer_size(sb.prepare(7)) == 7); + } + + void testStreambuf() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string const s = "Hello, world"; + expect(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t z = s.size() - (x + y); + std::size_t v = s.size() - (t + u); + { + streambuf sb(i); + decltype(sb)::mutable_buffers_type d; + d = sb.prepare(z); expect(buffer_size(d) == z); + d = sb.prepare(0); expect(buffer_size(d) == 0); + d = sb.prepare(y); expect(buffer_size(d) == y); + d = sb.prepare(x); expect(buffer_size(d) == x); + sb.commit(buffer_copy(d, buffer(s.data(), x))); + expect(sb.size() == x); + expect(buffer_size(sb.data()) == sb.size()); + d = sb.prepare(x); expect(buffer_size(d) == x); + d = sb.prepare(0); expect(buffer_size(d) == 0); + d = sb.prepare(z); expect(buffer_size(d) == z); + d = sb.prepare(y); expect(buffer_size(d) == y); + sb.commit(buffer_copy(d, buffer(s.data()+x, y))); + sb.commit(1); + expect(sb.size() == x + y); + expect(buffer_size(sb.data()) == sb.size()); + d = sb.prepare(x); expect(buffer_size(d) == x); + d = sb.prepare(y); expect(buffer_size(d) == y); + d = sb.prepare(0); expect(buffer_size(d) == 0); + d = sb.prepare(z); expect(buffer_size(d) == z); + sb.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + sb.commit(2); + expect(sb.size() == x + y + z); + expect(buffer_size(sb.data()) == sb.size()); + expect(to_string(sb.data()) == s); + sb.consume(t); + d = sb.prepare(0); expect(buffer_size(d) == 0); + expect(to_string(sb.data()) == s.substr(t, std::string::npos)); + sb.consume(u); + expect(to_string(sb.data()) == s.substr(t + u, std::string::npos)); + sb.consume(v); + expect(to_string(sb.data()) == ""); + sb.consume(1); + d = sb.prepare(0); expect(buffer_size(d) == 0); + } + }}}}} + } + + template + static + bool + eq(basic_streambuf const& sb1, + basic_streambuf const& sb2) + { + return to_string(sb1.data()) == to_string(sb2.data()); + } + + void testSpecial() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string const s = "Hello, world"; + expect(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + streambuf sb(i); + sb.commit(buffer_copy(sb.prepare(x), buffer(s.data(), x))); + sb.commit(buffer_copy(sb.prepare(y), buffer(s.data()+x, y))); + sb.commit(buffer_copy(sb.prepare(z), buffer(s.data()+x+y, z))); + expect(to_string(sb.data()) == s); + { + streambuf sb2(sb); + expect(eq(sb, sb2)); + } + { + streambuf sb2; + sb2 = sb; + expect(eq(sb, sb2)); + } + { + streambuf sb2(std::move(sb)); + expect(to_string(sb2.data()) == s); + expect(buffer_size(sb.data()) == 0); + sb = std::move(sb2); + expect(to_string(sb.data()) == s); + expect(buffer_size(sb2.data()) == 0); + } + } + }}} + } + + void testAllocator() + { + { + using alloc_type = + test_allocator; + using sb_type = basic_streambuf; + sb_type sb; + expect(sb.get_allocator().id() == 1); + } + { + using alloc_type = + test_allocator; + using sb_type = basic_streambuf; + sb_type sb; + expect(sb.get_allocator().id() == 2); + sb_type sb2(sb); + expect(sb2.get_allocator().id() == 2); + sb_type sb3(sb, alloc_type{}); + //expect(sb3.get_allocator().id() == 3); + } + } + + void run() override + { + testPrepare(); + testStreambuf(); + testSpecial(); + testAllocator(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_streambuf,core,beast); + +} // test +} // beast diff --git a/test/bind_handler.cpp b/test/bind_handler.cpp new file mode 100644 index 00000000..1d478b8e --- /dev/null +++ b/test/bind_handler.cpp @@ -0,0 +1,37 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace test { + +class bind_handler_test : public detail::unit_test::suite +{ +public: + static void foo (int) + { + } + + void run() + { + auto f (bind_handler ( + std::bind (&foo, std::placeholders::_1), + 42)); + f(); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(bind_handler,core,beast); + +} // test +} // beast diff --git a/test/buffer_cat.cpp b/test/buffer_cat.cpp new file mode 100644 index 00000000..be44f0eb --- /dev/null +++ b/test/buffer_cat.cpp @@ -0,0 +1,81 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +class buffer_cat_test : public detail::unit_test::suite +{ +public: + template< class Iterator > + static + std::reverse_iterator + make_reverse_iterator( Iterator i ) + { + return std::reverse_iterator(i); + } + + void testBufferCat() + { + using boost::asio::buffer_size; + using boost::asio::const_buffer; + char buf[10]; + std::list b1; + std::vector b2{ + const_buffer{buf+0, 1}, + const_buffer{buf+1, 2}}; + std::list b3; + std::array b4{{ + const_buffer{buf+3, 1}, + const_buffer{buf+4, 2}, + const_buffer{buf+6, 3}}}; + std::list b5{ + const_buffer{buf+9, 1}}; + std::list b6; + auto bs = buffer_cat( + b1, b2, b3, b4, b5, b6); + expect(buffer_size(bs) == 10); + std::vector v; + for(auto iter = make_reverse_iterator(bs.end()); + iter != make_reverse_iterator(bs.begin()); ++iter) + v.emplace_back(*iter); + expect(buffer_size(bs) == 10); + decltype(bs) bs2(bs); + auto bs3(std::move(bs)); + bs = bs2; + bs3 = std::move(bs2); + { + boost::asio::streambuf sb1, sb2; + expect(buffer_size(buffer_cat( + sb1.prepare(5), sb2.prepare(7))) == 12); + sb1.commit(5); + sb2.commit(7); + expect(buffer_size(buffer_cat( + sb1.data(), sb2.data())) == 12); + } + } + + void run() override + { + testBufferCat(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffer_cat,core,beast); + +} // test +} // beast diff --git a/test/buffers_adapter.cpp b/test/buffers_adapter.cpp new file mode 100644 index 00000000..1b14cf42 --- /dev/null +++ b/test/buffers_adapter.cpp @@ -0,0 +1,122 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +namespace beast { +namespace test { + +class buffers_adapter_test : public detail::unit_test::suite +{ +public: + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + void testBuffersAdapter() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + using boost::asio::const_buffer; + using boost::asio::mutable_buffer; + char buf[12]; + std::string const s = "Hello, world"; + expect(s.size() == sizeof(buf)); + for(std::size_t i = 1; i < 4; ++i) { + for(std::size_t j = 1; j < 4; ++j) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t k = sizeof(buf) - (i + j); + std::size_t z = sizeof(buf) - (x + y); + std::size_t v = sizeof(buf) - (t + u); + { + std::memset(buf, 0, sizeof(buf)); + std::array bs{{ + mutable_buffer{&buf[0], i}, + mutable_buffer{&buf[i], j}, + mutable_buffer{&buf[i+j], k}}}; + buffers_adapter ba(std::move(bs)); + expect(ba.max_size() == sizeof(buf)); + decltype(ba)::mutable_buffers_type d; + d = ba.prepare(z); expect(buffer_size(d) == z); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(y); expect(buffer_size(d) == y); + d = ba.prepare(x); expect(buffer_size(d) == x); + ba.commit(buffer_copy(d, buffer(s.data(), x))); + expect(ba.size() == x); + expect(ba.max_size() == sizeof(buf) - x); + expect(buffer_size(ba.data()) == ba.size()); + d = ba.prepare(x); expect(buffer_size(d) == x); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(z); expect(buffer_size(d) == z); + d = ba.prepare(y); expect(buffer_size(d) == y); + ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + ba.commit(1); + expect(ba.size() == x + y); + expect(ba.max_size() == sizeof(buf) - (x + y)); + expect(buffer_size(ba.data()) == ba.size()); + d = ba.prepare(x); expect(buffer_size(d) == x); + d = ba.prepare(y); expect(buffer_size(d) == y); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(z); expect(buffer_size(d) == z); + ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + ba.commit(2); + expect(ba.size() == x + y + z); + expect(ba.max_size() == 0); + expect(buffer_size(ba.data()) == ba.size()); + expect(to_string(ba.data()) == s); + ba.consume(t); + d = ba.prepare(0); expect(buffer_size(d) == 0); + expect(to_string(ba.data()) == s.substr(t, std::string::npos)); + ba.consume(u); + expect(to_string(ba.data()) == s.substr(t + u, std::string::npos)); + ba.consume(v); + expect(to_string(ba.data()) == ""); + ba.consume(1); + d = ba.prepare(0); expect(buffer_size(d) == 0); + try + { + ba.prepare(1); + fail(); + } + catch(...) + { + pass(); + } + } + }}}}}} + } + void run() override + { + testBuffersAdapter(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffers_adapter,core,beast); + +} // test +} // beast diff --git a/test/buffers_debug.cpp b/test/buffers_debug.cpp new file mode 100644 index 00000000..cd420b86 --- /dev/null +++ b/test/buffers_debug.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/consuming_buffers.cpp b/test/consuming_buffers.cpp new file mode 100644 index 00000000..9b3ce353 --- /dev/null +++ b/test/consuming_buffers.cpp @@ -0,0 +1,95 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace test { + +class consuming_buffers_test : public beast::detail::unit_test::suite +{ +public: + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + void testBuffers() + { + using boost::asio::buffer; + using boost::asio::const_buffer; + char buf[12]; + std::string const s = "Hello, world"; + expect(s.size() == sizeof(buf)); + buffer_copy(buffer(buf), buffer(s)); + expect(to_string(buffer(buf)) == s); + for(std::size_t i = 1; i < 4; ++i) { + for(std::size_t j = 1; j < 4; ++j) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t k = sizeof(buf) - (i + j); + std::size_t z = sizeof(buf) - (x + y); + { + std::array bs{{ + const_buffer{&buf[0], i}, + const_buffer{&buf[i], j}, + const_buffer{&buf[i+j], k}}}; + consuming_buffers cb(bs); + expect(to_string(cb) == s); + cb.consume(0); + expect(to_string(cb) == s); + cb.consume(x); + expect(to_string(cb) == s.substr(x)); + cb.consume(y); + expect(to_string(cb) == s.substr(x+y)); + cb.consume(z); + expect(to_string(cb) == ""); + cb.consume(1); + expect(to_string(cb) == ""); + } + }}}} + } + + void testNullBuffers() + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::null_buffers; + consuming_buffers cb( + null_buffers{}); + expect(buffer_size(cb) == 0); + consuming_buffers cb2( + null_buffers{}); + expect(buffer_copy(cb2, cb) == 0); + } + + void run() override + { + testBuffers(); + testNullBuffers(); + } +}; + +BEAST_DEFINE_TESTSUITE(consuming_buffers,asio,beast); + +} // test +} // beast diff --git a/test/detail/base64.cpp b/test/detail/base64.cpp new file mode 100644 index 00000000..7125d9d0 --- /dev/null +++ b/test/detail/base64.cpp @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace beast { +namespace test { + +class base64_test : public detail::unit_test::suite +{ +public: + void + check (std::string const& in, std::string const& out) + { + auto const encoded = detail::base64_encode (in); + expect (encoded == out); + expect (detail::base64_decode (encoded) == in); + } + + void + run() + { + check ("", ""); + check ("f", "Zg=="); + check ("fo", "Zm8="); + check ("foo", "Zm9v"); + check ("foob", "Zm9vYg=="); + check ("fooba", "Zm9vYmE="); + check ("foobar", "Zm9vYmFy"); + } +}; + +BEAST_DEFINE_TESTSUITE(base64,detail,beast); + +} // test +} // beast + diff --git a/test/detail/empty_base_optimization.cpp b/test/detail/empty_base_optimization.cpp new file mode 100644 index 00000000..d6bf972d --- /dev/null +++ b/test/detail/empty_base_optimization.cpp @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2014, Howard Hinnant + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if BEAST_INCLUDE_BEASTCONFIG +#include +#endif + +#include + +#include + +namespace beast { +namespace test { + +class empty_base_optimization_test + : public beast::detail::unit_test::suite +{ +public: + template + class test1 + : private detail::empty_base_optimization + { + using Base = detail::empty_base_optimization; + void* m_p; + public: + explicit test1 (T const& t) + : Base (t) + {} + + T& member() {return Base::member();} + T const& member() const {return Base::member();} + }; + + template + class test2 + { + void* m_p; + T m_t; + public: + explicit test2 (T const& t) + : m_t (t) + {} + + T& member() {return m_t;} + T const& member() const {return m_t;} + }; + + struct Empty + { + operator bool() {return true;} + }; + + static + bool + test_one () + { + test1 t1(1); + test2 t2(2); + static_assert(sizeof(t1) == sizeof(t2), "don't optimize for int"); + if (t1.member() != 1) + return false; + if (t2.member() != 2) + return false; + return true; + } + + static + bool + test_two () + { + test1 t1((Empty())); + test2 t2((Empty())); + static_assert(sizeof(t1) < sizeof(t2), "do optimize for Empty"); + if (t1.member() != true) + return false; + if (t2.member() != true) + return false; + return true; + } + + void + run () + { + expect (test_one()); + expect (test_two()); + pass (); + } +}; + +BEAST_DEFINE_TESTSUITE(empty_base_optimization,detail,beast); + +} // test +} // beast diff --git a/test/handler_alloc.cpp b/test/handler_alloc.cpp new file mode 100644 index 00000000..f27a1943 --- /dev/null +++ b/test/handler_alloc.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/basic_headers.cpp b/test/http/basic_headers.cpp new file mode 100644 index 00000000..fc78890e --- /dev/null +++ b/test/http/basic_headers.cpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { +namespace test { + +class basic_headers_test : public beast::detail::unit_test::suite +{ +public: + template + using bha = basic_headers; + + using bh = basic_headers>; + + template + static + void + fill(std::size_t n, basic_headers& h) + { + for(std::size_t i = 1; i<= n; ++i) + h.insert(std::to_string(i), i); + } + + void testHeaders() + { + bh h1; + expect(h1.empty()); + fill(1, h1); + expect(h1.size() == 1); + bh h2; + h2 = h1; + expect(h2.size() == 1); + h2.insert("2", "2"); + expect(std::distance(h2.begin(), h2.end()) == 2); + h1 = std::move(h2); + expect(h1.size() == 2); + expect(h2.size() == 0); + bh h3(std::move(h1)); + expect(h3.size() == 2); + expect(h1.size() == 0); + } + + void run() override + { + testHeaders(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_headers,http,beast); + +} // test +} // asio +} // beast diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp new file mode 100644 index 00000000..e1cfc1e3 --- /dev/null +++ b/test/http/basic_parser.cpp @@ -0,0 +1,636 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "message_fuzz.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class basic_parser_test : public beast::detail::unit_test::suite +{ + std::mt19937 rng_; + +public: + struct cb_req_checker + { + bool method = false; + bool uri = false; + bool request = false; + }; + + struct cb_res_checker + { + bool reason = false; + bool response = false; + }; + + template + struct cb_checker + : public basic_parser> + , std::conditional::type + + { + bool field = false; + bool value = false; + bool headers = false; + bool body = false; + bool complete = false; + + private: + friend class basic_parser>; + + void on_method(boost::string_ref const&, error_code&) + { + this->method = true; + } + void on_uri(boost::string_ref const&, error_code&) + { + this->uri = true; + } + void on_reason(boost::string_ref const&, error_code&) + { + this->reason = true; + } + void on_request(error_code&) + { + this->request = true; + } + void on_response(error_code&) + { + this->response = true; + } + void on_field(boost::string_ref const&, error_code&) + { + field = true; + } + void on_value(boost::string_ref const&, error_code&) + { + value = true; + } + int on_headers(error_code&) + { + headers = true; + return 0; + } + void on_body(boost::string_ref const&, error_code&) + { + body = true; + } + void on_complete(error_code&) + { + complete = true; + } + }; + + //-------------------------------------------------------------------------- + + static + std::string + escaped_string(boost::string_ref const& s) + { + std::string out; + out.reserve(s.size()); + char const* p = s.data(); + while(p != s.end()) + { + if(*p == '\r') + out.append("\\r"); + else if(*p == '\n') + out.append("\\n"); + else if (*p == '\t') + out.append("\\t"); + else + out.append(p, 1); + ++p; + } + return out; + } + + template + struct null_parser : basic_parser> + { + }; + + template + class test_parser : + public basic_parser> + { + std::string field_; + std::string value_; + + void check() + { + if(! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + fields.emplace(field_, value_); + field_.clear(); + value_.clear(); + } + } + + public: + std::map fields; + std::string body; + + void on_field(boost::string_ref const& s, error_code&) + { + check(); + field_.append(s.data(), s.size()); + } + + void on_value(boost::string_ref const& s, error_code&) + { + value_.append(s.data(), s.size()); + } + + int on_headers(error_code&) + { + check(); + return 0; + } + + void on_body(boost::string_ref const& s, error_code&) + { + body.append(s.data(), s.size()); + } + }; + + void + testCallbacks() + { + { + cb_checker p; + error_code ec; + std::string const s = + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + p.write(s.data(), s.size(), ec); + if( expect(! ec)) + { + expect(p.method); + expect(p.uri); + expect(p.request); + expect(p.field); + expect(p.value); + expect(p.headers); + expect(p.body); + expect(p.complete); + } + } + { + cb_checker p; + error_code ec; + std::string const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + p.write(s.data(), s.size(), ec); + if( expect(! ec)) + { + expect(p.reason); + expect(p.response); + expect(p.field); + expect(p.value); + expect(p.headers); + expect(p.body); + expect(p.complete); + } + } + } + + // Parse the entire input buffer as a valid message, + // then parse in two pieces of all possible lengths. + // + template + void + parse(boost::string_ref const& m, F&& f) + { + { + error_code ec; + Parser p; + p.write(m.data(), m.size(), ec); + if(expect(p.complete())) + if(expect(! ec, ec.message())) + f(p); + } + for(std::size_t i = 1; i < m.size() - 1; ++i) + { + error_code ec; + Parser p; + p.write(&m[0], i, ec); + if(! expect(! ec, ec.message())) + continue; + if(p.complete()) + { + f(p); + } + else + { + p.write(&m[i], m.size() - i, ec); + if(! expect(! ec, ec.message())) + continue; + expect(p.complete()); + f(p); + } + } + } + + // Parse with an expected error code + // + template + void + parse_ev(boost::string_ref const& m, parse_error ev) + { + { + error_code ec; + null_parser p; + p.write(m.data(), m.size(), ec); + if(expect(! p.complete())) + expect(ec == ev, ec.message()); + } + for(std::size_t i = 1; i < m.size() - 1; ++i) + { + error_code ec; + null_parser p; + p.write(&m[0], i, ec); + if(! expect(! p.complete())) + continue; + if(ec) + { + expect(ec == ev, ec.message()); + continue; + } + p.write(&m[i], m.size() - i, ec); + if(! expect(! p.complete())) + continue; + if(! expect(ec == ev, ec.message())) + continue; + } + } + + //-------------------------------------------------------------------------- + + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + //-------------------------------------------------------------------------- + + // Parse a valid message with expected version + // + template + void + version(boost::string_ref const& m, + unsigned major, unsigned minor) + { + parse>(m, + [&](null_parser const& p) + { + expect(p.http_major() == major); + expect(p.http_minor() == minor); + }); + } + + // Parse a valid message with expected flags mask + // + void + checkf(boost::string_ref const& m, std::uint8_t mask) + { + parse>(m, + [&](null_parser const& p) + { + expect(p.flags() & mask); + }); + } + + void + testVersion() + { + version("GET / HTTP/0.0\r\n\r\n", 0, 0); + version("GET / HTTP/0.1\r\n\r\n", 0, 1); + version("GET / HTTP/0.9\r\n\r\n", 0, 9); + version("GET / HTTP/1.0\r\n\r\n", 1, 0); + version("GET / HTTP/1.1\r\n\r\n", 1, 1); + version("GET / HTTP/9.9\r\n\r\n", 9, 9); + version("GET / HTTP/999.999\r\n\r\n", 999, 999); + parse_ev("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version); + } + + void + testConnection(std::string const& token, + std::uint8_t flag) + { + checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + " \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + " \t\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \r\n" " " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" " " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \r\n" "\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" "\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X, " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X,\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X,\t " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + " \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag); + } + + void + testContentLength() + { + std::size_t const length = 0; + std::string const length_s = + std::to_string(length); + + checkf("GET / HTTP/1.1\r\nContent-Length:"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \t\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t \r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + } + + void + testTransferEncoding() + { + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \t\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t \r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked ); + } + + void + testFlags() + { + testConnection("keep-alive", + parse_flag::connection_keep_alive); + + testConnection("close", + parse_flag::connection_close); + + testConnection("upgrade", + parse_flag::connection_upgrade); + + testContentLength(); + + testTransferEncoding(); + + checkf( + "GET / HTTP/1.1\r\n" + "Upgrade: x\r\n" + "\r\n", + parse_flag::upgrade + ); + + parse_ev( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding:chunked\r\n" + "Content-Length: 0\r\n" + "\r\n", parse_error::illegal_content_length); + } + + void + testUpgrade() + { + null_parser p; + boost::string_ref s = + "GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n"; + error_code ec; + p.write(s.data(), s.size(), ec); + if(! expect(! ec, ec.message())) + return; + expect(p.complete()); + expect(p.upgrade()); + } + + void testBad() + { + parse_ev(" ", parse_error::bad_method); + parse_ev(" G", parse_error::bad_method); + parse_ev("G:", parse_error::bad_request); + parse_ev("GET /", parse_error::bad_uri); + parse_ev("GET / X", parse_error::bad_version); + parse_ev("GET / HX", parse_error::bad_version); + parse_ev("GET / HTTX", parse_error::bad_version); + parse_ev("GET / HTTPX", parse_error::bad_version); + parse_ev("GET / HTTP/.", parse_error::bad_version); + parse_ev("GET / HTTP/1000", parse_error::bad_version); + parse_ev("GET / HTTP/1. ", parse_error::bad_version); + parse_ev("GET / HTTP/1.1000", parse_error::bad_version); + parse_ev("GET / HTTP/1.1\r ", parse_error::bad_crlf); + parse_ev("GET / HTTP/1.1\r\nf :", parse_error::bad_field); + } + + void + testRandomReq(std::size_t N) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + std::string s; + { + streambuf sb; + mg.request(sb); + s.reserve(buffer_size(sb.data())); + for(auto const& b : sb.data()) + s.append(buffer_cast(b), + buffer_size(b)); + } + null_parser p; + for(std::size_t j = 1; j < s.size() - 1; ++j) + { + error_code ec; + p.write(&s[0], j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + if(! p.complete()) + { + p.write(&s[j], s.size() - j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + } + if(! expect(p.complete())) + break; + if(! p.keep_alive()) + { + p.~null_parser(); + new(&p) null_parser{}; + } + } + } + } + + void + testRandomResp(std::size_t N) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + std::string s; + { + streambuf sb; + mg.response(sb); + s.reserve(buffer_size(sb.data())); + for(auto const& b : sb.data()) + s.append(buffer_cast(b), + buffer_size(b)); + } + null_parser p; + for(std::size_t j = 1; j < s.size() - 1; ++j) + { + error_code ec; + p.write(&s[0], j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + if(! p.complete()) + { + p.write(&s[j], s.size() - j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + } + if(! expect(p.complete())) + break; + if(! p.keep_alive()) + { + p.~null_parser(); + new(&p) null_parser{}; + } + } + } + } + + void testBody() + { + auto match = + [&](std::string const& body) + { + return + [&](test_parser const& p) + { + expect(p.body == body); + }; + }; + parse>( + "GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1")); + parse>( + "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123")); + parse>( + "GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match("")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "1\r\n" + "a\r\n" + "0\r\n" + "\r\n", match("a")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "2\r\n" + "ab\r\n" + "0\r\n" + "\r\n", match("ab")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "2\r\n" + "ab\r\n" + "1\r\n" + "c\r\n" + "0\r\n" + "\r\n", match("abc")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "10\r\n" + "1234567890123456\r\n" + "0\r\n" + "\r\n", match("1234567890123456")); + } + + void run() override + { + testCallbacks(); + testVersion(); + testFlags(); + testUpgrade(); + testBad(); + testRandomReq(100); + testRandomResp(100); + testBody(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_parser,http,beast); + +} // http +} // beast diff --git a/test/http/chunk_encode.cpp b/test/http/chunk_encode.cpp new file mode 100644 index 00000000..31ba7bc1 --- /dev/null +++ b/test/http/chunk_encode.cpp @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace beast { +namespace http { +namespace test { + +class chunk_encode_test : public beast::detail::unit_test::suite +{ +public: + // Convert CR LF to printables for display + static + std::string + encode (std::string const& s) + { + std::string result; + for(auto const c : s) + { + if (c == '\r') + result += "\\r"; + else if (c== '\n') + result += "\\n"; + else + result += c; + } + return result; + } + + // Print the contents of a ConstBufferSequence to the log + template + static + void + print (ConstBufferSequence const& buffers, Log log) + { + for(auto const& buf : buffers) + log << encode (std::string( + boost::asio::buffer_cast(buf), + boost::asio::buffer_size(buf))); + } + + // Convert a ConstBufferSequence to a string + template + static + std::string + buffer_to_string (ConstBufferSequence const& b) + { + std::string s; + auto const n = boost::asio::buffer_size(b); + s.resize(n); + boost::asio::buffer_copy( + boost::asio::buffer(&s[0], n), b); + return s; + } + + // Append a ConstBufferSequence to an existing string + template + static + void + buffer_append (std::string& s, ConstBufferSequence const& b) + { + s += buffer_to_string(b); + } + + // Convert the input sequence of the stream to a + // chunked-encoded string. The input sequence is consumed. + template + static + std::string + streambuf_to_string (Streambuf& sb, + bool final_chunk = false) + { + std::string s; + buffer_append(s, chunk_encode(sb.data(), final_chunk)); + return s; + } + + // Check an input against the correct chunk encoded version + void + check (std::string const& in, std::string const& answer, + bool final_chunk = true) + { + streambuf sb(3); + sb << in; + auto const out = streambuf_to_string (sb, final_chunk); + if (! expect (out == answer)) + log << "expected\n" << encode(answer) << + "\ngot\n" << encode(out); + } + + void testStreambuf() + { + streambuf sb(3); + std::string const s = + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789"; + sb << s; + expect(buffer_to_string(sb.data()) == s); + } + + void + testEncoder() + { + check("", "0\r\n\r\n"); + check("x", "1\r\nx\r\n0\r\n\r\n"); + check("abcd", "4\r\nabcd\r\n0\r\n\r\n"); + check("x", "1\r\nx\r\n", false); + check( + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + , + "d2\r\n" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "\r\n" + "0\r\n\r\n"); + } + + void + run() + { + testStreambuf(); + testEncoder(); + } +}; + +BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast); + +} // test +} // http +} // beast + diff --git a/test/http/empty_body.cpp b/test/http/empty_body.cpp new file mode 100644 index 00000000..1ae53977 --- /dev/null +++ b/test/http/empty_body.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/error.cpp b/test/http/error.cpp new file mode 100644 index 00000000..8444e3ec --- /dev/null +++ b/test/http/error.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/headers.cpp b/test/http/headers.cpp new file mode 100644 index 00000000..79d68ffb --- /dev/null +++ b/test/http/headers.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/message.cpp b/test/http/message.cpp new file mode 100644 index 00000000..ee6aee61 --- /dev/null +++ b/test/http/message.cpp @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace test { + +class sync_echo_http_server +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + beast::detail::unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + beast::detail::unit_test::thread thread_; + +public: + sync_echo_http_server( + endpoint_type ep, beast::detail::unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_http_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = beast::detail::unit_test::thread(suite_, + [&] + { + ios_.run(); + }); + } + + ~sync_echo_http_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec && + ec != boost::asio::error::operation_aborted) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + std::thread{&sync_echo_http_server::do_client, this, + std::move(sock_), boost::asio::io_service::work{ + sock_.get_io_service()}}.detach(); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_http_server::on_accept, this, + beast::asio::placeholders::error)); + } + + void + do_client(socket_type sock, boost::asio::io_service::work) + { + error_code ec; + streambuf rb; + for(;;) + { + request req; + read(sock, rb, req, ec); + if(ec) + break; + response resp( + {100, "OK", req.version}); + resp.body = "Completed successfully."; + write(sock, resp, ec); + if(ec) + break; + } + } +}; + +class message_test : public beast::detail::unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + void + syncEcho(endpoint_type ep) + { + boost::asio::io_service ios; + socket_type sock(ios); + sock.connect(ep); + + streambuf rb; + { + request req( + {beast::http::method_t::http_get, "/", 11}); + req.body = "Beast.HTTP"; + req.headers.replace("Host", + ep.address().to_string() + ":" + + std::to_string(ep.port())); + write(sock, req); + } + { + response m; + read(sock, rb, m); + } + } + + void + testAsio() + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + sync_echo_http_server s(ep, *this); + syncEcho(ep); + } + + void run() override + { + //testAsio(); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(message,http,beast); + +} // test +} // http +} // beast diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp new file mode 100644 index 00000000..7d0254ee --- /dev/null +++ b/test/http/message_fuzz.hpp @@ -0,0 +1,562 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP +#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +std::string +escaped_string(boost::string_ref const& s) +{ + std::string out; + out.reserve(s.size()); + char const* p = s.data(); + while(p != s.end()) + { + if(*p == '\r') + out.append("\\r"); + else if(*p == '\n') + out.append("\\n"); + else if (*p == '\t') + out.append("\\t"); + else + out.append(p, 1); + ++p; + } + return out; +} + +// Produces random HTTP messages +// +template +class message_fuzz_t +{ + std::mt19937 rng_; + + static + std::string + to_hex(std::size_t v) + { + if(! v) + return "0"; + std::string s; + while(v > 0) + { + s.insert(s.begin(), + "0123456789abcdef"[v&0xf]); + v >>= 4; + } + return s; + } + +public: + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + std::string + method() + { +#if 0 + // All IANA registered methods + static char const* const list[39] = { + "ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT", "CONNECT", + "COPY", "DELETE", "GET", "HEAD", "LABEL", "LINK", "LOCK", "MERGE", + "MKACTIVITY", "MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE", + "MOVE", "OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI", "PROPFIND", + "PROPPATCH", "PUT", "REBIND", "REPORT", "SEARCH", "TRACE", "UNBIND", + "UNCHECKOUT", "UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF", + "VERSION-CONTROL" + }; + return list[rand(39)]; +#else + // methods parsed by nodejs-http-parser + static char const* const list[33] = { + "ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "HEAD", "GET", + "LINK", "LOCK", "MERGE", "MKCOL", "MKCALENDAR", "MKACTIVITY", "M-SEARCH", + "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", + "PURGE", "PUT", "REBIND", "REPORT", "SEARCH", "SUBSCRIBE", "TRACE", + "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE" + }; + return list[rand(33)]; +#endif + } + + std::string + scheme() + { + static char const* const list[241] = { + "aaa", "aaas", "about", "acap", "acct", "acr", "adiumxtra", "afp", "afs", + "aim", "appdata", "apt", "attachment", "aw", "barion", "beshare", "bitcoin", + "blob", "bolo", "callto", "cap", "chrome", "chrome-extension", "cid", + "coap", "coaps", "com-eventbrite-attendee", "content", "crid", "cvs", + "data", "dav", "dict", "dis", "dlna-playcontainer", "dlna-playsingle", + "dns", "dntp", "dtn", "dvb", "ed2k", "example", "facetime", "fax", "feed", + "feedready", "file", "filesystem", "finger", "fish", "ftp", "geo", "gg", + "git", "gizmoproject", "go", "gopher", "gtalk", "h323", "ham", "hcp", + "http", "https", "iax", "icap", "icon", "im", "imap", "info", "iotdisco", + "ipn", "ipp", "ipps", "irc", "irc6", "ircs", "iris", "iris.beep", + "iris.lwz", "iris.xpc", "iris.xpcs", "isostore", "itms", "jabber", "jar", + "jms", "keyparc", "lastfm", "ldap", "ldaps", "magnet", "mailserver", + "mailto", "maps", "market", "message", "mid", "mms", + "modem", "ms-access", "ms-drive-to", "ms-enrollment", "ms-excel", + "ms-getoffice", "ms-help", "ms-infopath", "ms-media-stream-id", "ms-project", + "ms-powerpoint", "ms-publisher", "ms-search-repair", + "ms-secondary-screen-controller", "ms-secondary-screen-setup", + "ms-settings", "ms-settings-airplanemode", "ms-settings-bluetooth", + "ms-settings-camera", "ms-settings-cellular", "ms-settings-cloudstorage", + "ms-settings-emailandaccounts", "ms-settings-language", "ms-settings-location", + "ms-settings-lock", "ms-settings-nfctransactions", "ms-settings-notifications", + "ms-settings-power", "ms-settings-privacy", "ms-settings-proximity", + "ms-settings-screenrotation", "ms-settings-wifi", "ms-settings-workplace", + "ms-spd", "ms-transit-to", "ms-visio", "ms-walk-to", "ms-word", "msnim", + "msrp", "msrps", "mtqp", "mumble", "mupdate", "mvn", "news", "nfs", "ni", + "nih", "nntp", "notes", "oid", "opaquelocktoken", "pack", "palm", "paparazzi", + "pkcs11", "platform", "pop", "pres", "prospero", "proxy", "psyc", "query", + "redis", "rediss", "reload", "res", "resource", "rmi", "rsync", "rtmfp", + "rtmp", "rtsp", "rtsps", "rtspu", "secondlife", "service", "session", "sftp", + "sgn", "shttp", "sieve", "sip", "sips", "skype", "smb", "sms", "smtp", + "snews", "snmp", "soap.beep", "soap.beeps", "soldat", "spotify", "ssh", + "steam", "stun", "stuns", "submit", "svn", "tag", "teamspeak", "tel", + "teliaeid", "telnet", "tftp", "things", "thismessage", "tip", "tn3270", + "tool", "turn", "turns", "tv", "udp", "unreal", "urn", "ut2004", "v-event", + "vemmi", "ventrilo", "videotex", "vnc", "view-source", "wais", "webcal", + "wpid", "ws", "wss", "wtai", "wyciwyg", "xcon", "xcon-userid", "xfire", + "xmlrpc.beep", "xmlrpc.beeps", "xmpp", "xri", "ymsgr", "z39.50", "z39.50r", + "z39.50s:" + }; + return list[rand(241)]; + } + + std::string + pchar() + { + if(rand(4)) + return std::string(1, + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + ":@&=+$,"[rand(69)]); + std::string s = "%"; + s += "0123456789abcdef"[rand(16)]; + s += "0123456789abcdef"[rand(16)]; + return s; + } + + char + uric() + { + return 'a'; + } + + char + uric_no_slash() + { + return 'a'; + } + + std::string + param() + { + std::string s; + while(rand(2)) + s += pchar(); + return s; + } + + std::string + query() + { + std::string s; + while(rand(2)) + s += uric(); + return s; + } + + std::string + userinfo() + { + std::string s; + while(rand(2)) + s += "a"; + return s; + } + + /* authority = server | reg_name + + reg_name = 1*( unreserved | escaped | "$" | "," | + ";" | ":" | "@" | "&" | "=" | "+" ) + + server = [ [ userinfo "@" ] hostport ] + userinfo = *( unreserved | escaped | + ";" | ":" | "&" | "=" | "+" | "$" | "," ) + + hostport = host [ ":" port ] + host = hostname | IPv4address + hostname = *( domainlabel "." ) toplabel [ "." ] + domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + toplabel = alpha | alpha *( alphanum | "-" ) alphanum + IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit + port = *digit + */ + std::string + server() + { + std::string s; + if(rand(2)) + s += userinfo() + "@"; + return s; + } + + std::string + reg_name() + { + std::string s; + s = "a"; + while(rand(2)) + s += "a"; + return s; + } + + std::string + authority() + { + if(rand(2)) + return server(); + return reg_name(); + } + + std::string + opaque_part() + { + std::string s; + s += uric_no_slash(); + while(rand(2)) + s += uric(); + return s; + } + + /* abs_path = "/" path_segments + path_segments = segment *( "/" segment ) + segment = *pchar *( ";" param ) + param = *pchar + pchar = unreserved | escaped | + ":" | "@" | "&" | "=" | "+" | "$" | "," + */ + std::string + abs_path() + { + std::string s = "/"; + for(;;) + { + while(rand(2)) + s += pchar(); + while(rand(2)) + s += ";" + param(); + if(rand(2)) + break; + s.append("/"); + } + return s; + } + + /* net_path = "//" authority [ abs_path ] + */ + std::string + net_path() + { + std::string s = "//"; + s += authority(); + if(rand(2)) + s += abs_path(); + return s; + } + + /* absoluteURI = scheme ":" ( hier_part | opaque_part ) + scheme = alpha *( alpha | digit | "+" | "-" | "." ) + hier_part = ( net_path | abs_path ) [ "?" query ] + abs_path = "/" path_segments + query = *uric + opaque_part = uric_no_slash *uric + */ + std::string + abs_uri() + { + std::string s; + s = scheme() + ":"; + if(rand(2)) + { + if(rand(2)) + s += net_path(); + else + s += abs_path(); + if(rand(2)) + s += "?" + query(); + } + else + { + s += opaque_part(); + } + return s; + } + + std::string + uri() + { + //switch(rand(4)) + switch(1) + { + case 0: return abs_uri(); + case 1: return abs_path(); + case 2: return authority(); + default: + case 3: break; + } + return "*"; + } + + std::string + token() + { + static char constexpr valid[78] = + "!#$%&\'*+-." "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" + "UVWXYZ^_`a" "bcdefghijk" "lmnopqrstu" "vwxyz|~"; + std::string s; + s.append(1, valid[rand(77)]); + while(rand(4)) + s.append(1, valid[rand(77)]); + return s; + } + +#if 0 + std::string + uri() + { + static char constexpr alpha[63] = + "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" + "UVWXYZabcd" "efghijklmn" "opqrstuvwx" "yz"; + std::string s; + s = "/"; + while(rand(4)) + s.append(1, alpha[rand(62)]); + return s; + } +#endif + + std::string + field() + { static char const* const list[289] = + { + "A-IM", + "Accept", "Accept-Additions", "Accept-Charset", "Accept-Datetime", "Accept-Encoding", + "Accept-Features", "Accept-Language", "Accept-Patch", "Accept-Ranges", "Age", "Allow", + "ALPN", "Also-Control", "Alt-Svc", "Alt-Used", "Alternate-Recipient", "Alternates", + "Apply-To-Redirect-Ref", "Approved", "Archive", "Archived-At", "Article-Names", + "Article-Updates", "Authentication-Info", "Authentication-Results", "Authorization", + "Auto-Submitted", "Autoforwarded", "Autosubmitted", "Base", "Bcc", "Body", "C-Ext", + "C-Man", "C-Opt", "C-PEP", "C-PEP-Info", "Cache-Control", + "CalDAV-Timezones", "Cc", "Close", "Comments", /*"Connection",*/ "Content-Alternative", + "Content-Base", "Content-Description", "Content-Disposition", "Content-Duration", + "Content-Encoding", "Content-features", "Content-ID", "Content-Identifier", + "Content-Language", /*"Content-Length",*/ "Content-Location", "Content-MD5", + "Content-Range", "Content-Return", "Content-Script-Type", "Content-Style-Type", + "Content-Transfer-Encoding", "Content-Type", "Content-Version", "Control", "Conversion", + "Conversion-With-Loss", "Cookie", "Cookie2", "DASL", "DAV", "DL-Expansion-History", "Date", + "Date-Received", "Default-Style", "Deferred-Delivery", "Delivery-Date", "Delta-Base", + "Depth", "Derived-From", "Destination", "Differential-ID", "Digest", + "Discarded-X400-IPMS-Extensions", "Discarded-X400-MTS-Extensions", "Disclose-Recipients", + "Disposition-Notification-Options", "Disposition-Notification-To", "Distribution", + "DKIM-Signature", "Downgraded-Bcc", "Downgraded-Cc", "Downgraded-Disposition-Notification-To", + "Downgraded-Final-Recipient", "Downgraded-From", "Downgraded-In-Reply-To", + "Downgraded-Mail-From", "Downgraded-Message-Id", "Downgraded-Original-Recipient", + "Downgraded-Rcpt-To", "Downgraded-References", "Downgraded-Reply-To", "Downgraded-Resent-Bcc", + "Downgraded-Resent-Cc", "Downgraded-Resent-From", "Downgraded-Resent-Reply-To", + "Downgraded-Resent-Sender", "Downgraded-Resent-To", "Downgraded-Return-Path", + "Downgraded-Sender", "Downgraded-To", "Encoding", "Encrypted", "ETag", "Expect", + "Expires", "Expiry-Date", "Ext", "Followup-To", "Forwarded", "From", + "Generate-Delivery-Report", "GetProfile", "Hobareg", "Host", "HTTP2-Settings", "IM", "If", + "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Schedule-Tag-Match", + "If-Unmodified-Since", "Importance", "In-Reply-To", "Incomplete-Copy", "Injection-Date", + "Injection-Info", "Keep-Alive", "Keywords", "Label", "Language", "Last-Modified", + "Latest-Delivery-Time", "Lines", "Link", "List-Archive", "List-Help", "List-ID", + "List-Owner", "List-Post", "List-Subscribe", "List-Unsubscribe", "Location", "Lock-Token", + "Man", "Max-Forwards", "Memento-Datetime", "Message-Context", "Message-ID", "Message-Type", + "Meter", "MIME-Version", "MMHS-Exempted-Address", "MMHS-Extended-Authorisation-Info", + "MMHS-Subject-Indicator-Codes", "MMHS-Handling-Instructions", "MMHS-Message-Instructions", + "MMHS-Codress-Message-Indicator", "MMHS-Originator-Reference", "MMHS-Primary-Precedence", + "MMHS-Copy-Precedence", "MMHS-Message-Type", "MMHS-Other-Recipients-Indicator-To", + "MMHS-Other-Recipients-Indicator-CC", "MMHS-Acp127-Message-Identifier", "MMHS-Originator-PLAD", + "MT-Priority", "Negotiate", "Newsgroups", "NNTP-Posting-Date", "NNTP-Posting-Host", + "Obsoletes", "Opt", "Ordering-Type", "Organization", "Origin", + "Original-Encoded-Information-Types", "Original-From", "Original-Message-ID", + "Original-Recipient", "Original-Sender", "Originator-Return-Address", "Original-Subject", + "Overwrite", "P3P", "Path", "PEP", "PICS-Label", "Pep-Info", "Position", "Posting-Version", + "Pragma", "Prefer", "Preference-Applied", "Prevent-NonDelivery-Report", "Priority", + "ProfileObject", "Protocol", "Protocol-Info", "Protocol-Query", "Protocol-Request", + "Proxy-Authenticate", "Proxy-Authentication-Info", "Proxy-Authorization", "Proxy-Features", + "Proxy-Instruction", "Public", "Public-Key-Pins", "Public-Key-Pins-Report-Only", "Range", + "Received", "Received-SPF", "Redirect-Ref", "References", "Referer", "Relay-Version", + "Reply-By", "Reply-To", "Require-Recipient-Valid-Since", "Resent-Bcc", "Resent-Cc", + "Resent-Date", "Resent-From", "Resent-Message-ID", "Resent-Reply-To", "Resent-Sender", + "Resent-To", "Retry-After", "Return-Path", "Safe", "Schedule-Reply", "Schedule-Tag", + "Sec-WebSocket-Accept", "Sec-WebSocket-Extensions", "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", "Sec-WebSocket-Version", "Security-Scheme", "See-Also", "Sender", + "Sensitivity", "Server", "Set-Cookie", "Set-Cookie2", + "SetProfile", "SLUG", "SoapAction", "Solicitation", "Status-URI", "Strict-Transport-Security", + "Subject", "Summary", "Supersedes", "Surrogate-Capability", "Surrogate-Control", "TCN", + "TE", "Timeout", "To", "Trailer", /*"Transfer-Encoding",*/ "URI", /*"Upgrade",*/ "User-Agent", + "Variant-Vary", "Vary", "VBR-Info", "Via", "WWW-Authenticate", "Want-Digest", "Warning", + "X400-Content-Identifier", "X400-Content-Return", "X400-Content-Type", "X400-MTS-Identifier", + "X400-Originator", "X400-Received", "X400-Recipients", "X400-Trace", "X-Frame-Options", "Xref" + }; + return list[rand(289)]; + } + + std::string + text() + { + std::string s; + while(rand(3)) + { + for(;;) + { + char c = rand(256); + if(detail::is_text(c)) + { + s.append(1, c); + break; + } + } + } + return s; + } + + std::string + value() + { + std::string s; + while(rand(3)) + { + if(rand(5)) + { + s.append(text()); + } + else + { + // LWS + if(! rand(4)) + s.append("\r\n"); + s.append(1, rand(2) ? ' ' : '\t'); + while(rand(2)) + s.append(1, rand(2) ? ' ' : '\t'); + } + } + return s; + } + + template + void + headers(Streambuf& sb) + { + while(rand(6)) + { + write(sb, field()); + write(sb, rand(4) ? ": " : ":"); + write(sb, value()); + write(sb, "\r\n"); + } + } + + template + void + body(Streambuf& sb) + { + if(! rand(4)) + { + write(sb, "Content-Length: 0\r\n\r\n"); + return; + } + if(rand(2)) + { + auto const len = rand(500); + write(sb, "Content-Length: ", len, "\r\n\r\n"); + for(auto const& b : sb.prepare(len)) + { + auto p = boost::asio::buffer_cast(b); + auto n = boost::asio::buffer_size(b); + while(n--) + *p++ = static_cast(32 + rand(26+26+10+6)); + } + sb.commit(len); + } + else + { + auto len = rand(500); + write(sb, "Transfer-Encoding: chunked\r\n\r\n"); + while(len > 0) + { + auto n = std::min(1 + rand(300), len); + len -= n; + write(sb, to_hex(n), "\r\n"); + for(auto const& b : sb.prepare(n)) + { + auto p = boost::asio::buffer_cast(b); + auto m = boost::asio::buffer_size(b); + while(m--) + *p++ = static_cast(32 + rand(26+26+10+6)); + } + sb.commit(n); + write(sb, "\r\n"); + } + write(sb, "0\r\n\r\n"); + } + } + + template + void + request(Streambuf& sb) + { + write(sb, method(), " ", uri(), " HTTP/1.1\r\n"); + headers(sb); + body(sb); + } + + template + void + response(Streambuf& sb) + { + write(sb, "HTTP/1."); + write(sb, rand(2) ? "0" : "1"); + write(sb, " ", 100 + rand(401), " "); + write(sb, token()); + write(sb, "\r\n"); + headers(sb); + body(sb); + write(sb, "\r\n"); + } +}; + +using message_fuzz = message_fuzz_t<>; + +} // http +} // beast + +#endif diff --git a/test/http/method.cpp b/test/http/method.cpp new file mode 100644 index 00000000..9328428c --- /dev/null +++ b/test/http/method.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/nodejs-parser/AUTHORS b/test/http/nodejs-parser/AUTHORS new file mode 100644 index 00000000..5323b685 --- /dev/null +++ b/test/http/nodejs-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +Thomas LE ROUX +Randy Rizun +Andre Louis Caron +Simon Zimmermann +Erik Dubbelboer +Martell Malone +Bertrand Paquet +BogDan Vatra +Peter Faiman +Corey Richardson +Tóth Tamás +Cam Swords +Chris Dickinson +Uli Köhler +Charlie Somerville +Patrik Stutz +Fedor Indutny +runner +Alexis Campailla +David Wragg +Vinnie Falco +Alex Butum +Rex Feng +Alex Kocharin +Mark Koopman +Helge Heß +Alexis La Goutte +George Miroshnykov +Maciej Małecki +Marc O'Morain +Jeff Pinner +Timothy J Fontaine +Akagi201 +Romain Giraud +Jay Satiro +Arne Steen +Kjell Schubert +Olivier Mengué diff --git a/test/http/nodejs-parser/LICENSE-MIT b/test/http/nodejs-parser/LICENSE-MIT new file mode 100644 index 00000000..58010b38 --- /dev/null +++ b/test/http/nodejs-parser/LICENSE-MIT @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +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. diff --git a/test/http/nodejs-parser/README.md b/test/http/nodejs-parser/README.md new file mode 100644 index 00000000..eedd7f8c --- /dev/null +++ b/test/http/nodejs-parser/README.md @@ -0,0 +1,246 @@ +HTTP Parser +=========== + +[![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](https://travis-ci.org/nodejs/http-parser) + +This is a parser for HTTP messages written in C. It parses both requests and +responses. The parser is designed to be used in performance HTTP +applications. It does not make any syscalls nor allocations, it does not +buffer data, it can be interrupted at anytime. Depending on your +architecture, it only requires about 40 bytes of data per message +stream (in a web server that is per connection). + +Features: + + * No dependencies + * Handles persistent streams (keep-alive). + * Decodes chunked encoding. + * Upgrade support + * Defends against buffer overflow attacks. + +The parser extracts the following information from HTTP messages: + + * Header fields and values + * Content-Length + * Request method + * Response status code + * Transfer-Encoding + * HTTP version + * Request URL + * Message body + + +Usage +----- + +One `http_parser` object is used per TCP connection. Initialize the struct +using `http_parser_init()` and set the callbacks. That might look something +like this for a request parser: +```c +http_parser_settings settings; +settings.on_url = my_url_callback; +settings.on_header_field = my_header_field_callback; +/* ... */ + +http_parser *parser = malloc(sizeof(http_parser)); +http_parser_init(parser, HTTP_REQUEST); +parser->data = my_socket; +``` + +When data is received on the socket execute the parser and check for errors. + +```c +size_t len = 80*1024, nparsed; +char buf[len]; +ssize_t recved; + +recved = recv(fd, buf, len, 0); + +if (recved < 0) { + /* Handle error. */ +} + +/* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been received. + */ +nparsed = http_parser_execute(parser, &settings, buf, recved); + +if (parser->upgrade) { + /* handle new protocol */ +} else if (nparsed != recved) { + /* Handle error. Usually just close the connection. */ +} +``` + +HTTP needs to know where the end of the stream is. For example, sometimes +servers send responses without Content-Length and expect the client to +consume input (for the body) until EOF. To tell http_parser about EOF, give +`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors +can still be encountered during an EOF, so one must still be prepared +to receive them. + +Scalar valued message information such as `status_code`, `method`, and the +HTTP version are stored in the parser structure. This data is only +temporally stored in `http_parser` and gets reset on each new message. If +this information is needed later, copy it out of the structure during the +`headers_complete` callback. + +The parser decodes the transfer-encoding for both requests and responses +transparently. That is, a chunked encoding is decoded before being sent to +the on_body callback. + + +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the WebSocket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the +WebSocket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body, issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() will stop parsing at the end of the headers and return. + +The user is expected to check if `parser->upgrade` has been set to 1 after +`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied +offset by the return value of `http_parser_execute()`. + + +Callbacks +--------- + +During the `http_parser_execute()` call, the callbacks set in +`http_parser_settings` will be executed. The parser maintains state and +never looks behind, so buffering the data is not necessary. If you need to +save certain data for later usage, you can do that from the callbacks. + +There are two types of callbacks: + +* notification `typedef int (*http_cb) (http_parser*);` + Callbacks: on_message_begin, on_headers_complete, on_message_complete. +* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` + Callbacks: (requests only) on_url, + (common) on_header_field, on_header_value, on_body; + +Callbacks must return 0 on success. Returning a non-zero value indicates +error to the parser, making it exit immediately. + +For cases where it is necessary to pass local information to/from a callback, +the `http_parser` object's `data` field can be used. +An example of such a case is when using threads to handle a socket connection, +parse a request, and then give a response over that socket. By instantiation +of a thread-local struct containing relevant data (e.g. accepted socket, +allocated memory for callbacks to write into, etc), a parser's callbacks are +able to communicate data between the scope of the thread and the scope of the +callback in a threadsafe manner. This allows http-parser to be used in +multi-threaded contexts. + +Example: +``` + typedef struct { + socket_t sock; + void* buffer; + int buf_len; + } custom_data_t; + + +int my_url_callback(http_parser* parser, const char *at, size_t length) { + /* access to thread local custom_data_t struct. + Use this access save parsed data for later use into thread local + buffer, or communicate over socket + */ + parser->data; + ... + return 0; +} + +... + +void http_parser_thread(socket_t sock) { + int nparsed = 0; + /* allocate memory for user data */ + custom_data_t *my_data = malloc(sizeof(custom_data_t)); + + /* some information for use by callbacks. + * achieves thread -> callback information flow */ + my_data->sock = sock; + + /* instantiate a thread-local parser */ + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); /* initialise parser */ + /* this custom data reference is accessible through the reference to the + parser supplied to callback functions */ + parser->data = my_data; + + http_parser_settings settings; / * set up callbacks */ + settings.on_url = my_url_callback; + + /* execute parser */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + ... + /* parsed information copied from callback. + can now perform action on data copied into thread-local memory from callbacks. + achieves callback -> thread information flow */ + my_data->buffer; + ... +} + +``` + +In case you parse HTTP message in chunks (i.e. `read()` request line +from socket, parse, read half headers, parse, etc) your data callbacks +may be called more than once. Http-parser guarantees that data pointer is only +valid for the lifetime of callback. You can also `read()` into a heap allocated +buffer to avoid copying memory around if this fits your application. + +Reading headers may be a tricky task if you read/parse headers partially. +Basically, you need to remember whether last header callback was field or value +and apply the following logic: + + (on_header_field and on_header_value shortened to on_h_*) + ------------------------ ------------ -------------------------------------------- + | State (prev. callback) | Callback | Description/action | + ------------------------ ------------ -------------------------------------------- + | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | + | | | into it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_field | New header started. | + | | | Copy current name,value buffers to headers | + | | | list and allocate new buffer for new name | + ------------------------ ------------ -------------------------------------------- + | field | on_h_field | Previous name continues. Reallocate name | + | | | buffer and append callback data to it | + ------------------------ ------------ -------------------------------------------- + | field | on_h_value | Value for current header started. Allocate | + | | | new buffer and copy callback data to it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_value | Value continues. Reallocate value buffer | + | | | and append callback data to it | + ------------------------ ------------ -------------------------------------------- + + +Parsing URLs +------------ + +A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. +Users of this library may wish to use it to parse URLs constructed from +consecutive `on_url` callbacks. + +See examples of reading in headers: + +* [partial example](http://gist.github.com/155877) in C +* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C +* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript diff --git a/test/http/nodejs-parser/http_parser.c b/test/http/nodejs-parser/http_parser.c new file mode 100644 index 00000000..71961754 --- /dev/null +++ b/test/http/nodejs-parser/http_parser.c @@ -0,0 +1,2468 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * 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 "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (enum state) (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + goto reexecute; \ + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start + , h_matching_connection_keep_alive + , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + , h_connection_upgrade + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + enum state p_state = (enum state) parser->state; + const unsigned int lenient = parser->lenient_http_headers; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (CURRENT_STATE() == s_header_field) + header_field_mark = data; + if (CURRENT_STATE() == s_header_value) + header_value_mark = data; + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); + +reexecute: + switch (CURRENT_STATE()) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_first_http_major); + break; + + case s_res_first_http_major: + if (UNLIKELY(ch < '0' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_major); + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_res_first_http_minor); + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + UPDATE_STATE(s_res_first_status_code); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + UPDATE_STATE(s_res_line_almost_done); + break; + case LF: + UPDATE_STATE(s_header_field_start); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (IS_ALPHA(ch)) { + + switch (parser->method << 16 | parser->index << 8 | ch) { +#define XX(meth, pos, ch, new_meth) \ + case (HTTP_##meth << 16 | pos << 8 | ch): \ + parser->method = HTTP_##new_meth; break; + + XX(POST, 1, 'U', PUT) + XX(POST, 1, 'A', PATCH) + XX(CONNECT, 1, 'H', CHECKOUT) + XX(CONNECT, 2, 'P', COPY) + XX(MKCOL, 1, 'O', MOVE) + XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 2, 'A', MKACTIVITY) + XX(MKCOL, 3, 'A', MKCALENDAR) + XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(REPORT, 2, 'B', REBIND) + XX(POST, 1, 'R', PROPFIND) + XX(PROPFIND, 4, 'P', PROPPATCH) + XX(PUT, 2, 'R', PURGE) + XX(LOCK, 1, 'I', LINK) + XX(UNLOCK, 2, 'S', UNSUBSCRIBE) + XX(UNLOCK, 2, 'B', UNBIND) + XX(UNLOCK, 3, 'I', UNLINK) +#undef XX + + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (ch == '-' && + parser->index == 1 && + parser->method == HTTP_MKCOL) { + parser->method = HTTP_MSEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + UPDATE_STATE((ch == CR) ? + s_req_line_almost_done : + s_header_field_start); + CALLBACK_DATA(url); + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_first_http_major); + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (UNLIKELY(ch < '1' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_major); + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_req_first_http_minor); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + /* XXX allow spaces after digit? */ + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_field_start); + break; + } + + case s_header_field_start: + { + if (ch == CR) { + UPDATE_STATE(s_headers_almost_done); + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + if (parser->flags & F_CONTENTLENGTH) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + parser->header_state = h_content_length; + parser->flags |= F_CONTENTLENGTH; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = (enum header_states) parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = (const char*) memchr(p, CR, limit); + p_lf = (const char*) memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; + break; + } + + case s_header_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + UPDATE_STATE(s_message_done); + CALLBACK_NOTIFY_NOADVANCE(chunk_complete); + REEXECUTE(); + } + + /* Cannot use chunked encoding and a content-length header together + per the HTTP specification. */ + if ((parser->flags & F_CHUNKED) && + (parser->flags & F_CONTENTLENGTH)) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: + { + int hasBody; + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + hasBody = parser->flags & F_CHUNKED || + (parser->content_length > 0 && parser->content_length != ULLONG_MAX); + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (!http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + if (parser->upgrade) { + /* Exit, the rest of the message is in a different protocol. */ + RETURN((p - data) + 1); + } + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + CALLBACK_NOTIFY(chunk_header); + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + CALLBACK_NOTIFY(chunk_complete); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +void +http_parser_settings_init(http_parser_settings *settings) +{ + memset(settings, 0, sizeof(*settings)); +} + +const char * +http_errno_name(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + assert(u->field_set & (1 << UF_HOST)); + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} diff --git a/test/http/nodejs-parser/http_parser.h b/test/http/nodejs-parser/http_parser.h new file mode 100644 index 00000000..105ae510 --- /dev/null +++ b/test/http/nodejs-parser/http_parser.h @@ -0,0 +1,362 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * 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 http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 7 +#define HTTP_PARSER_VERSION_PATCH 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef HTTP_MAX_HEADER_SIZE +# define HTTP_MAX_HEADER_SIZE (80*1024) +#endif + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* WebDAV */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + /* subversion */ \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + /* upnp */ \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + /* CalDAV */ \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 + , F_CONTENTLENGTH = 1 << 7 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + XX(CB_chunk_header, "the on_chunk_header callback failed") \ + XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 7; /* index into current matcher */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + */ + http_cb on_chunk_header; + http_cb on_chunk_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +/* Initialize http_parser_settings members to 0 + */ +void http_parser_settings_init(http_parser_settings *settings); + + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/test/http/nodejs_parser.cpp b/test/http/nodejs_parser.cpp new file mode 100644 index 00000000..a216ec0d --- /dev/null +++ b/test/http/nodejs_parser.cpp @@ -0,0 +1,17 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifdef _MSC_VER +# pragma warning (push) +# pragma warning (disable: 4127) // conditional expression is constant +# pragma warning (disable: 4244) // integer conversion, possible loss of data +#endif +#include "nodejs-parser/http_parser.c" +#ifdef _MSC_VER +# pragma warning (pop) +#endif + diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp new file mode 100644 index 00000000..44f13f09 --- /dev/null +++ b/test/http/nodejs_parser.hpp @@ -0,0 +1,881 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_NODEJS_PARSER_HPP +#define BEAST_HTTP_NODEJS_PARSER_HPP + +#include "nodejs-parser/http_parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +class nodejs_message_category + : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "nodejs-http-error"; + } + + std::string + message(int ev) const override + { + return http_errno_description( + static_cast(ev)); + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition{ev, *this}; + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(boost::system::error_code const& error, + int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +template +boost::system::error_code +make_nodejs_error(int http_errno) +{ + static nodejs_message_category const mc{}; + return boost::system::error_code{http_errno, mc}; +} + +inline +beast::http::method_t +convert_http_method(http_method m) +{ + using namespace beast; + switch (m) + { + case HTTP_DELETE: return http::method_t::http_delete; + case HTTP_GET: return http::method_t::http_get; + case HTTP_HEAD: return http::method_t::http_head; + case HTTP_POST: return http::method_t::http_post; + case HTTP_PUT: return http::method_t::http_put; + + // pathological + case HTTP_CONNECT: return http::method_t::http_connect; + case HTTP_OPTIONS: return http::method_t::http_options; + case HTTP_TRACE: return http::method_t::http_trace; + + // webdav + case HTTP_COPY: return http::method_t::http_copy; + case HTTP_LOCK: return http::method_t::http_lock; + case HTTP_MKCOL: return http::method_t::http_mkcol; + case HTTP_MOVE: return http::method_t::http_move; + case HTTP_PROPFIND: return http::method_t::http_propfind; + case HTTP_PROPPATCH: return http::method_t::http_proppatch; + case HTTP_SEARCH: return http::method_t::http_search; + case HTTP_UNLOCK: return http::method_t::http_unlock; + case HTTP_BIND: return http::method_t::http_bind; + case HTTP_REBIND: return http::method_t::http_rebind; + case HTTP_UNBIND: return http::method_t::http_unbind; + case HTTP_ACL: return http::method_t::http_acl; + + // subversion + case HTTP_REPORT: return http::method_t::http_report; + case HTTP_MKACTIVITY: return http::method_t::http_mkactivity; + case HTTP_CHECKOUT: return http::method_t::http_checkout; + case HTTP_MERGE: return http::method_t::http_merge; + + // upnp + case HTTP_MSEARCH: return http::method_t::http_msearch; + case HTTP_NOTIFY: return http::method_t::http_notify; + case HTTP_SUBSCRIBE: return http::method_t::http_subscribe; + case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe; + + // RFC-5789 + case HTTP_PATCH: return http::method_t::http_patch; + case HTTP_PURGE: return http::method_t::http_purge; + + // CalDav + case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar; + + // RFC-2068, section 19.6.1.2 + case HTTP_LINK: return http::method_t::http_link; + case HTTP_UNLINK: return http::method_t::http_unlink; + }; + + return http::method_t::http_get; +} + +} // detail + +template +class nodejs_basic_parser +{ + http_parser state_; + boost::system::error_code* ec_; + bool complete_ = false; + std::string url_; + std::string status_; + std::string field_; + std::string value_; + +public: + using error_code = boost::system::error_code; + + nodejs_basic_parser(nodejs_basic_parser&& other); + + nodejs_basic_parser& + operator=(nodejs_basic_parser&& other); + + nodejs_basic_parser(nodejs_basic_parser const& other); + + nodejs_basic_parser& operator=(nodejs_basic_parser const& other); + + explicit + nodejs_basic_parser(bool request) noexcept; + + bool + complete() const noexcept + { + return complete_; + } + + std::size_t + write(void const* data, std::size_t size) + { + error_code ec; + auto const used = write(data, size, ec); + if(ec) + throw boost::system::system_error{ec}; + return used; + } + + std::size_t + write(void const* data, std::size_t size, + error_code& ec); + + template + std::size_t + write(ConstBufferSequence const& buffers) + { + error_code ec; + auto const used = write(buffers, ec); + if(ec) + throw boost::system::system_error{ec}; + return used; + } + + template + std::size_t + write(ConstBufferSequence const& buffers, + error_code& ec); + + void + write_eof() + { + error_code ec; + write_eof(ec); + if(ec) + throw boost::system::system_error{ec}; + } + + void + write_eof(error_code& ec); + +private: + Derived& + impl() + { + return *static_cast(this); + } + + template + class has_on_start_t + { + template().on_start(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_start = + std::integral_constant::value>; + + void + call_on_start(std::true_type) + { + impl().on_start(); + } + + void + call_on_start(std::false_type) + { + } + + template + class has_on_field_t + { + template().on_field( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_field = + std::integral_constant::value>; + + void + call_on_field(std::string const& field, + std::string const& value, std::true_type) + { + impl().on_field(field, value); + } + + void + call_on_field(std::string const&, std::string const&, + std::false_type) + { + } + + template + class has_on_headers_complete_t + { + template().on_headers_complete( + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_headers_complete = + std::integral_constant::value>; + + void + call_on_headers_complete(error_code& ec, std::true_type) + { + impl().on_headers_complete(ec); + } + + void + call_on_headers_complete(error_code&, std::false_type) + { + } + + template + class has_on_request_t + { + template().on_request( + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_request = + std::integral_constant::value>; + + void + call_on_request(method_t method, std::string url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + impl().on_request( + method, url, major, minor, keep_alive, upgrade); + } + + void + call_on_request(method_t, std::string, int, int, bool, bool, + std::false_type) + { + } + + template + class has_on_response_t + { + template().on_response( + std::declval(), std::declval, + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); +#if 0 + using type = decltype(check(0)); +#else + // VFALCO Trait seems broken for http::parser + using type = std::true_type; +#endif + public: + static bool const value = type::value; + }; + template + using has_on_response = + std::integral_constant::value>; + + bool + call_on_response(int status, std::string text, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + return impl().on_response( + status, text, major, minor, keep_alive, upgrade); + } + + bool + call_on_response(int, std::string, int, int, bool, bool, + std::false_type) + { + // VFALCO Certainly incorrect + return true; + } + + template + class has_on_body_t + { + template().on_body( + std::declval(), std::declval(), + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_body = + std::integral_constant::value>; + + void + call_on_body(void const* data, std::size_t bytes, + error_code& ec, std::true_type) + { + impl().on_body(data, bytes, ec); + } + + void + call_on_body(void const*, std::size_t, + error_code&, std::false_type) + { + } + + template + class has_on_complete_t + { + template().on_complete(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_complete = + std::integral_constant::value>; + + void + call_on_complete(std::true_type) + { + impl().on_complete(); + } + + void + call_on_complete(std::false_type) + { + } + + void + check_header(); + + static int cb_message_start(http_parser*); + static int cb_url(http_parser*, char const*, std::size_t); + static int cb_status(http_parser*, char const*, std::size_t); + static int cb_header_field(http_parser*, char const*, std::size_t); + static int cb_header_value(http_parser*, char const*, std::size_t); + static int cb_headers_complete(http_parser*); + static int cb_body(http_parser*, char const*, std::size_t); + static int cb_message_complete(http_parser*); + static int cb_chunk_header(http_parser*); + static int cb_chunk_complete(http_parser*); + + struct hooks_t : http_parser_settings + { + hooks_t() + { + http_parser_settings_init(this); + on_message_begin = &nodejs_basic_parser::cb_message_start; + on_url = &nodejs_basic_parser::cb_url; + on_status = &nodejs_basic_parser::cb_status; + on_header_field = &nodejs_basic_parser::cb_header_field; + on_header_value = &nodejs_basic_parser::cb_header_value; + on_headers_complete = &nodejs_basic_parser::cb_headers_complete; + on_body = &nodejs_basic_parser::cb_body; + on_message_complete = &nodejs_basic_parser::cb_message_complete; + on_chunk_header = &nodejs_basic_parser::cb_chunk_header; + on_chunk_complete = &nodejs_basic_parser::cb_chunk_complete; + } + }; + + static + http_parser_settings const* + hooks(); +}; + +template +template +std::size_t +nodejs_basic_parser::write( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::size_t bytes_used = 0; + for (auto const& buffer : buffers) + { + auto const n = write( + buffer_cast(buffer), + buffer_size(buffer), ec); + if(ec) + return 0; + bytes_used += n; + if(complete()) + break; + } + return bytes_used; +} + +template +http_parser_settings const* +nodejs_basic_parser::hooks() +{ + static hooks_t const h; + return &h; +} + +template +nodejs_basic_parser:: +nodejs_basic_parser(nodejs_basic_parser&& other) +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = std::move(other.url_); + status_ = std::move(other.status_); + field_ = std::move(other.field_); + value_ = std::move(other.value_); +} + +template +auto +nodejs_basic_parser::operator=(nodejs_basic_parser&& other) -> + nodejs_basic_parser& +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = std::move(other.url_); + status_ = std::move(other.status_); + field_ = std::move(other.field_); + value_ = std::move(other.value_); + return *this; +} + +template +nodejs_basic_parser:: +nodejs_basic_parser(nodejs_basic_parser const& other) +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = other.url_; + status_ = other.status_; + field_ = other.field_; + value_ = other.value_; +} + +template +auto +nodejs_basic_parser:: +operator=(nodejs_basic_parser const& other) -> + nodejs_basic_parser& +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = other.url_; + status_ = other.status_; + field_ = other.field_; + value_ = other.value_; + return *this; +} + +template +nodejs_basic_parser::nodejs_basic_parser(bool request) noexcept +{ + state_.data = this; + http_parser_init(&state_, request + ? http_parser_type::HTTP_REQUEST + : http_parser_type::HTTP_RESPONSE); +} + +template +std::size_t +nodejs_basic_parser::write(void const* data, + std::size_t size, error_code& ec) +{ + ec_ = &ec; + auto const n = http_parser_execute( + &state_, hooks(), + static_cast(data), size); + if(! ec) + ec = detail::make_nodejs_error( + static_cast(state_.http_errno)); + if(ec) + return 0; + return n; +} + +template +void +nodejs_basic_parser::write_eof(error_code& ec) +{ + ec_ = &ec; + http_parser_execute( + &state_, hooks(), nullptr, 0); + if(! ec) + ec = detail::make_nodejs_error( + static_cast(state_.http_errno)); +} + +template +void +nodejs_basic_parser::check_header() +{ + if (! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + call_on_field(field_, value_, + has_on_field{}); + field_.clear(); + value_.clear(); + } +} + +template +int +nodejs_basic_parser::cb_message_start(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.complete_ = false; + t.url_.clear(); + t.status_.clear(); + t.field_.clear(); + t.value_.clear(); + t.call_on_start(has_on_start{}); + return 0; +} + +template +int +nodejs_basic_parser::cb_url(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.url_.append(in, bytes); + return 0; +} + +template +int +nodejs_basic_parser::cb_status(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.status_.append(in, bytes); + return 0; +} + +template +int +nodejs_basic_parser::cb_header_field(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.check_header(); + t.field_.append(in, bytes); + return 0; +} + +template +int +nodejs_basic_parser::cb_header_value(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.value_.append(in, bytes); + return 0; +} + +template +int +nodejs_basic_parser::cb_headers_complete(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.check_header(); + t.call_on_headers_complete(*t.ec_, + has_on_headers_complete{}); + if(*t.ec_) + return 1; + bool const keep_alive = + http_should_keep_alive(p) != 0; + if(p->type == http_parser_type::HTTP_REQUEST) + { + t.call_on_request(detail::convert_http_method( + http_method(p->method)), t.url_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_request{}); + return 0; + } + return t.call_on_response(p->status_code, t.status_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_response{}) ? 0 : 1; +} + +template +int +nodejs_basic_parser::cb_body(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.call_on_body(in, bytes, *t.ec_, has_on_body{}); + return *t.ec_ ? 1 : 0; +} + +template +int +nodejs_basic_parser::cb_message_complete(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.complete_ = true; + t.call_on_complete(has_on_complete{}); + return 0; +} + +template +int +nodejs_basic_parser::cb_chunk_header(http_parser*) +{ + return 0; +} + +template +int +nodejs_basic_parser::cb_chunk_complete(http_parser*) +{ + return 0; +} + +} // http +} // beast + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A HTTP parser. + + The parser may only be used once. +*/ +template +class nodejs_parser + : public nodejs_basic_parser> +{ + using message_type = + message; + + message_type m_; + typename message_type::body_type::reader r_; + bool started_ = false; + +public: + nodejs_parser(nodejs_parser&&) = default; + + nodejs_parser() + : http::nodejs_basic_parser(isRequest) + , r_(m_) + { + } + + /// Returns `true` if at least one byte has been processed + bool + started() + { + return started_; + } + + message_type + release() + { + return std::move(m_); + } + +private: + friend class http::nodejs_basic_parser; + + void + on_start() + { + started_ = true; + } + + void + on_field(std::string const& field, std::string const& value) + { + m_.headers.insert(field, value); + } + + void + on_headers_complete(error_code&) + { + // vFALCO TODO Decode the Content-Length and + // Transfer-Encoding, see if we can reserve the buffer. + // + // r_.reserve(content_length) + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.method = method; + m_.url = url; + m_.version = major * 10 + minor; + return true; + } + + bool + on_request(http::method_t, std::string const&, + int, int, bool, bool, + std::false_type) + { + return true; + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade) + { + return on_request(method, url, + major, minor, keep_alive, upgrade, + typename message_type::is_request{}); + } + + bool + on_response(int status, std::string const& reason, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.status = status; + m_.reason = reason; + m_.version = major * 10 + minor; + // VFALCO TODO return expect_body_ + return true; + } + + bool + on_response(int, std::string const&, int, int, bool, bool, + std::false_type) + { + return true; + } + + bool + on_response(int status, std::string const& reason, + int major, int minor, bool keep_alive, bool upgrade) + { + return on_response( + status, reason, major, minor, keep_alive, upgrade, + std::integral_constant{}); + } + + void + on_body(void const* data, + std::size_t size, error_code& ec) + { + r_.write(data, size, ec); + } + + void + on_complete() + { + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp new file mode 100644 index 00000000..5cc9b0f9 --- /dev/null +++ b/test/http/parse_error.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/parser.cpp b/test/http/parser.cpp new file mode 100644 index 00000000..e9ee2b70 --- /dev/null +++ b/test/http/parser.cpp @@ -0,0 +1,69 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace http { + +class parser_test : public beast::detail::unit_test::suite +{ +public: + void run() override + { + { + error_code ec; + parser>> p; + std::string const s = + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + p.write(s.data(), s.size(), ec); + expect(! ec); + expect(p.complete()); + auto m = p.release(); + expect(m.method == method_t::http_get); + expect(m.url == "/"); + expect(m.version == 11); + expect(m.headers["User-Agent"] == "test"); + expect(m.body == "*"); + } + { + error_code ec; + parser>> p; + std::string const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + p.write(s.data(), s.size(), ec); + expect(! ec); + expect(p.complete()); + auto m = p.release(); + expect(m.status == 200); + expect(m.reason == "OK"); + expect(m.version == 11); + expect(m.headers["Server"] == "test"); + expect(m.body == "*"); + } + } +}; + +BEAST_DEFINE_TESTSUITE(parser,http,beast); + +} // http +} // beast diff --git a/test/http/parser_bench.cpp b/test/http/parser_bench.cpp new file mode 100644 index 00000000..0df290cf --- /dev/null +++ b/test/http/parser_bench.cpp @@ -0,0 +1,155 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "nodejs_parser.hpp" +#include "message_fuzz.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_bench_test : public beast::detail::unit_test::suite +{ +public: + static std::size_t constexpr N = 2000; + + using corpus = std::vector; + + corpus creq_; + corpus cres_; + std::size_t size_ = 0; + + parser_bench_test() + { + creq_ = build_corpus(N/2, std::true_type{}); + cres_ = build_corpus(N/2, std::false_type{}); + } + + corpus + build_corpus(std::size_t n, std::true_type) + { + corpus v; + v.resize(N); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.request(v[i]); + size_ += v[i].size(); + } + return v; + } + + corpus + build_corpus(std::size_t n, std::false_type) + { + corpus v; + v.resize(N); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.response(v[i]); + size_ += v[i].size(); + } + return v; + } + + template + void + testParser(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& sb : v) + { + Parser p; + error_code ec; + p.write(sb.data(), ec); + if(! expect(! ec, ec.message())) + log << debug::buffers_to_string( + sb.data()) << std::endl; + } + } + + template + void + timedTest(std::size_t repeat, std::string const& name, Function&& f) + { + using namespace std::chrono; + using clock_type = std::chrono::high_resolution_clock; + log << name; + for(std::size_t trial = 1; trial <= repeat; ++trial) + { + auto const t0 = clock_type::now(); + f(); + auto const elapsed = clock_type::now() - t0; + log << + "Trial " << trial << ": " << + duration_cast(elapsed).count() << " ms"; + } + } + + template + struct null_parser : basic_parser> + { + }; + + void + testSpeed() + { + static std::size_t constexpr Trials = 3; + static std::size_t constexpr Repeat = 50; + + log << "sizeof(request parser) == " << + sizeof(basic_parser>); + + log << "sizeof(response parser) == " << + sizeof(basic_parser>); + + testcase << "Parser speed test, " << + ((Repeat * size_ + 512) / 1024) << "KB in " << + (Repeat * (creq_.size() + cres_.size())) << " messages"; + + timedTest(Trials, "nodejs_parser", + [&] + { + testParser>( + Repeat, creq_); + testParser>( + Repeat, cres_); + }); + timedTest(Trials, "http::basic_parser", + [&] + { + testParser>( + Repeat, creq_); + testParser>( + Repeat, cres_); + }); + pass(); + } + + void run() override + { + pass(); + testSpeed(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser_bench,http,beast); + +} // http +} // beast + diff --git a/test/http/read.cpp b/test/http/read.cpp new file mode 100644 index 00000000..70b057ba --- /dev/null +++ b/test/http/read.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/reason.cpp b/test/http/reason.cpp new file mode 100644 index 00000000..857d19be --- /dev/null +++ b/test/http/reason.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/resume_context.cpp b/test/http/resume_context.cpp new file mode 100644 index 00000000..02c9f11d --- /dev/null +++ b/test/http/resume_context.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/rfc2616.cpp b/test/http/rfc2616.cpp new file mode 100644 index 00000000..6ae47dd5 --- /dev/null +++ b/test/http/rfc2616.cpp @@ -0,0 +1,115 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace rfc2616 { +namespace test { + +class rfc2616_test : public beast::detail::unit_test::suite +{ +public: + void + checkSplit(std::string const& s, + std::vector const& expected) + { + auto const parsed = split_commas(s.begin(), s.end()); + expect (parsed == expected); + } + + void testSplit() + { + checkSplit("", {}); + checkSplit(" ", {}); + checkSplit(" ", {}); + checkSplit("\t", {}); + checkSplit(" \t ", {}); + checkSplit(",", {}); + checkSplit(",,", {}); + checkSplit(" ,", {}); + checkSplit(" , ,", {}); + checkSplit("x", {"x"}); + checkSplit(" x", {"x"}); + checkSplit(" \t x", {"x"}); + checkSplit("x ", {"x"}); + checkSplit("x \t", {"x"}); + checkSplit(" \t x \t ", {"x"}); + checkSplit("\"\"", {}); + checkSplit(" \"\"", {}); + checkSplit("\"\" ", {}); + checkSplit("\"x\"", {"x"}); + checkSplit("\" \"", {" "}); + checkSplit("\" x\"", {" x"}); + checkSplit("\"x \"", {"x "}); + checkSplit("\" x \"", {" x "}); + checkSplit("\"\tx \"", {"\tx "}); + checkSplit("x,y", {"x", "y"}); + checkSplit("x ,\ty ", {"x", "y"}); + checkSplit("x, y, z", {"x","y","z"}); + checkSplit("x, \"y\", z", {"x","y","z"}); + checkSplit(",,x,,\"y\",,", {"x","y"}); + } + + void + checkIter(std::string const& s, + std::vector const& expected) + { + std::vector got; + for(auto const& v : make_list(s)) + got.emplace_back(v); + expect(got == expected); + } + + void + testIter() + { + checkIter("x", {"x"}); + checkIter(" x", {"x"}); + checkIter("x\t", {"x"}); + checkIter("\tx ", {"x"}); + checkIter(",x", {"x"}); + checkIter("x,", {"x"}); + checkIter(",x,", {"x"}); + checkIter(" , x\t,\t", {"x"}); + checkIter("x,y", {"x", "y"}); + checkIter("x, ,y ", {"x", "y"}); + checkIter("\"x\"", {"x"}); + } + + void + testList() + { + expect(token_in_list("x", "x")); + expect(token_in_list("x,y", "x")); + expect(token_in_list("x,y", "y")); + expect(token_in_list("x, y ", "y")); + expect(token_in_list("x", "X")); + expect(token_in_list("Y", "y")); + expect(token_in_list("close, keepalive", "close")); + expect(token_in_list("close, keepalive", "keepalive")); + } + + void + run() + { + testSplit(); + testIter(); + testList(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc2616,http,beast); + +} // test +} // rfc2616 +} // beast diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp new file mode 100644 index 00000000..93a1a81a --- /dev/null +++ b/test/http/rfc7230.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/streambuf_body.cpp b/test/http/streambuf_body.cpp new file mode 100644 index 00000000..a1a57234 --- /dev/null +++ b/test/http/streambuf_body.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/string_body.cpp b/test/http/string_body.cpp new file mode 100644 index 00000000..43e2e0f7 --- /dev/null +++ b/test/http/string_body.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/write.cpp b/test/http/write.cpp new file mode 100644 index 00000000..3ea41773 --- /dev/null +++ b/test/http/write.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 00000000..22efdfcc --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +#include +# undef WIN32_LEAN_AND_MEAN +# else +#include +# endif +#endif + +#include + +// Simple main used to produce stand +// alone executables that run unit tests. +int main() +{ + using namespace beast::detail::unit_test; + +#ifdef _MSC_VER + { + int flags = _CrtSetDbgFlag (_CRTDBG_REPORT_FLAG); + flags |= _CRTDBG_LEAK_CHECK_DF; + _CrtSetDbgFlag (flags); + } +#endif + + { + beast::detail::debug_ostream s; + reporter r (s); + bool failed (r.run_each (global_suites())); + if (failed) + return EXIT_FAILURE; + return EXIT_SUCCESS; + } +} diff --git a/test/placeholders.cpp b/test/placeholders.cpp new file mode 100644 index 00000000..d16718b8 --- /dev/null +++ b/test/placeholders.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/prepare_buffers.cpp b/test/prepare_buffers.cpp new file mode 100644 index 00000000..8ec75ccf --- /dev/null +++ b/test/prepare_buffers.cpp @@ -0,0 +1,101 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +namespace beast { +namespace test { + +class prepare_buffers_test : public beast::detail::unit_test::suite +{ +public: + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + void testBuffers() + { + using boost::asio::buffer_size; + using boost::asio::const_buffer; + std::string const s = "Hello, world"; + expect(s.size() == 12); + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + std::array bs{{ + const_buffer{&s[0], x}, + const_buffer{&s[x], y}, + const_buffer{&s[x+y], z}}}; + for(std::size_t i = 0; i <= s.size() + 1; ++i) + { + auto pb = prepare_buffers(i, bs); + expect(to_string(pb) == s.substr(0, i)); + auto pb2 = pb; + expect(to_string(pb2) == to_string(pb)); + pb = prepare_buffers(0, bs); + pb2 = pb; + expect(buffer_size(pb2) == 0); + pb2 = prepare_buffers(i, bs); + expect(to_string(pb2) == s.substr(0, i)); + } + } + }} + } + + void testNullBuffers() + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::null_buffers; + auto pb0 = prepare_buffers(0, null_buffers{}); + expect(buffer_size(pb0) == 0); + auto pb1 = prepare_buffers(1, null_buffers{}); + expect(buffer_size(pb1) == 0); + expect(buffer_copy(pb0, pb1) == 0); + + using pb_type = decltype(pb0); + consuming_buffers cb(pb0); + expect(buffer_size(cb) == 0); + expect(buffer_copy(cb, pb1) == 0); + cb.consume(1); + expect(buffer_size(cb) == 0); + expect(buffer_copy(cb, pb1) == 0); + + auto pbc = prepare_buffers(2, cb); + expect(buffer_size(pbc) == 0); + expect(buffer_copy(pbc, cb) == 0); + } + + void run() override + { + testBuffers(); + testNullBuffers(); + } +}; + +BEAST_DEFINE_TESTSUITE(prepare_buffers,asio,beast); + +} // test +} // beast diff --git a/test/static_streambuf.cpp b/test/static_streambuf.cpp new file mode 100644 index 00000000..22c7bcc2 --- /dev/null +++ b/test/static_streambuf.cpp @@ -0,0 +1,111 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace test { + +class static_streambuf_test : public beast::detail::unit_test::suite +{ +public: + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + void testStaticStreambuf() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + char buf[12]; + std::string const s = "Hello, world"; + expect(s.size() == sizeof(buf)); + for(std::size_t i = 1; i < 4; ++i) { + for(std::size_t j = 1; j < 4; ++j) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t z = sizeof(buf) - (x + y); + std::size_t v = sizeof(buf) - (t + u); + { + std::memset(buf, 0, sizeof(buf)); + static_streambuf_n ba; + decltype(ba)::mutable_buffers_type d; + d = ba.prepare(z); expect(buffer_size(d) == z); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(y); expect(buffer_size(d) == y); + d = ba.prepare(x); expect(buffer_size(d) == x); + ba.commit(buffer_copy(d, buffer(s.data(), x))); + expect(ba.size() == x); + expect(buffer_size(ba.data()) == ba.size()); + d = ba.prepare(x); expect(buffer_size(d) == x); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(z); expect(buffer_size(d) == z); + d = ba.prepare(y); expect(buffer_size(d) == y); + ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + ba.commit(1); + expect(ba.size() == x + y); + expect(buffer_size(ba.data()) == ba.size()); + d = ba.prepare(x); expect(buffer_size(d) == x); + d = ba.prepare(y); expect(buffer_size(d) == y); + d = ba.prepare(0); expect(buffer_size(d) == 0); + d = ba.prepare(z); expect(buffer_size(d) == z); + ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + ba.commit(2); + expect(ba.size() == x + y + z); + expect(buffer_size(ba.data()) == ba.size()); + expect(to_string(ba.data()) == s); + ba.consume(t); + d = ba.prepare(0); expect(buffer_size(d) == 0); + expect(to_string(ba.data()) == s.substr(t, std::string::npos)); + ba.consume(u); + expect(to_string(ba.data()) == s.substr(t + u, std::string::npos)); + ba.consume(v); + expect(to_string(ba.data()) == ""); + ba.consume(1); + d = ba.prepare(0); expect(buffer_size(d) == 0); + try + { + ba.prepare(1); + fail(); + } + catch(...) + { + pass(); + } + } + }}}}}} + } + + void run() override + { + testStaticStreambuf(); + } +}; + +BEAST_DEFINE_TESTSUITE(static_streambuf,asio,beast); + +} // test +} // beastp diff --git a/test/streambuf.cpp b/test/streambuf.cpp new file mode 100644 index 00000000..608d5427 --- /dev/null +++ b/test/streambuf.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/streambuf_readstream.cpp b/test/streambuf_readstream.cpp new file mode 100644 index 00000000..80d34f6b --- /dev/null +++ b/test/streambuf_readstream.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/type_check.cpp b/test/type_check.cpp new file mode 100644 index 00000000..2bdde9a9 --- /dev/null +++ b/test/type_check.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/error.cpp b/test/websocket/error.cpp new file mode 100644 index 00000000..5800f3f2 --- /dev/null +++ b/test/websocket/error.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/option.cpp b/test/websocket/option.cpp new file mode 100644 index 00000000..07190cca --- /dev/null +++ b/test/websocket/option.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/rfc6455.cpp b/test/websocket/rfc6455.cpp new file mode 100644 index 00000000..d7ad9068 --- /dev/null +++ b/test/websocket/rfc6455.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/static_string.cpp b/test/websocket/static_string.cpp new file mode 100644 index 00000000..4b6431d7 --- /dev/null +++ b/test/websocket/static_string.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/teardown.cpp b/test/websocket/teardown.cpp new file mode 100644 index 00000000..324acbe3 --- /dev/null +++ b/test/websocket/teardown.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/websocket/utf8_checker.cpp b/test/websocket/utf8_checker.cpp new file mode 100644 index 00000000..39b96014 --- /dev/null +++ b/test/websocket/utf8_checker.cpp @@ -0,0 +1,264 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace websocket { + +class utf8_checker_test : public beast::detail::unit_test::suite +{ +public: + void + testOneByteSequence() + { + detail::utf8_checker utf8; + std::array const buf = + ([]() + { + std::array values; + std::uint8_t i = 0; + for (auto& c : values) + c = i++; + return values; + })(); + + // Valid range 0-127 + expect(utf8.write(buf.data(), 128)); + expect(utf8.finish()); + + // Invalid range 128-193 + for (auto it = std::next(buf.begin(), 128); + it != std::next(buf.begin(), 194); ++it) + expect(! utf8.write(&(*it), 1)); + + // Invalid range 245-255 + for (auto it = std::next(buf.begin(), 245); + it != buf.end(); ++it) + expect(! utf8.write(&(*it), 1)); + } + + void + testTwoByteSequence() + { + detail::utf8_checker utf8; + std::uint8_t buf[2]; + for(auto i = 194; i <= 223; ++i) + { + // First byte valid range 194-223 + buf[0] = static_cast(i); + + for (auto j = 128; j <= 191; ++j) + { + // Second byte valid range 128-191 + buf[1] = static_cast(j); + expect(utf8.write(buf, 2)); + expect(utf8.finish()); + } + + for (auto j = 0; j <= 127; ++j) + { + // Second byte invalid range 0-127 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 2)); + } + + for (auto j = 192; j <= 255; ++j) + { + // Second byte invalid range 192-255 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 2)); + } + } + } + + void + testThreeByteSequence() + { + detail::utf8_checker utf8; + std::uint8_t buf[3]; + for (auto i = 224; i <= 239; ++i) + { + // First byte valid range 224-239 + buf[0] = static_cast(i); + + std::int32_t const b = (i == 224 ? 160 : 128); + std::int32_t const e = (i == 237 ? 159 : 191); + for (auto j = b; j <= e; ++j) + { + // Second byte valid range 128-191 or 160-191 or 128-159 + buf[1] = static_cast(j); + + for (auto k = 128; k <= 191; ++k) + { + // Third byte valid range 128-191 + buf[2] = static_cast(k); + expect(utf8.write(buf, 3)); + expect(utf8.finish()); + } + + for (auto k = 0; k <= 127; ++k) + { + // Third byte invalid range 0-127 + buf[2] = static_cast(k); + expect(! utf8.write(buf, 3)); + } + + for (auto k = 192; k <= 255; ++k) + { + // Third byte invalid range 192-255 + buf[2] = static_cast(k); + expect(! utf8.write(buf, 3)); + } + } + + for (auto j = 0; j < b; ++j) + { + // Second byte invalid range 0-127 or 0-159 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 3)); + } + + for (auto j = e + 1; j <= 255; ++j) + { + // Second byte invalid range 160-255 or 192-255 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 3)); + } + } + } + + void + testFourByteSequence() + { + detail::utf8_checker utf8; + std::uint8_t buf[4]; + for (auto i = 240; i <= 244; ++i) + { + // First byte valid range 240-244 + buf[0] = static_cast(i); + + std::int32_t const b = (i == 240 ? 144 : 128); + std::int32_t const e = (i == 244 ? 143 : 191); + for (auto j = b; j <= e; ++j) + { + // Second byte valid range 128-191 or 144-191 or 128-143 + buf[1] = static_cast(j); + + for (auto k = 128; k <= 191; ++k) + { + // Third byte valid range 128-191 + buf[2] = static_cast(k); + + for (auto n = 128; n <= 191; ++n) + { + // Fourth byte valid range 128-191 + buf[3] = static_cast(n); + expect(utf8.write(buf, 4)); + expect(utf8.finish()); + } + + for (auto n = 0; n <= 127; ++n) + { + // Fourth byte invalid range 0-127 + buf[3] = static_cast(n); + expect(! utf8.write(buf, 4)); + } + + for (auto n = 192; n <= 255; ++n) + { + // Fourth byte invalid range 192-255 + buf[3] = static_cast(n); + expect(! utf8.write(buf, 4)); + } + } + + for (auto k = 0; k <= 127; ++k) + { + // Third byte invalid range 0-127 + buf[2] = static_cast(k); + expect(! utf8.write(buf, 4)); + } + + for (auto k = 192; k <= 255; ++k) + { + // Third byte invalid range 192-255 + buf[2] = static_cast(k); + expect(! utf8.write(buf, 4)); + } + } + + for (auto j = 0; j < b; ++j) + { + // Second byte invalid range 0-127 or 0-143 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 3)); + } + + for (auto j = e + 1; j <= 255; ++j) + { + // Second byte invalid range 144-255 or 192-255 + buf[1] = static_cast(j); + expect(! utf8.write(buf, 3)); + } + } + } + + void + testWithStreamBuffer() + { + using namespace boost::asio; + // Valid UTF8 encoded text + std::vector> const data{ + {0x48,0x65,0x69,0x7A,0xC3,0xB6,0x6C,0x72,0xC3,0xBC,0x63,0x6B, + 0x73,0x74,0x6F,0xC3,0x9F,0x61,0x62,0x64,0xC3,0xA4,0x6D,0x70, + 0x66,0x75,0x6E,0x67}, + {0xCE,0x93,0xCE,0xB1,0xCE,0xB6,0xCE,0xAD,0xCE,0xB5,0xCF,0x82, + 0x20,0xCE,0xBA,0xCE,0xB1,0xE1,0xBD,0xB6,0x20,0xCE,0xBC,0xCF, + 0x85,0xCF,0x81,0xCF,0x84,0xCE,0xB9,0xE1,0xBD,0xB2,0xCF,0x82, + 0x20,0xCE,0xB4,0xE1,0xBD,0xB2,0xCE,0xBD,0x20,0xCE,0xB8,0xE1, + 0xBD,0xB0,0x20,0xCE,0xB2,0xCF,0x81,0xE1,0xBF,0xB6,0x20,0xCF, + 0x80,0xCE,0xB9,0xE1,0xBD,0xB0,0x20,0xCF,0x83,0xCF,0x84,0xE1, + 0xBD,0xB8,0x20,0xCF,0x87,0xCF,0x81,0xCF,0x85,0xCF,0x83,0xCE, + 0xB1,0xCF,0x86,0xE1,0xBD,0xB6,0x20,0xCE,0xBE,0xCE,0xAD,0xCF, + 0x86,0xCF,0x89,0xCF,0x84,0xCE,0xBF}, + {0xC3,0x81,0x72,0x76,0xC3,0xAD,0x7A,0x74,0xC5,0xB1,0x72,0xC5, + 0x91,0x20,0x74,0xC3,0xBC,0x6B,0xC3,0xB6,0x72,0x66,0xC3,0xBA, + 0x72,0xC3,0xB3,0x67,0xC3,0xA9,0x70} + }; + detail::utf8_checker utf8; + for(auto const& s : data) + { + beast::streambuf sb( + s.size() / 4); // Force split across blocks + sb.commit(buffer_copy( + sb.prepare(s.size()), + const_buffer(s.data(), s.size()))); + expect(utf8.write(sb.data())); + expect(utf8.finish()); + } + } + + void run() override + { + testOneByteSequence(); + testTwoByteSequence(); + testThreeByteSequence(); + testFourByteSequence(); + testWithStreamBuffer(); + } +}; + +BEAST_DEFINE_TESTSUITE(utf8_checker,websocket,beast); + +} // websocket +} // beast diff --git a/test/write_streambuf.cpp b/test/write_streambuf.cpp new file mode 100644 index 00000000..68290fbc --- /dev/null +++ b/test/write_streambuf.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include +#include +#include + +namespace beast { + +class write_streambuf_test : public beast::detail::unit_test::suite +{ +public: + void run() override + { + streambuf sb; + std::string s; + write(sb, boost::asio::const_buffer{"", 0}); + write(sb, boost::asio::mutable_buffer{nullptr, 0}); + write(sb, boost::asio::null_buffers{}); + write(sb, boost::asio::const_buffers_1{"", 0}); + write(sb, boost::asio::mutable_buffers_1{nullptr, 0}); + write(sb, s); + write(sb, 23); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(write_streambuf,core,beast); + +} // beast